diff --git a/src/lib/thread.cpp b/src/lib/thread.cpp index 588c9d402..bd025f0bd 100644 --- a/src/lib/thread.cpp +++ b/src/lib/thread.cpp @@ -31,11 +31,14 @@ #include "lib/thread.hpp" #include "lib/format-string.hpp" +#include "lib/symbol.hpp" #include "lib/util.hpp" #include #include +using util::_Fmt; +using lib::Literal; using std::chrono::steady_clock; using std::chrono_literals::operator ""ms; @@ -45,47 +48,49 @@ namespace lib { namespace thread{ namespace { - - void - setThreadName (std::thread& handle, string name) + string + lifecycleMsg (Literal phase, string threadID) { - // API limitation: max 15 characters + \0 - name = util::sanitise(name).substr(0, 15); - - pthread_t nativeHandle = handle.native_handle(); - pthread_setname_np(nativeHandle, name.c_str()); - } - - void - lifecycle (string format, string threadID) - { - string message = util::_Fmt{format} % threadID; - TRACE (thread, "%s", message.c_str()); + return _Fmt{"Thread '%s' %s"} % threadID % phase; } } - - template - void - ThreadWrapper::markThreadStart (string const& threadID) + /** @note implies get_id() != std::thread::id{} ==> it is running */ + bool + ThreadWrapper::invokedWithinThread() const { - lifecycle ("Thread '%s' start...", threadID); - setThreadName (threadImpl_, threadID); + return threadImpl_.get_id() == std::this_thread::get_id(); + } + + + void + ThreadWrapper::markThreadStart() + { + TRACE (thread, "%s", lifecycleMsg ("start...", threadID_).c_str()); + setThreadName(); } - template void - ThreadWrapper::markThreadEnd (string const& threadID) + ThreadWrapper::markThreadEnd() { - lifecycle ("Thread '%s' finished.", threadID); + TRACE (thread, "%s", lifecycleMsg ("finished.", threadID_).c_str()); } - template void - ThreadWrapper::waitGracePeriod() noexcept + ThreadWrapper::setThreadName() + { + pthread_t nativeHandle = threadImpl_.native_handle(); + + // API limitation: max 15 characters + \0 + pthread_setname_np(nativeHandle, threadID_.substr(0, 15).c_str()); + } + + + void + ThreadWrapper::waitGracePeriod() noexcept { try { auto start = steady_clock::now(); @@ -94,21 +99,13 @@ namespace thread{ ) std::this_thread::yield(); } - ERROR_LOG_AND_IGNORE (thread, "Thread shutdown wait") + ERROR_LOG_AND_IGNORE (thread, lifecycleMsg("shutdown wait", threadID_).c_str()); if (threadImpl_.joinable()) - ALERT (thread, "Thread failed to terminate after grace period. Abort."); + ALERT (thread, "Thread '%s' failed to terminate after grace period. Abort.", threadID_.c_str()); // invocation of std::thread dtor will presumably call std::terminate... } - template void ThreadWrapper::markThreadStart (string const&); - template void ThreadWrapper::markThreadEnd (string const&); - template void ThreadWrapper::waitGracePeriod () noexcept; - - template void ThreadWrapper::markThreadStart (string const&); - template void ThreadWrapper::markThreadEnd (string const&); - template void ThreadWrapper::waitGracePeriod () noexcept; - }}// namespace lib::thread diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp index c402867dd..0c570d2de 100644 --- a/src/lib/thread.hpp +++ b/src/lib/thread.hpp @@ -111,47 +111,108 @@ #include +namespace util { + std::string sanitise (std::string const&); +} namespace lib { using std::string; + namespace thread {// Thread-wrapper base implementation... - template - class ThreadWrapper + struct ThreadWrapper : util::MoveOnly { + const string threadID_; + std::thread threadImpl_; + + bool isLive() const { return threadImpl_.joinable(); } + + + /** @internal derived classes may create an inactive thread */ + ThreadWrapper() + : threadID_{util::BOTTOM_INDICATOR} + , threadImpl_{} + { } + + template + ThreadWrapper (string const& threadID, ARGS&& ...args) + : threadID_{util::sanitise (threadID)} + , threadImpl_{std::forward (args)... } + { } + + /** determine if the currently executing code runs within this thread */ + bool invokedWithinThread() const; + + void markThreadStart(); + void markThreadEnd (); + void setThreadName (); + void waitGracePeriod() noexcept; + }; + + + template + struct PolicyTODO + : BAS + { + using BAS::BAS; + + void + handle_thread_still_running() + { + BAS::waitGracePeriod(); + } + }; + + /** + * Policy-based configuration of thread lifecycle + */ + template class POL> + class ThreadLifecycle + : protected POL + { + using Policy = POL; + template void - main (string threadID, FUN&& threadFunction, ARGS&& ...args) + main (FUN&& threadFunction, ARGS&& ...args) { - markThreadStart (threadID); + Policy::markThreadStart(); try { // execute the actual operation in this new thread std::invoke (std::forward (threadFunction), std::forward (args)...); } ERROR_LOG_AND_IGNORE (thread, "Thread function") // - markThreadEnd (threadID); - if (autoTerm) - threadImpl_.detach(); + Policy::markThreadEnd(); +// if (autoTerm) + Policy::threadImpl_.detach(); } protected: - std::thread threadImpl_; - - /** @internal derived classes may create an inactive thread */ - ThreadWrapper() : threadImpl_{} { } - - ~ThreadWrapper() + ~ThreadLifecycle() { - if (autoTerm and threadImpl_.joinable()) - waitGracePeriod(); + if (Policy::isLive()) + Policy::handle_thread_still_running(); } public: + /** + * Is this thread »active« and thus tied to OS resources? + * @note this implies some statefulness, which may contradict the RAII pattern. + * - especially note the possibly for derived classes to create an _empty_ Thread. + * - moreover note that ThreadJoinable may have terminated, but still awaits `join()`. + */ + explicit + operator bool() const + { + return Policy::isLive(); + } + + /** Create a new thread to execute the given operation. * The new thread starts up synchronously, can't be cancelled and it can't be joined. * @param threadID human readable descriptor to identify the thread for diagnostics @@ -164,40 +225,14 @@ namespace lib { * until the new thread terminates. */ template - ThreadWrapper (string const& threadID, FUN&& threadFunction, ARGS&& ...args) - : threadImpl_{&ThreadWrapper::main, this - , threadID - , std::forward (threadFunction) - , std::forward (args)... } + ThreadLifecycle (string const& threadID, FUN&& threadFunction, ARGS&& ...args) + : Policy{threadID + , &ThreadLifecycle::main, this + , std::forward (threadFunction) + , std::forward (args)... } { } - /** - * Is this thread »active« and thus tied to OS resources? - * @note this implies some statefulness, which may contradict the RAII pattern. - * - especially note the possibly for derived classes to create an _empty_ Thread. - * - moreover note that ThreadJoinable may have terminated, but still awaits `join()`. - */ - explicit - operator bool() const - { - return threadImpl_.joinable(); - } - - - - protected: - /** determine if the currently executing code runs within this thread */ - bool - invokedWithinThread() const - { - return threadImpl_.get_id() == std::this_thread::get_id(); - } // Note: implies get_id() != std::thread::id{} ==> it is running - - private: - void markThreadStart (string const& threadID); - void markThreadEnd (string const& threadID); - void waitGracePeriod() noexcept; }; }//(End)base implementation. @@ -215,11 +250,11 @@ namespace lib { * `std::terminate` afterwards, should the thread still be active then. */ class Thread - : public thread::ThreadWrapper + : public thread::ThreadLifecycle { public: - using ThreadWrapper::ThreadWrapper; + using ThreadLifecycle::ThreadLifecycle; }; @@ -237,10 +272,10 @@ namespace lib { * the application is shut down immediately via `std::terminate`. */ class ThreadJoinable - : public thread::ThreadWrapper + : public thread::ThreadLifecycle { public: - using ThreadWrapper::ThreadWrapper; + using ThreadLifecycle::ThreadLifecycle; /** put the caller into a blocking wait until this thread has terminated */ void