static_assert( __cplusplus > 2020'99 ); #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace Alepha { inline namespace Aluminum { namespace Mockination { class MockMutexImpl { private: using thread_type= Alepha::Truss::thread::id; using mutex_type= Alepha::Truss::mutex; using lock_type= Alepha::Truss::unique_lock< mutex_type >; using condition_type= Alepha::Truss::condition_variable; static inline thread_type get_this_thread() { return Alepha::Truss::this_thread::get_id(); } class Waiter { private: MockMutexImpl *const this_; condition_type condition; inline Waiter( const Waiter & )= delete; inline Waiter &operator= ( const Waiter & )= delete; public: inline ~Waiter() { this->this_->waiters.erase( get_this_thread() ); } explicit inline Waiter( MockMutexImpl *const i_t, const lock_type & ) : this_( i_t ) { this->this_->waiters[ get_this_thread() ]= this; } void wait( lock_type &lock ) { this->condition.wait( lock ); } void unblock( lock_type &lock ) { this->condition.notify_all(); } }; mutable mutex_type internal_mutex; mutable mutex_type access; mutable std::map< thread_type, Waiter * > waiters; mutable condition_type lockReleased; mutable bool unlockWaiterReady= false; mutable condition_type lockWaited; mutable condition_type lockEntered; mutable condition_type waiterAvailable; thread_type holder_; std::exception_ptr interruption; private: // Internal impls, unlocked inline lock_type lockAccess() const { return lock_type{ this->access }; } inline bool locked( const lock_type & ) const { return this->holder_ != thread_type{}; } inline thread_type holder( const lock_type & ) const { return this->holder_; } inline void waitLocked( lock_type &lock ) const { this->lockEntered.wait( lock, [this, &lock] { return this->locked( lock ); } ); } inline void waitLocked( lock_type &&lock ) const { this->waitLocked( lock ); } inline void waitUnlocked( lock_type &lock ) const { this->unlockWaiterReady= true; this->lockWaited.notify_all(); this->lockReleased.wait( lock, [this, &lock]{ return !this->locked( lock ); } ); } inline void waitUnlocked( lock_type &&lock ) const { this->waitUnlocked( lock ); } void allowFirst( lock_type &lock ) { assert( !this->waiters.empty() ); this->waiters.begin()->second->unblock( lock ); } void allowFirst( lock_type &&lock ) { this->allowFirst( lock ); } void allow( const thread_type next, lock_type &lock ) { assert( !this->waiters.empty() ); const auto found= this->waiters.find( next ); assert( found != this->waiters.end() ); found->second->unblock( lock ); } void allow( const thread_type next, lock_type &&lock ) { this->allow( next, lock ); } public: inline ~MockMutexImpl()= default; explicit inline MockMutexImpl( const MockMutexImpl & )= delete; inline MockMutexImpl &operator= ( const MockMutexImpl & )= delete; explicit inline MockMutexImpl( MockMutexImpl && )= delete; inline MockMutexImpl &operator= ( MockMutexImpl && )= delete; explicit inline MockMutexImpl()= default; // TODO: Consider breaking this part of the API out into a management handle // object that can be used to avoid having client threads work with the management // interface /*! * @brief Returns an observation of the waiting state. * @returns A list of the number of threads * @note That the returned value may become out of date, as soon as consumed. */ inline std::vector< thread_type > getWaiters() const { lock_type lock( this->access ); std::vector< thread_type > rv; rv.reserve( this->waiters.size() ); std::transform( begin( this->waiters ), end( this->waiters ), back_inserter( rv ), []( const auto &w ) { return w.first; } ); return rv; } inline void waitForWaiter( const thread_type waiter ) const { lock_type lock( this->access ); waiterAvailable.wait( lock, [this, waiter] { return this->waiters.find( waiter ) != end( this->waiters ); } ); } /*! * @brief Returns an observation of the waiting state. * @returns The number of threads waiting for entry to the lock. * @note That the returned value may become out of date, as soon as consumed. */ inline bool hasWaiters() const { lock_type lock( this->access ); return !this->waiters.empty(); } /*! * @brief Returns an observation of the locked-state. * @returns True if the lock is in the locked state. * @returns False if the lock is not in the locked state. */ inline bool locked() const { return this->locked( lock_type{ this->access } ); } /*! * @brief Returns an observation of the current holder of the lock. * @returns The `Alepha::Truss::thread::id` of the thread holding the lock. * @note A default constructed thread-id indicates no current holder. */ inline thread_type holder() const { return this->holder( this->lockAccess() ); } /*! * @brief Blocks the caller until the lock transitions to the locked state. * @pre The lock has been previously pumped with a request to transition a * waiter into the lock-held state. * @post The most recently requested waiter to enter the lock will transition into * the lock-held state. */ void waitLocked() const { this->waitLocked( this->lockAccess() ); } /*! * @brief Blocks the caller until the lock transitions to the unlocked state. * @pre The lock was previously pumped with a request to transition a * waiter into the lock-held state. (The lock is in the locked or * indeterminate state.) * @post The most recently requested waiter will have completed its critical * section. */ void waitUnlocked() const { this->waitUnlocked( this->lockAccess() ); } /*! * @brief Pumps the first waiter in the internal wait list to transition to the * locked state. * @pre The lock is in the unlocked state. * @post The lock is in an indeterminate state. * @note A call to `waitUnlocked` must be made before a subsequent call to * `allowFirst` or `allow` can be made -- otherwise the lock is in an * indeterminate state. * @note The "first waiter" is an unspecified waiting thread, and not necessarily * the longest waiting thread -- threads are not pumped in "fifo" order by * this operation. */ void allowFirst() { this->allowFirst( this->lockAccess() ); } /*! * @brief Pumps the first waiter in the internal wait list to transition to the * locked state. * @pre The lock is in the unlocked state. * @invariant A single thread will enter the lock, and then release it. * @post The lock has returned to the unlocked state, after a single thread entered. * @note `allowFirstWait` can be called multiple times in succession without * intervening calls. * @note The "first waiter" is an unspecified waiting thread, and not necessarily * the longest waiting thread -- threads are not pumped in "fifo" order by * this operation. */ void allowFirstWait() { lock_type lock( this->access ); this->allowFirst( lock ); this->waitLocked( lock ); this->waitUnlocked( lock ); } /*! * @brief Pumps the specified waiter in the internal wait list to transition to the * locked state. * @param next The thread to permit into the lock next. * @pre The lock is in the unlocked state. * @post The lock is in an indeterminate state. * @note A call to `waitUnlocked` must be made before a subsequent call to * `allowFirst` or `allow` can be made -- otherwise the lock is in an * indeterminate state. */ void allow( const thread_type next ) { this->allow( next, lock_type{ this->access } ); } /*! * @brief Pumps the specified waiter in the internal wait list to transition to the * locked state. * @param next The thread to permit into the lock next. * @pre The lock is in the unlocked state. * @invariant A single, specified thread will enter the lock, and then release it. * @post The lock has returned to the unlocked state, after the specified thread * entered. * @note `allowWait` can be called multiple times in succession without * intervening calls. */ void allowWait( const thread_type next ) { lock_type lock( this->access ); this->allow( next, lock ); this->waitLocked( lock ); assert( this->holder( lock ) == next ); this->waitUnlocked( lock ); } #if 0 template< typename Exception > void interruptFirst( Exception exc ) { lock_type lock( this->access ); assert( !this->waiters.empty() ); this->interruption= std::make_exception_ptr( std::move( exc ) ); this->waiters.begin()->second->unblock( lock ); } template< typename Exception > void interrupt( const thread_type next, Exception exc ) { lock_type lock( this->access ); const auto found= this->waiters.find( next ); assert( found != this->waiters.end() ); this->interruption= std::make_exception_ptr( std::move( exc ) ); found->second->unblock( lock ); } #endif // Mutex interface: /*! * @brief Attempt to assert the lock. * @pre The current thread does NOT hold the lock. * @post The lock is in the locked state and `Alepha::Truss::this_thread::get_id()` * is registered as the current holder of this thread. */ inline void lock() { lock_type lock( this->access ); assert( this->holder_ != get_this_thread() ); // Waiter needs to stick around until exiting this function -- it indicates // an actual waiting state. Exiting this function removes that state. Waiter waiter( this, lock ); waiterAvailable.notify_all(); // unblock anyone waiting for new waiters. waiter.wait( lock ); if( this->interruption ) { auto interrupt= this->interruption; this->interruption= nullptr; std::rethrow_exception( interrupt ); } this->holder_= get_this_thread(); this->lockEntered.notify_all(); this->internal_mutex.lock(); } /*! * @brief Release control over this lock. * @pre The current thread holds the lock. * @post The lock transitions to the unlocked state and this thread is deregistered * as the current holder of this thread. * * @note Threads do not actually release the lock until permitted to do so by * a `waitUnlocked` operation from an external thread, or if the thread * entered this lock by means of an `allowWait` or `allowFirstWait` * operation. */ // Unlocking is techincally a thread synchronization point, but we will // crash the program at present, since it gets called from destructors. // TODO: Use a universal interrupt block here. inline void unlock() noexcept { lock_type lock( this->access ); assert( this->holder_ == get_this_thread() ); this->internal_mutex.unlock(); while( !this->unlockWaiterReady ) { this->lockWaited.wait( lock ); } this->unlockWaiterReady= false; this->holder_= {}; this->lockReleased.notify_all(); } }; template< int id > class MockMutex { public: static MockMutexImpl impl; inline void unlock() noexcept { impl.unlock(); } inline void lock() noexcept { impl.lock(); } }; template< int id > MockMutexImpl MockMutex< id >::impl; } } }