forked from Alepha/Alepha
Add a thread primitive which has support for notifications.
Now you can send an `Alepha::Notification` into a thread as an interrupt. You have to use the Alepha threading primitives.
This commit is contained in:
168
Thread.h
Normal file
168
Thread.h
Normal file
@ -0,0 +1,168 @@
|
||||
static_assert( __cplusplus > 2020'00 );
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Alepha/Alepha.h>
|
||||
|
||||
#include <Alepha/boost_path/thread.hpp>
|
||||
#include <Alepha/boost_path/thread/mutex.hpp>
|
||||
#include <Alepha/boost_path/thread/condition_variable.hpp>
|
||||
|
||||
#include <Alepha/Exception.h>
|
||||
|
||||
namespace Alepha::Hydrogen
|
||||
{
|
||||
namespace detail::thread
|
||||
{
|
||||
inline namespace exports {}
|
||||
namespace exports
|
||||
{
|
||||
using CrossThreadNotificationRethrowError= synthetic_exception< struct cross_thread_notification_failure, Error >;
|
||||
}
|
||||
|
||||
class NotificationInfo
|
||||
{
|
||||
private:
|
||||
std::mutex access;
|
||||
std::exception_ptr notification;
|
||||
|
||||
public:
|
||||
//template( Concepts::DerivedFrom< Notification > Exc )
|
||||
void
|
||||
setNotification( std::exception_ptr &&exception )
|
||||
{
|
||||
std::lock_guard lock( access );
|
||||
notification= std::move( exception );
|
||||
}
|
||||
|
||||
template< typename Callable >
|
||||
void
|
||||
check_interrupt( Callable &&callable )
|
||||
try
|
||||
{
|
||||
callable();
|
||||
}
|
||||
catch( const boost_ns::thread_interrupted & )
|
||||
{
|
||||
std::lock_guard lock( access );
|
||||
if( not notification ) throw;
|
||||
try
|
||||
{
|
||||
std::rethrow_exception( std::move( notification ) );
|
||||
}
|
||||
catch( const std::bad_alloc & )
|
||||
{
|
||||
throw build_exception< CrossThreadNotificationRethrowError >( "`std::bad_alloc` encountered in trying to "
|
||||
"raise a cross-thread notification" );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline thread_local NotificationInfo notification;
|
||||
|
||||
namespace exports
|
||||
{
|
||||
class ConditionVariable
|
||||
: private boost_ns::condition_variable
|
||||
{
|
||||
public:
|
||||
using condition_variable::notify_all;
|
||||
using condition_variable::notify_one;
|
||||
|
||||
template< typename Lock >
|
||||
void
|
||||
wait( Lock &&lock )
|
||||
{
|
||||
notification.check_interrupt( [&]{ condition_variable::wait( std::forward< Lock >( lock ) ); } );
|
||||
}
|
||||
|
||||
template< typename Lock, typename Predicate >
|
||||
void
|
||||
wait( Lock &&lock, Predicate &&predicate )
|
||||
{
|
||||
notification.check_interrupt( [&]{ condition_variable::wait( std::forward< Lock >( lock ),
|
||||
std::forward< Predicate >( predicate ) ); } );
|
||||
}
|
||||
};
|
||||
|
||||
namespace this_thread
|
||||
{
|
||||
template< typename Clock, typename Duration >
|
||||
void
|
||||
sleep_until( const boost_ns::chrono::time_point< Clock, Duration > &abs_time )
|
||||
{
|
||||
notification.check_interrupt( [&]{ boost_ns::this_thread::sleep_until( abs_time ); } );
|
||||
}
|
||||
|
||||
#if 0
|
||||
template< typename Rep, typename Period >
|
||||
void
|
||||
sleep_for( const boost_ns::chrono::duration< Rep, Period > &rel_time )
|
||||
{
|
||||
notification.check_interrupt( [&]( boost_ns::this_thread::sleep_until( rel_time ); } );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadNotification
|
||||
{
|
||||
NotificationInfo *myNotification= nullptr;
|
||||
};
|
||||
|
||||
namespace exports
|
||||
{
|
||||
class Thread
|
||||
: ThreadNotification, boost_ns::thread
|
||||
{
|
||||
public:
|
||||
template< typename Callable >
|
||||
explicit
|
||||
Thread( Callable &&callable )
|
||||
: thread
|
||||
(
|
||||
[this, callable= std::forward< Callable >( callable )]
|
||||
{
|
||||
myNotification= ¬ification;
|
||||
try { callable(); }
|
||||
catch( const Notification & )
|
||||
{
|
||||
// Notifications are not fatal.
|
||||
}
|
||||
}
|
||||
)
|
||||
{}
|
||||
|
||||
using thread::join;
|
||||
using thread::detach;
|
||||
|
||||
using thread::interrupt;
|
||||
|
||||
//template( Concepts::DerivedFrom< Notification > Exc )
|
||||
template< typename Exc >
|
||||
void
|
||||
interrupt( Exc &&exception )
|
||||
try
|
||||
{
|
||||
throw std::forward< Exc >( exception );
|
||||
}
|
||||
catch( const Notification & )
|
||||
{
|
||||
myNotification->setNotification( std::current_exception() );
|
||||
interrupt();
|
||||
}
|
||||
};
|
||||
|
||||
using Mutex= boost_ns::mutex;
|
||||
using boost_ns::mutex;
|
||||
using boost_ns::unique_lock;
|
||||
using boost_ns::lock_guard;
|
||||
}
|
||||
}
|
||||
|
||||
inline namespace exports {}
|
||||
namespace exports::inline thread
|
||||
{
|
||||
using namespace detail::thread::exports;
|
||||
}
|
||||
}
|
10
Thread.test/Makefile
Normal file
10
Thread.test/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
CXXFLAGS+= -std=c++2a -I ../
|
||||
CXXFLAGS+= -g -O0
|
||||
CXX=clang++-12
|
||||
|
||||
CXXFLAGS+= -Wno-inline-namespace-reopened-noninline
|
||||
CXXFLAGS+= -Wno-unused-comparison
|
||||
|
||||
LDLIBS+= -lboost_thread -lpthread
|
||||
|
||||
all: thread
|
70
Thread.test/thread.cc
Normal file
70
Thread.test/thread.cc
Normal file
@ -0,0 +1,70 @@
|
||||
static_assert( __cplusplus > 2020'00 );
|
||||
|
||||
#include <Alepha/Thread.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <Alepha/Testing/test.h>
|
||||
#include <Alepha/Testing/TableTest.h>
|
||||
#include <Alepha/Utility/evaluation.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using Alepha::exports::types::argcnt_t;
|
||||
using Alepha::exports::types::argvec_t;
|
||||
}
|
||||
|
||||
int
|
||||
main( const argcnt_t argcnt, const argvec_t argvec )
|
||||
{
|
||||
return Alepha::Testing::runAllTests( argcnt, argvec );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace util= Alepha::Utility;
|
||||
using namespace Alepha::Testing::exports;
|
||||
|
||||
using MyNotification= Alepha::create_exception< struct my_notification, Alepha::Notification >;
|
||||
|
||||
auto tests= Alepha::Utility::enroll <=[]
|
||||
{
|
||||
"smoke"_test <=[] () -> bool
|
||||
{
|
||||
std::cerr << "Smoke started..." << std::endl;
|
||||
Alepha::Mutex access;
|
||||
Alepha::ConditionVariable cv;
|
||||
auto threadMain= [&]
|
||||
{
|
||||
try
|
||||
{
|
||||
Alepha::unique_lock lock( access );
|
||||
std::cerr << "Child thread started..." << std::endl;
|
||||
cv.wait( lock );
|
||||
std::cerr << "Child thread awoken illegally!" << std::endl;
|
||||
}
|
||||
catch( const boost::thread_interrupted & )
|
||||
{
|
||||
std::cerr << "SHIT! We didn't get intercepted!" << std::endl;
|
||||
throw;
|
||||
}
|
||||
catch( const MyNotification &n )
|
||||
{
|
||||
std::cerr << "I caught it: " << n.message() << "!" << std::endl;
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
access.lock();
|
||||
std::cerr << "Launching child thread..." << std::endl;
|
||||
Alepha::Thread thr( threadMain );
|
||||
std::cerr << "Child thread now launched..." << std::endl;
|
||||
::sleep( 1 );
|
||||
access.unlock();
|
||||
thr.interrupt( Alepha::build_exception< MyNotification >( "My message" ) );
|
||||
thr.join();
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user