diff --git a/Mockination/MockCondition.h b/Mockination/MockCondition.h new file mode 100644 index 0000000..342b093 --- /dev/null +++ b/Mockination/MockCondition.h @@ -0,0 +1,62 @@ +#include +register "Alepha/Mockination/MockCondition.h"; + +#include + +#include +#include + +namespace Alepha +{ + inline namespace Aluminum + { + namespace Mockination + { + class MockCondition + { + private: + Alepha::Truss::mutex access; + Alepha::Truss::condition condition; + + public: + inline void + notify_all() + { + this->condition.notify_all(); + } + + inline void + notify_one() + { + this->condition.notify_one(); + } + + template< typename UniqueLock > + inline void + wait( UniqueLock &lock ) + { + auto fake_lock= Alepha::use_unique( this->access ); + lock.unlock(); + this->condition.wait( fake_lock ); + lock.lock(); + } + + template< typename UniqueLock, typename Predicate > + inline void + wait( UniqueLock &lock, Predicate &&predicate ) + { + while( !predicate() ) + { + { + auto fake_lock= Alepha::use_unique( this->access ); + lock.unlock(); + this->condition.wait( fake_lock ); + } + lock.lock(); + } + assert( predicate() ); + } + }; + } + } +} diff --git a/Mockination/MockFunction.h b/Mockination/MockFunction.h new file mode 100644 index 0000000..37b70f0 --- /dev/null +++ b/Mockination/MockFunction.h @@ -0,0 +1,235 @@ +#include +register "Alepha/Mockination/mockination"; + +#include +#include + +#include +#include + +#include + +namespace Alepha +{ + inline namespace Aluminum + { + namespace Mockination + { + template< std::size_t order, std::size_t count > + struct overload_count + { + using type= overload_count; + static constexpr std::size_t value= count; + }; + + // We'd like a `boost::mpl::map`, probably. A metafunction for now. + + template< typename ... Args > struct count_map; + + template< std::size_t order, std::size_t count > + struct count_map< overload_count< order, count > > + { + using entry_type= overload_count< order, count >; + }; + + template< std::size_t order, std::size_t count, typename ... Args > + struct count_map< overload_count< order, count >, Args... > + { + using entry_type= overload_count< order, count >; + }; + + //template< template CountMap, std::size_t order_key > struct find_entry; + + #if 0 + template< std::size_t order_key, std::size_t count, typename ... Args > + find_entry< count_map< overload_count< order_key, count >, Args... >, + { + using type= + } + #endif + + template< int id, typename Function > class MockFunctionImpl; + + template< int id, typename Function, typename ... Selection > struct select_overload; + + template< int id, typename Function > + struct select_overload< id, Function, Function > + { + using type= MockFunctionImpl< id, Function >; + }; + + template< int id, typename Function, typename Selection0, typename ... Selections > + struct select_overload< id, Function, Selection0, Selections... > + : select_overload< id, Function, Selections... > {}; + + template< int id, typename Function, typename ... Selections > + struct select_overload< id, Function, Function, Selections... > + { + using type= MockFunctionImpl< id, Function >; + }; + + + class skip_execution {}; + + + template< int id, typename ... Args > + class MockFunctionImpl< id, void ( Args ... ) > + { + public: + static Alepha::Truss::function< void ( Args ... ) > impl; + + using return_type= void; + + template< typename Needed > + using overload= typename select_overload< id, Needed, void ( Args... ) >::type; + + MockFunctionImpl()= default; + + static void clear() { impl= nullptr; } + + void + operator() ( Args ... args ) const + { + if( impl == nullptr ) abort(); + return impl( std::forward< Args >( args )... ); + } + + static void set_operation( Alepha::Truss::function< void ( Args ... ) > i ) + { + std::cerr << "Set operation impl..." << std::endl; + impl= std::move( i ); + } + + static void set_operation_impl( Alepha::Truss::function< void ( Args ... ) > i ) { impl= std::move( i ); } + + static void + add_operation( Alepha::Truss::function< void ( Args ... ) > i ) + { + Alepha::Truss::function< void ( Args ... ) > first= impl ? impl : []( Args ... ){}; + impl= [first, i]( Args... args ) + { + try { i( args... ); } catch( const skip_execution & ) {} + first( args... ); + }; + } + }; + template< int id, typename ... Args > + Alepha::Truss::function< void ( Args ... ) > MockFunctionImpl< id, void ( Args ... ) >::impl= nullptr; + + template< int id, typename Rv, typename ... Args > + class MockFunctionImpl< id, Rv ( Args ... ) > + { + public: + static Alepha::Truss::function< Rv ( Args ... ) > impl; + + using return_type= Rv; + + template< typename Needed > + using overload= typename select_overload< id, Needed, Rv ( Args... ) >::type; + + MockFunctionImpl()= default; + + static void clear() { impl= nullptr; } + + Rv operator() ( Args ... args ) const { return impl( std::forward< Args >( args )... ); } + + static void + set_result( Rv r ) + { + impl= [r]( Args ... args ){ return r; }; + } + + static void set_operation( Alepha::Truss::function< Rv ( Args ... ) > i ) { impl= std::move( i ); } + static void set_operation_impl( Alepha::Truss::function< Rv ( Args ... ) > i ) { impl= std::move( i ); } + + static Rv + add_operation( Alepha::Truss::function< Rv ( Args ... ) > i ) + { + impl= [first= impl, i]( Args ... args ) + { + try { return i( args... ); } catch( const skip_execution & ) {} + return first( args... ); + }; + } + }; + + template< int id, typename Rv, typename ... Args > + Alepha::Truss::function< Rv ( Args... ) > MockFunctionImpl< id, Rv ( Args... ) >::impl= nullptr; + + template< int id, typename ... Funcs > class MockFunction; + + template< int id, typename Rv, typename ... Args > + class MockFunction< id, Rv( Args ... ) > + : public MockFunctionImpl< id, Rv( Args... ) > + { + public: + static void clear_all() { MockFunctionImpl< id, Rv( Args... ) >::clear(); } + }; + + template< typename M > struct match; + template< typename Class > + struct match< void (Class::*) ( std::size_t ) > : std::true_type + { + }; + template< typename Class > + struct match< void (Class::*) ( std::size_t ) const > : std::true_type + { + }; + + template< typename T > + auto + soak_traits( T )//typename std::enable_if< std::is_class< T >::value, T >::type ) + { + return typename Meta::functor_traits< decltype( &T::operator() ) >::type{}; + } + + template< typename Rv, typename ... Args > + Meta::functor_traits< Rv ( Args... ) > + soak_traits( Rv ( Args... ) ) + { + return Meta::functor_traits< Rv ( Args... ) >{}; + } + + template< int id, typename Func0, typename ... Funcs > + class MockFunction< id, Func0, Funcs... > + : public MockFunctionImpl< id, Func0 >, public MockFunction< id, Funcs... > + { + public: + template< typename Needed > + using overload= typename select_overload< id, Needed, Func0, Funcs... >::type; + + template< typename Callable > + static void + set_operation( Callable c ) + { + std::cerr << "Set operation deduced..." << std::endl; + auto magic_traits= soak_traits( c ); + using magic_traits_type= decltype( magic_traits ); + using traits= typename magic_traits_type::type; + using ftype= typename traits::std_function_type; + + static_assert( Meta::require_relationship< std::is_base_of, + MockFunctionImpl< id, typename traits::functor_type >, + MockFunction >::type::value, + "No overload exists in this mock for the specified function type." ); + MockFunctionImpl< id, typename traits::functor_type >::set_operation_impl( c ); + } + + template< typename Callable, typename FuncType > + static void + set_operation( Callable c ) + { + std::cerr << "Set operation forced..." << std::endl; + MockFunctionImpl< id, FuncType >::set_operation_impl( Alepha::Truss::function< FuncType >{ c } ); + } + + static void + clear_all() + { + MockFunctionImpl< id, Func0 >::clear(); + MockFunction< id, Funcs... >::clear_all(); + } + }; + } + } +} diff --git a/Mockination/MockMutex.h b/Mockination/MockMutex.h new file mode 100644 index 0000000..7775f82 --- /dev/null +++ b/Mockination/MockMutex.h @@ -0,0 +1,412 @@ +#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; + } + } +} diff --git a/Mockination/MockMutex.test/Makefile b/Mockination/MockMutex.test/Makefile new file mode 100644 index 0000000..8493643 --- /dev/null +++ b/Mockination/MockMutex.test/Makefile @@ -0,0 +1,22 @@ +CPPFLAGS+= -I ../../../ +CXXFLAGS+= -std=c++1z +CXXFLAGS+= -g -O0 +#CXXFLAGS+= -O3 +CXX=g++ +LDLIBS+= -lboost_thread -lboost_system +LDLIBS+= -lpthread +CC=gcc + +TESTS=$(shell ls test* | sed -e "s/\.cc//g" -e 's/\.o//g' | sort | uniq) +TEST_OBJS=`ls test* | sed -e 's/.cc/.o/g' | grep '\.o\>' | sort | uniq` + +all: $(TESTS) + + +HEADERS= ../MockMutex.h + +test0.o: $(HEADERS) +test1.o: $(HEADERS) + +clean: + rm -f *.o $(TESTS) diff --git a/Mockination/MockMutex.test/test0.cc b/Mockination/MockMutex.test/test0.cc new file mode 100644 index 0000000..9e2cf17 --- /dev/null +++ b/Mockination/MockMutex.test/test0.cc @@ -0,0 +1,164 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace Alepha::Testing; + +namespace +{ + static std::shared_timed_mutex access; + static std::unordered_map< Alepha::Truss::thread::id, int > idMap; + static std::atomic< int > next{ 0 }; + + static int + getSimpleThreadId() + { + { + std::shared_lock< std::shared_timed_mutex > lock( access ); + auto found= idMap.find( Alepha::Truss::this_thread::get_id() ); + if( found != end( idMap ) ) return found->second; + } + + std::unique_lock< std::shared_timed_mutex > lock( access ); + return idMap[ Alepha::Truss::this_thread::get_id() ]= next++; + } + + + static std::ostream & + print() + { + const auto id= getSimpleThreadId(); + return std::cerr << "Thread " << id << ": "; + } + + auto test= "basic locking smoke test"_test <=[] + { + Alepha::Mockination::MockMutexImpl mtx; + + print() << "Creating thread." << std::endl; + + auto lockingCode= [&mtx] + { + print() << "Going to lock" << std::endl; + mtx.lock(); + print() << "Locked" << std::endl; + mtx.unlock(); + print() << "Unlocked" << std::endl; + }; + + // Test allow/waitLocked/waitUnlocked + auto thread1= Alepha::Truss::thread{ lockingCode }; + + print() << "Control will now sleep for 2 seconds to let slave into lock." << std::endl; + sleep( 2 ); + + print() << "Control will now permit thread 1 into the lock, in 2 seconds." << std::endl; + + sleep( 2 ); + + mtx.allowFirst(); + mtx.waitLocked(); + mtx.waitUnlocked(); + + thread1.join(); + + // Test allow/waitUnlocked + thread1= Alepha::Truss::thread{ lockingCode }; + + print() << "Control will now sleep for 2 seconds to let slave into lock." << std::endl; + sleep( 2 ); + + print() << "Control will now permit thread 1 into the lock, in 2 seconds." << std::endl; + + sleep( 2 ); + + mtx.allowFirst(); + mtx.waitUnlocked(); + + thread1.join(); + + // Test allowFirstWait + thread1= Alepha::Truss::thread{ lockingCode }; + + print() << "Control will now sleep for 2 seconds to let slave into lock." << std::endl; + sleep( 2 ); + + print() << "Control will now permit thread 1 into the lock, in 2 seconds." << std::endl; + + sleep( 2 ); + + mtx.allowFirstWait(); + + thread1.join(); + }; + + auto test2= "basic locking correctness"_test <=[] + { + Alepha::Mockination::MockMutexImpl mtx; + + assert( !mtx.hasWaiters() ); + assert( !mtx.locked() ); + + print() << "Creating thread." << std::endl; + auto thread1= Alepha::Truss::thread + { + [&mtx] + { + print() << "Going to lock" << std::endl; + mtx.lock(); + print() << "Locked" << std::endl; + assert( mtx.locked() ); + auto waiters= mtx.getWaiters(); + assert( !mtx.hasWaiters() ); + assert( mtx.getWaiters().size() == 0 ); + assert( waiters.empty() ); + assert( waiters.size() == 0 ); + mtx.unlock(); + print() << "Unlocked" << std::endl; + } + }; + assert( !mtx.locked() ); + while( !mtx.hasWaiters() ); // Wait for some waiters... + + assert( mtx.getWaiters().size() == 1 ); + assert( mtx.getWaiters().at( 0 ) == thread1.get_id() ); + + print() << "Control will now permit thread 1 into the lock." << std::endl; + + mtx.allowFirst(); + + mtx.waitLocked(); + assert( mtx.locked() ); + assert( !mtx.hasWaiters() ); + assert( mtx.getWaiters().size() == 0 ); + + mtx.waitUnlocked(); + assert( !mtx.locked() ); + + auto waiters= mtx.getWaiters(); + assert( !mtx.hasWaiters() ); + assert( mtx.getWaiters().size() == 0 ); + assert( waiters.empty() ); + assert( waiters.size() == 0 ); + + thread1.join(); + + assert( !mtx.locked() ); + assert( mtx.getWaiters().size() == 0 ); + }; +} + +int +main( const int argcnt, const char *const *const argvec ) +{ + return runAllTests( { argvec + 1, argvec + argcnt } ); +} diff --git a/Mockination/MockMutex.test/test1.cc b/Mockination/MockMutex.test/test1.cc new file mode 100644 index 0000000..6ca05ac --- /dev/null +++ b/Mockination/MockMutex.test/test1.cc @@ -0,0 +1,100 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace Alepha::Testing; + +namespace +{ + static std::shared_timed_mutex access; + static std::unordered_map< Alepha::Truss::thread::id, int > idMap; + static std::atomic< int > next{ 0 }; + + static int + getSimpleThreadId() + { + { + std::shared_lock< std::shared_timed_mutex > lock( access ); + auto found= idMap.find( Alepha::Truss::this_thread::get_id() ); + if( found != end( idMap ) ) return found->second; + } + + std::unique_lock< std::shared_timed_mutex > lock( access ); + return idMap[ Alepha::Truss::this_thread::get_id() ]= next++; + } + + + static std::ostream & + print() + { + const auto id= getSimpleThreadId(); + return std::cerr << "Thread " << id << ": "; + } + + static auto test1= "smoke"_test <=[] + { + Alepha::Mockination::MockMutexImpl mtx; + + print() << "Creating thread." << std::endl; + + auto lockingCode= [&mtx] + { + print() << "Going to lock" << std::endl; + mtx.lock(); + print() << "Locked" << std::endl; + mtx.unlock(); + print() << "Unlocked" << std::endl; + }; + + // Test allow in first order + auto thread1= Alepha::Truss::thread{ lockingCode }; + auto thread2= Alepha::Truss::thread{ lockingCode }; + + print() << "Control will now wait for thread 1 to bump into the lock." << std::endl; + mtx.waitForWaiter( thread1.get_id() ); + print() << "Control will now wait for thread 2 to bump into the lock." << std::endl; + mtx.waitForWaiter( thread2.get_id() ); + + print() << "Control will now permit thread 1 into the lock." << std::endl; + mtx.allowWait( thread1.get_id() ); + + print() << "Control will now permit thread 2 into the lock." << std::endl; + mtx.allowWait( thread2.get_id() ); + + thread1.join(); + thread2.join(); + + // Test reverse order + print() << "Control will now wait for thread 1 to bump into the lock." << std::endl; + thread1= Alepha::Truss::thread{ lockingCode }; + print() << "Control will now wait for thread 2 to bump into the lock." << std::endl; + thread2= Alepha::Truss::thread{ lockingCode }; + + mtx.waitForWaiter( thread1.get_id() ); + mtx.waitForWaiter( thread2.get_id() ); + + print() << "Control will now permit thread 2 into the lock." << std::endl; + mtx.allowWait( thread2.get_id() ); + + print() << "Control will now permit thread 1 into the lock." << std::endl; + mtx.allowWait( thread1.get_id() ); + + thread1.join(); + thread2.join(); + }; +} + +int +main( const int argcnt, const char *const *const argvec ) +{ + return runAllTests( { argvec + 1, argvec + argcnt } ); +} diff --git a/Truss/README b/Truss/README new file mode 100644 index 0000000..1e23c49 --- /dev/null +++ b/Truss/README @@ -0,0 +1,6 @@ +The Truss sub-library of Alepha unifies dependencies upon boost and STL components into a +single tunable namespace. Alepha::Truss::function, for example is one of `boost::function` or +`std::function`, based upon tuning parameters. This allows a single `XXX::function` to be +presented in the ABI and API for Alepha. + +Alepha also provides a few of its own versions where conversions can make sense. diff --git a/Truss/basetypes.h b/Truss/basetypes.h new file mode 100644 index 0000000..d736be4 --- /dev/null +++ b/Truss/basetypes.h @@ -0,0 +1,44 @@ +#include +register "Alepha/Truss/basetypes.h"; + + +namespace Alepha +{ + namespace Truss + { + namespace types + { + using nullptr_t= decltype( nullptr ); + using size_t= decltype( sizeof( 0 ) ); + } + + namespace detail + { + template< unsigned char v > struct count_one_bits_unsigned_char; + + template<> + struct count_one_bits_unsigned_char< 0 > + { + static const types::size_t value= 0; + }; + + template< unsigned char v > + struct count_one_bits_unsigned_char + { + static const types::size_t value= ( ( v & 0x1 ) ? 1 : 0 ) + + count_one_bits_unsigned_char< ( v >> 1 ) >::value; + }; + + const unsigned char zero= 0; + const unsigned char max= zero - 1; + + const types::size_t platform_char_bits= count_one_bits_unsigned_char< max >::value; + + class uint24_t + { + private: + std::uint32_t value:24; + }; + } + } +} diff --git a/Truss/condition_variable.h b/Truss/condition_variable.h new file mode 100644 index 0000000..0a4c744 --- /dev/null +++ b/Truss/condition_variable.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include + +#include + +namespace Alepha::Hydrogen::Truss +{ + ALEPHA_BOOST_THREAD namespace BoostThread + { + using boost::condition_variable_any; + + using condition_variable= condition_variable_any; + + using condition= condition_variable; + } + + ALEPHA_STD_THREAD namespace StdThread + { + using std::condition_variable_any; + + using condition_variable= condition_variable_any; + + using condition= condition_variable; + } +} diff --git a/Truss/function.h b/Truss/function.h new file mode 100644 index 0000000..78e9462 --- /dev/null +++ b/Truss/function.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include + + +namespace Alepha::Hydrogen::Truss +{ + using ::std::function; +} + diff --git a/Truss/m2.h b/Truss/m2.h new file mode 100644 index 0000000..a75a05f --- /dev/null +++ b/Truss/m2.h @@ -0,0 +1,236 @@ +#include +register "Alepha/Truss/memory.h"; + +#include + +#include + +namespace Alepha +{ + namespace memory_detail + { + class disable_default_init + { + protected: + disable_default_init()= default; + }; + } + + template< typename T > + class single_ptr; + + template< typename T > + class nullable_single_ptr + : private memory_detail::disable_default_init + { + private: + T *p; + + public: + template< typename U > + inline + nullable_single_ptr( U *const i_p ) + : p ( i_p ) {} + + template< typename U > + nullable_single_ptr( const single_ptr< U > i_p ); + + inline T & + operator *() const { ALEPHA_ASSERT( this->p ); return *this->p; } + + inline T * + operator->() const { ALEPHA_ASSERT( this->p ); return this->p; } + + inline T * + get_raw() const { return this->p; } + }; + + template< typename T > + class single_ptr + : private memory_detail::disable_default_init + { + private: + T *p; + + template< typename U > + friend class nullable_single_ptr; + + public: + template< typename U > + inline + single_ptr( U *const i_p ) + : p ( i_p ) + { + if( this->p == nullptr ) throw std::runtime_error( "Nullptr" ); + } + + template< typename U > + inline + single_ptr( const nullable_single_ptr< U > i_p ) + : p ( i_p ) {} + + inline T & + operator *() const { ALEPHA_ASSERT( this->p ); return *this->p; } + + inline T * + operator->() const { ALEPHA_ASSERT( this->p ); return this->p; } + + inline T * + get_raw() const { return p; } + }; + + template< typename T > + template< typename U > + inline + nullable_single_ptr< T >::nullable_single_ptr( const single_ptr< U > i_p ) + : p( i_p.p ) {} + + class bad_reference_ptr + : public std::runtime_error + { + public: + explicit inline + bad_reference_ptr( const std::string &message ) + : std::runtime_error( message ) {} + }; +} + +namespace Alepha::Truss +{ + namespace memory_detail_debug + { + template< typename T > + class ref_ptr; + + template< typename T > + class unique_ptr + : private memory_detail::disable_default_init + { + private: + std::shared_ptr< T > p; + + // Used only for the make-unique wrapper to adapt to make-shared + explicit inline + unique_ptr( std::shared_ptr< T > &&i_p ) + : p( std::move( i_p ) ) {} + + // Our unique_ptr cannot be default constructed, unlike the one in std. + explicit inline + unique_ptr()= delete; + + // Our unique_ptr doesn't copy, so we disable it. + explicit inline + unique_ptr( const unique_ptr< T > & )= delete; + inline unique_ptr &operator= ( const unique_ptr< T > & )= delete; + + friend class ref_ptr< T >; + + public: + // Our unique_ptr does move, so we enable it. + inline + unique_ptr( unique_ptr< T > && )= default; + inline unique_ptr &operator= ( unique_ptr< T > && )= default; + + inline T & + operator *() const { ALEPHA_ASSERT( this->p ); return *this->p; } + + inline T * + operator->() const { ALEPHA_ASSERT( this->p ); return this->p.get(); } + + inline single_ptr< T > + get_raw() const + { + return this->p.get(); + } + + ref_ptr< T > get() const; + + template< typename U, typename ... Args > + friend unique_ptr< U > make_unique( Args && ... args ); + + inline friend void + swap( unique_ptr &a, unique_ptr &b ) + { + using std::swap; + swap( a.p, b.p ); + } + }; + + template< typename U, typename ... Args > + inline unique_ptr< U > + make_unique( Args && ... args ) + { + unique_ptr< U > rv( std::make_shared< U >( std::forward< Args >( args ) ... ) ); + return rv; + } + + template< typename T > + class distilled_reference + : private memory_detail::disable_default_init + { + private: + std::shared_ptr< T > p; + }; + + + template< typename T > + class ref_ptr + : private memory_detail::disable_default_init + { + public: + class exception + : public bad_reference_ptr + { + public: + explicit inline + exception( const std::string &message ) + : bad_reference_ptr( message ) {} + }; + + private: + std::weak_ptr< T > p; + + // Our unique_ptr cannot be default constructed, unlike the one in std. + explicit inline + ref_ptr()= delete; + + T * + distill() const + try + {} + + catch( const std::bad_weak_ptr & ) + { + throw bad_reference_ptr( "Access to an expired pointer owned by someone else." ); + } + + public: + inline + ref_ptr( const unique_ptr< T > &i_p ) + : p( i_p.p ) {} + + inline T & + operator *() const { return *std::shared_ptr< T >{ this->p }; } + + inline T * + operator->() const { return std::shared_ptr< T >{ this->p }.get(); } + + inline single_ptr< T > + get() const + { + return std::shared_ptr< T >{ this->p }.get(); + } + }; + + template< typename T > + inline ref_ptr< T > + unique_ptr< T >::get() const + { + return ref_ptr< T >( *this ); + } + } + + using memory_detail_debug::unique_ptr; + using memory_detail_debug::ref_ptr; + using memory_detail_debug::make_unique; +} diff --git a/Truss/memory.h b/Truss/memory.h new file mode 100644 index 0000000..b61598f --- /dev/null +++ b/Truss/memory.h @@ -0,0 +1,205 @@ +#include +register "Alepha/Truss/memory.h"; + +#include + +#include +#include + +namespace Alepha +{ + namespace Hydrogen + { + namespace memory_detail + { + class disable_default_init { protected: disable_default_init()= default; }; + } + + template< typename T > class single_ptr; + + template< typename T > + class nullable_single_ptr : private memory_detail::disable_default_init + { + private: + T *p; + + public: + template< typename U > + inline nullable_single_ptr( U *const i_p ) : p ( i_p ) {} + + template< typename U > nullable_single_ptr( const single_ptr< U > i_p ); + + inline T &operator *() const { ALEPHA_ASSERT( this->p ); return *this->p; } + inline T *operator->() const { ALEPHA_ASSERT( this->p ); return this->p; } + + inline T *get_raw() const { return this->p; } + }; + + template< typename T > + class single_ptr : private memory_detail::disable_default_init + { + private: + T *p; + + template< typename U > friend class nullable_single_ptr; + + public: + template< typename U > + inline single_ptr( U *const i_p ) + : p ( i_p ) + { + if( this->p == nullptr ) throw std::runtime_error( "Nullptr" ); + } + + template< typename U > + inline single_ptr( const nullable_single_ptr< U > i_p ) : p ( i_p ) {} + + inline T &operator *() const { ALEPHA_ASSERT( this->p ); return *this->p; } + inline T *operator->() const { ALEPHA_ASSERT( this->p ); return this->p; } + + inline T *get_raw() const { return p; } + }; + + template< typename T > + template< typename U > + inline + nullable_single_ptr< T >::nullable_single_ptr( const single_ptr< U > i_p ) + : p( i_p.p ) {} + + class bad_reference_ptr + : public std::runtime_error + { + public: + explicit inline + bad_reference_ptr( const std::string &message ) + : std::runtime_error( message ) {} + }; + + namespace Truss + { + namespace memory_detail_debug + { + template< typename T > class ref_ptr; + + template< typename T > + class unique_ptr : private memory_detail::disable_default_init + { + private: + std::shared_ptr< T > p; + + // Used only for the make-unique wrapper to adapt to make-shared + explicit inline + unique_ptr( std::shared_ptr< T > &&i_p ) + : p( std::move( i_p ) ) {} + + // Our unique_ptr cannot be default constructed, unlike the one in std. + explicit inline unique_ptr()= delete; + + // Our unique_ptr doesn't copy, so we disable it. + explicit inline unique_ptr( const unique_ptr< T > & )= delete; + inline unique_ptr &operator= ( const unique_ptr< T > & )= delete; + + friend class ref_ptr< T >; + + public: + // Our unique_ptr does move, so we enable it. + inline unique_ptr( unique_ptr< T > && )= default; + inline unique_ptr &operator= ( unique_ptr< T > && )= default; + + inline T &operator *() const { ALEPHA_ASSERT( this->p ); return *this->p; } + + inline T *operator->() const { ALEPHA_ASSERT( this->p ); return this->p.get(); } + + inline single_ptr< T > + get_raw() const + { + return this->p.get(); + } + + ref_ptr< T > get() const; + + template< typename U, typename ... Args > + friend unique_ptr< U > make_unique( Args && ... args ); + + inline friend void + swap( unique_ptr &a, unique_ptr &b ) + { + using std::swap; + swap( a.p, b.p ); + } + }; + + template< typename U, typename ... Args > + inline unique_ptr< U > + make_unique( Args && ... args ) + { + unique_ptr< U > rv( std::make_shared< U >( std::forward< Args >( args )... ) ); + return rv; + } + + template< typename T > + class distilled_reference : private memory_detail::disable_default_init + { + private: + std::shared_ptr< T > p; + }; + + + template< typename T > + class ref_ptr : private memory_detail::disable_default_init + { + public: + class exception : public bad_reference_ptr + { + public: + explicit inline + exception( const std::string &message ) + : bad_reference_ptr( message ) {} + }; + + private: + std::weak_ptr< T > p; + + // Our unique_ptr cannot be default constructed, unlike the one in std. + explicit inline ref_ptr()= delete; + + T * + distill() const + try + { + return std::shared_ptr< T >{ this->p }.get(); + } + catch( const std::bad_weak_ptr & ) + { + toss< DEBUG_MEMORY_THROW >( bad_reference_ptr( + "Access to an expired pointer owned by someone else." ) ); + } + + public: + inline ref_ptr( const unique_ptr< T > &i_p ) : p( i_p.p ) {} + + inline T & operator *() const { return *this->distill(); } + + inline T * operator->() const { return this->distill(); } + + inline single_ptr< T > + get() const + { + return std::shared_ptr< T >{ this->p }.get(); + } + }; + + template< typename T > + inline ref_ptr< T > + unique_ptr< T >::get() const + { + return ref_ptr< T >( *this ); + } + } + + using memory_detail_debug::unique_ptr; + using memory_detail_debug::ref_ptr; + using memory_detail_debug::make_unique; + } + } +} diff --git a/Truss/memory.test/Makefile b/Truss/memory.test/Makefile new file mode 100644 index 0000000..d1264c1 --- /dev/null +++ b/Truss/memory.test/Makefile @@ -0,0 +1,20 @@ +CPPFLAGS+= -I ../../../ +CXXFLAGS+= -std=c++1z +CXXFLAGS+= -g -O0 +#CXXFLAGS+= -O3 +CXX=clang++ +#LDLIBS+= -lboost_thread -lboost_system +CC=clang++ + +TESTS=test0 test1 + +all: $(TESTS) + + +HEADERS= ../memory.h ../../Mockination/MockFunction.h Makefile + +test0.o: $(HEADERS) +test1.o: $(HEADERS) + +clean: + rm -f *.o $(TESTS) diff --git a/Truss/memory.test/examples.cc b/Truss/memory.test/examples.cc new file mode 100644 index 0000000..d9ba85f --- /dev/null +++ b/Truss/memory.test/examples.cc @@ -0,0 +1,51 @@ +#include +using Alepha::Atomic::Turnstile; + +int +main() +{ +} + +namespace +{ +namespace example1 +{ +//! [TurnstileExamples example1] +// Assume that the below code will run multithreaded +Turnstile myArena{ 8 }; + +void +mainLoop() +{ + Turnstile::ScopedUsage active( myArena ); + printf( "I am running in the arena, not everyone can." ); + sleep( 100 ); +} +//! [TurnstileExamples example1] +} // namespace example1 + + +namespace example2 +{ +//! [TurnstileExamples example2] +Turnstile myArena{ 8 }; + +class Worker +{ + public: + Worker() + { + myArena.enter(); + } + + // Assume some useful functionality + + // The worker will destroy his access upon leaving + ~Worker() + { + myArena.egress(); + } +}; +//! [TurnstileExamples example2] +} // namespace example2 +} // namespace diff --git a/Truss/memory.test/test0.cc b/Truss/memory.test/test0.cc new file mode 100644 index 0000000..48cca8d --- /dev/null +++ b/Truss/memory.test/test0.cc @@ -0,0 +1,63 @@ +#include + +#include + +#include + + + +namespace +{ + inline namespace Test0 + { + static void runTests(); + } +} + +int +main() +{ + runTests(); +} + +namespace +{ + static void + simple_unique_ptr_test() + { + auto p= Alepha::Truss::make_unique< std::string >( "Hello" ); + Alepha::Truss::unique_ptr< std::string > p2= std::move( p ); + + p= Alepha::Truss::make_unique< std::string >( "Hello" ); + + using std::swap; + swap( p, p2 ); + } + + static void + unique_ptr_usage_test() + { + auto p= Alepha::Truss::make_unique< std::string >( "Hello" ); + + std::string &s= *p; + std::size_t len= p->size(); + } + + static void + unique_ptr_capture_test() + { + auto p= Alepha::Truss::make_unique< std::string >( "Hello" ); + + Alepha::single_ptr< std::string > s= p.get_raw(); + + Alepha::Truss::ref_ptr< std::string > sp= p.get(); + } + + static void + Test0::runTests() + { + simple_unique_ptr_test(); + unique_ptr_usage_test(); + unique_ptr_capture_test(); + } +} diff --git a/Truss/memory.test/test1.cc b/Truss/memory.test/test1.cc new file mode 100644 index 0000000..5eb4070 --- /dev/null +++ b/Truss/memory.test/test1.cc @@ -0,0 +1,39 @@ +#include + +#include + +#include + + + +namespace +{ + inline namespace Test1 + { + static void runTests(); + } +} + +int +main() +{ + runTests(); +} + +namespace +{ + static void + unique_ptr_to_ref_ptr() + { + auto p= Alepha::Truss::make_unique< std::string >( "Hello" ); + Alepha::Truss::ref_ptr< std::string > r= p.get(); + + Alepha::Truss::ref_ptr< std::string > r2= r; + } + + static void + Test1::runTests() + { + unique_ptr_to_ref_ptr(); + } +} diff --git a/Truss/memory.test/test1a.cc b/Truss/memory.test/test1a.cc new file mode 100644 index 0000000..5eb4070 --- /dev/null +++ b/Truss/memory.test/test1a.cc @@ -0,0 +1,39 @@ +#include + +#include + +#include + + + +namespace +{ + inline namespace Test1 + { + static void runTests(); + } +} + +int +main() +{ + runTests(); +} + +namespace +{ + static void + unique_ptr_to_ref_ptr() + { + auto p= Alepha::Truss::make_unique< std::string >( "Hello" ); + Alepha::Truss::ref_ptr< std::string > r= p.get(); + + Alepha::Truss::ref_ptr< std::string > r2= r; + } + + static void + Test1::runTests() + { + unique_ptr_to_ref_ptr(); + } +} diff --git a/Truss/mutex.h b/Truss/mutex.h new file mode 100644 index 0000000..302cb51 --- /dev/null +++ b/Truss/mutex.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace Alepha::Hydrogen::Truss +{ + ALEPHA_BOOST_THREAD namespace BoostThread + { + using boost::mutex; + + using boost::timed_mutex; + using boost::recursive_mutex; + using boost::recursive_timed_mutex; + + using std::lock_guard; + using boost::unique_lock; + + using boost::defer_lock_t; + using boost::try_to_lock_t; + using boost::adopt_lock_t; + + using boost::defer_lock; + using boost::try_to_lock; + using boost::adopt_lock; + + using std::once_flag; + using std::call_once; + + using std::try_lock; + using std::lock; + } + + ALEPHA_STD_THREAD namespace StdThread + { + using std::mutex; + + using std::timed_mutex; + using std::recursive_mutex; + using std::recursive_timed_mutex; + + using std::lock_guard; + using std::unique_lock; + + using std::defer_lock_t; + using std::try_to_lock_t; + using std::adopt_lock_t; + + using std::defer_lock; + using std::try_to_lock; + using std::adopt_lock; + + using std::once_flag; + using std::call_once; + + using std::try_lock; + using std::lock; + } +} diff --git a/Truss/test.cc b/Truss/test.cc new file mode 100644 index 0000000..fe74773 --- /dev/null +++ b/Truss/test.cc @@ -0,0 +1,10 @@ +#include "basetypes.h" + +#include + + +int +main() +{ + std::cout << "Charbits: " << Alepha::std::detail::platform_char_bits << std::endl; +} diff --git a/Truss/thread.h b/Truss/thread.h new file mode 100644 index 0000000..86028bd --- /dev/null +++ b/Truss/thread.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include + +#include + +#include + +#include + +namespace Alepha::Hydrogen::Truss +{ + ALEPHA_BOOST_THREAD namespace BoostThread + { + // If you decide to use Alepha threading primitives, you'll get the boost ones. + // Eventually we'd like to add interrupt-with-reason semantics. That will be in + // Alepha::Thread. + + // I'd like to map Alepha::Truss's thread to be only std:: thread eventually. + // There will remain "boost::thread" semantics for Alepha, at present. + using boost::thread; + + namespace this_thread= boost::this_thread; + + namespace detail_thread + { + template< typename Ex > + auto + make_thrower( Ex &&exception ) + { + return [exception]{ throw exception; }; + } + } + } + + ALEPHA_STD_THREAD namespace StdThread + { + // If you decide to use Alepha threading primitives, you'll get the boost ones. + // Eventually we'd like to add interrupt-with-reason semantics. That will be in + // Alepha::Thread. + + // I'd like to map Alepha::Truss's thread to be only std:: thread eventually. + // There will remain "boost::thread" semantics for Alepha, at present. + using ::std::thread; + + namespace this_thread= ::std::this_thread; + + namespace detail_thread + { + template< typename Ex > + auto + make_thrower( Ex &&exception ) + { + return [exception]{ throw exception; }; + } + } + } + + namespace under_construction + { + // Built on top of std::thread + class thread + { + private: + Alepha::Truss::function< void () > interruption; + + ::std::thread thread; + }; + } +} diff --git a/Truss/thread_common.h b/Truss/thread_common.h new file mode 100644 index 0000000..89cf495 --- /dev/null +++ b/Truss/thread_common.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#ifdef ALEPHA_USE_BOOST_THREAD_IN_TRUSS +#define ALEPHA_BOOST_THREAD inline +#define ALEPHA_STD_THREAD +#else +#define ALEPHA_BOOST_THREAD +#define ALEPHA_STD_THREAD inline +#endif