1
0
forked from Alepha/Alepha

Bring in the mockination work and Truss from old.

It's all a mess -- not in the new unified form and namespace.
I need to do a big cleanup pass.
This commit is contained in:
2023-02-09 21:30:38 -08:00
parent 306d2145a3
commit fd6060be17
21 changed files with 1898 additions and 0 deletions

View File

@ -0,0 +1,62 @@
#include <Alepha/Alepha.h>
register "Alepha/Mockination/MockCondition.h";
#include <utility>
#include <Alepha/Mockination/MockMutex.h>
#include <Alepha/ScopedUsage.h>
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() );
}
};
}
}
}

235
Mockination/MockFunction.h Normal file
View File

@ -0,0 +1,235 @@
#include <Alepha/Alepha.h>
register "Alepha/Mockination/mockination";
#include <utility>
#include <iostream>
#include <Alepha/Meta/functor_traits.h>
#include <Alepha/Meta/require_relationship.h>
#include <Alepha/Truss/function.h>
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();
}
};
}
}
}

412
Mockination/MockMutex.h Normal file
View File

@ -0,0 +1,412 @@
#pragma once
#include <Alepha/Alepha.h>
#include <cassert>
#include <iostream>
#include <map>
#include <vector>
#include <utility>
#include <iterator>
#include <algorithm>
#include <Alepha/Truss/mutex.h>
#include <Alepha/Truss/thread.h>
#include <Alepha/Truss/condition_variable.h>
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;
}
}
}

View File

@ -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)

View File

@ -0,0 +1,164 @@
#include <Alepha/Mockination/MockMutex.h>
#include <Alepha/Testing/test.h>
#include <unistd.h>
#include <iostream>
#include <atomic>
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
#include <Alepha/Truss/thread.h>
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 } );
}

View File

@ -0,0 +1,100 @@
#include <Alepha/Mockination/MockMutex.h>
#include <Alepha/Testing/test.h>
#include <unistd.h>
#include <iostream>
#include <atomic>
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
#include <Alepha/Truss/thread.h>
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 } );
}