static_assert( __cplusplus > 2020'99 ); /*! * @file Dropbox.h * @brief Class which abstracts a "dropbox" metaphor, for threaded programming * * Copyright (C) 2024 Alepha Library. All rights reserved.
* @author ADAM David Alan Martin */ #pragma once #include #include #include #include #include #include #include #include "DeadDrop.h" namespace Alepha::Hydrogen::Atomic ::detail:: Dropbox_m { namespace C { const bool debug= false; const bool debugInterlock= false or C::debug; const bool debugPop= false or C::debug; const bool debugSSO= false or C::debug; } inline bool isSSO( const std::string &s ) { const auto base= reinterpret_cast< const char * >( &s ); if constexpr( C::debugSSO ) { error() << "Base is: " << (void *) base << std::endl; error() << "Data is: " << (void *) s.data() << std::endl; error() << "Storage base is: " << sizeof( s ) << std::endl; } const bool result= std::less_equal{}( base, s.data() ) and std::less{}( s.data(), base + sizeof( s ) ); if( C::debugSSO and result ) error() << "Found an SSO" << std::endl; return result; } inline namespace exports { template< typename > class Dropbox; using DropboxFinishedException= synthetic_exception< struct finished_exception, FinishedException >; using AnyTaggedDropboxFinishedException= AnyTagged< DropboxFinishedException >; template< typename tag > using TaggedDropboxFinishedException= Tagged< DropboxFinishedException, tag >; using DropboxFinishedCondition= synthetic_exception< struct finished_exception, FinishedCondition, DropboxFinishedException >; using AnyTaggedDropboxFinishedCondition= AnyTagged< DropboxFinishedCondition >; template< typename tag > using TaggedDropboxFinishedCondition= Tagged< DropboxFinishedCondition, tag >; // TODO: Move these to somewhere more sensible... template< typename T > std::size_t computeWeight( const T & ) { return sizeof( T ); } inline std::size_t computeWeight( const std::string &s ) { if( isSSO( s ) ) return sizeof( s ); return sizeof( s ) + s.capacity(); } template< typename T > std::size_t computeWeight( const std::vector< T > &v ) { std::size_t sz= sizeof( v ) + sizeof( T ) * ( v.capacity() - v.size() ); for( const auto &i: v ) { sz+= computeWeight( sz ); } return sz; } std::size_t computeWeight( const Concepts::Aggregate auto &agg ) { std::size_t rv= 0; // This won't account for slack space... but I have to separate out // the `sizeof` checks from the way `computeWeight` works. It's // "good enough" for now... template_for( agg ) <=[&]( const auto &element ) { rv+= computeWeight( element ); }; return rv; } } /*! * @brief The Dropbox class implements a Dropbox metaphor. Just like anonymous dead-drop boxes, * the Dropbox class lets users add items to a box from a single producer thread * and in a single consumer thread consume the contents of the mailbox. * * @tparam Item The Item type which is used in the dropbox containers. * * @note Dropboxes only support one consumer and one producer. * * @note Dropboxes only block on attempting to acquire the next `DeadDrop` to use after exhaustion. * * @invariant Dropboxes give out their contents in the order placed in. * * The Dropbox primitive is implemented as DeadDrop object which is rotated periodically. * When a producer or consumer exhausts the current DeadDrop port, it `release`es it. When * it needs a new DeadDrop port to actually use, it will `acquire` it. Only `acquire` blocks. */ template< typename Item > class exports::Dropbox { public: using FinishedException= synthetic_exception< struct finished_exception, DropboxFinishedException >; //using AnyTaggedFinishedException= AnyTagged< FinishedException >; //template< typename tag > using TaggedFinishedException= Tagged< FinishedException, tag >; using FinishedCondition= synthetic_exception< struct finished_exception, DropboxFinishedCondition, FinishedException >; //using AnyTaggedFinishedCondition= AnyTagged< FinishedCondition >; //template< typename tag > using TaggedFinishedCondition= Tagged< FinishedCondition, tag >; private: const std::size_t boxLimit; std::size_t weight= 0; struct Box { std::vector< Item > items; std::vector< Item >::iterator pos= begin( items ); std::exception_ptr notification; }; DeadDrop< Box > drops; void sendoff() { auto &producer= drops.producer.acquireBox(); producer.pos= begin( producer.items ); weight= 0; drops.producer.releaseBox(); } void recycle() { auto &filled= drops.consumer.acquireBox(); filled.notification= nullptr; filled.items.clear(); drops.consumer.releaseBox(); } public: explicit Dropbox( const size_t lim ) : boxLimit( lim ) { recycle(); } Item pop() { auto &filled= drops.consumer.acquireBox(); if( filled.pos == end( filled.items ) ) { [[unlikely]]; auto notification= std::move( filled.notification ); recycle(); std::rethrow_exception( std::move( notification ) ); } assert( not filled.items.empty() ); auto &rv= *filled.pos++; // If it was the last item and we're going to rotate/reccyle, // be sure that we don't reference the now-stale value. // This is a special case that will only come up at the end of each queue // cycle. // // I need to research if NRV applies in this circumstance -- if so, then the // `rv` variable can be an instance instead of a reference. if( filled.pos == end( filled.items ) and filled.notification == nullptr ) { [[unlikely]]; auto rvTemp= std::move( rv ); recycle(); return rvTemp; } return std::move( rv ); } bool pushWouldBlock() const noexcept { return weight > boxLimit; } void push( Item item ) { if( pushWouldBlock() ) [[unlikely]] sendoff(); assertion( weight <= boxLimit ); auto &preparing= drops.producer.acquireBox(); weight+= computeWeight( item ); preparing.items.push_back( std::move( item ) ); } void notify( auto newNotification ) { auto &preparing= drops.producer.acquireBox(); preparing.notification= std::make_exception_ptr( std::move( newNotification ) ); sendoff(); } void finish( auto newNotification ) { auto &preparing= drops.producer.acquireBox(); preparing.notification= std::make_exception_ptr( std::move( newNotification ) ); sendoff(); } void finish() { finish( build_exception< FinishedCondition >( "" ) ); } }; } namespace Alepha::Hydrogen::Atomic::inline exports::inline Dropbox_m { using namespace detail::Dropbox_m::exports; }