Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
thread.hpp
Go to the documentation of this file.
1/*
2 THREAD.hpp - thin convenience wrapper for starting threads
3
4 Copyright (C)
5 2008, 2010, Christian Thaeter <ct@pipapo.org>
6 2008, 2010, Hermann Vosseler <Ichthyostega@web.de>
7 2023, Hermann Vosseler <Ichthyostega@web.de>
8
9  **Lumiera** is free software; you can redistribute it and/or modify it
10  under the terms of the GNU General Public License as published by the
11  Free Software Foundation; either version 2 of the License, or (at your
12  option) any later version. See the file COPYING for further details.
13
14*/
15
16
115#ifndef LIB_THREAD_H
116#define LIB_THREAD_H
117
118
119#include "lib/error.hpp"
120#include "lib/nocopy.hpp"
121#include "include/logging.h"
122#include "lib/meta/trait.hpp"
123#include "lib/meta/function.hpp"
124#include "lib/format-util.hpp"
125#include "lib/result.hpp"
126
127#include <utility>
128#include <thread>
129#include <string>
130#include <tuple>
131
132
133namespace util {
134 std::string sanitise (std::string const&);
135}
136namespace lib {
137
138 using std::string;
139 using std::tuple;
140
141
142
143 namespace thread {// Thread-wrapper base implementation...
144
147 using lib::meta::_Fun;
148 using util::isnil;
149 using std::function;
150 using std::forward;
151 using std::move;
152 using std::decay_t;
153 using std::invoke_result_t;
154 using std::is_constructible;
155 using std::make_from_tuple;
156 using std::tuple_cat;
157 using std::is_same;
158 using std::__or_;
159
167 {
168 const string threadID_;
169 std::thread threadImpl_;
170
171 bool isLive() const { return threadImpl_.joinable(); }
172
173
176 : threadID_{util::BOTTOM_INDICATOR}
177 , threadImpl_{}
178 { }
179
180 ThreadWrapper (string const& threadID)
181 : threadID_{isnil(threadID)? "sub-thread" : util::sanitise (threadID)}
182 , threadImpl_{} //Note: deliberately not starting the thread yet...
183 { }
184
199 template<class...INVO>
200 void
201 launchThread (tuple<INVO...>&& invocation)
202 {
203 ASSERT (not isLive(), "Thread already running");
204 threadImpl_ = make_from_tuple<std::thread> (invocation);
205 };
206
207
209 bool invokedWithinThread() const;
210
211 void markThreadStart();
212 void markThreadEnd ();
213 void setThreadName ();
214 void waitGracePeriod() noexcept;
215
216 /* empty implementation for some policy methods */
220
221 static string decorate_with_global_count (string const&);
222
232 {
233 if (isLive())
234 threadImpl_.detach();
235 }
236 };
237
238
239
247 template<class BAS, typename=void>
249 : BAS
250 {
251 using BAS::BAS;
252
253 template<class FUN, typename...ARGS>
254 void
255 perform_thread_function(FUN&& callable, ARGS&& ...args)
256 {
257 try {
258 // execute the actual operation in this new thread
259 std::invoke (forward<FUN> (callable), forward<ARGS> (args)...);
260 }
261 ERROR_LOG_AND_IGNORE (thread, "Thread function")
262 }
263
264 void
266 {
267 BAS::detach_thread_from_wrapper();
268 }
269
270 void
272 {
273 BAS::waitGracePeriod();
274 }
275 };
276
277
284 template<class BAS, class TAR>
286 : PolicyLaunchOnly<BAS>
287 {
289 using BasePol::BasePol;
290
292 using Hook = function<void(Self&)>;
293
297
298 void
300 {
302 hook_beginThread (*this);
303 else
304 BasePol::handle_begin_thread();
305 }
306
307 void
309 {
311 hook_afterThread (*this);
312 // Note: ensure thread is detached at end
313 BAS::detach_thread_from_wrapper();
314 }
315
316 void
318 {
320 hook_looseThread (*this);
321 else
323 }
324 };
325
326
335 template<class BAS, typename RES>
337 : BAS
338 {
339 using BAS::BAS;
340
342 lib::Result<RES> result_{error::Logic{"No result yet, thread still running; need to join() first."}};
343
344
345 template<class FUN, typename...ARGS>
346 void
347 perform_thread_function(FUN&& callable, ARGS&& ...args)
348 {
349 static_assert (__or_<is_same<RES,void>
350 ,is_constructible<RES, invoke_result_t<FUN,ARGS...>>>());
351
352 // perform the given operation (failsafe) within this thread and capture result...
353 result_ = std::move (
354 lib::Result{forward<FUN>(callable)
355 ,forward<ARGS>(args)...});
356 }
357
358 void
360 {
361 /* do nothing -- thread must be joined manually */;
362 }
363
364 void
366 {
367 ALERT (thread, "Thread '%s' was not joined. Abort.", BAS::threadID_.c_str());
368 }
369 };
370
371
372
376 template<template<class,class> class POL, typename RES =void>
378 : protected POL<ThreadWrapper, RES>
379 {
380 using Policy = POL<ThreadWrapper,RES>;
381
382 template<typename...ARGS>
383 void
384 invokeThreadFunction (ARGS&& ...args)
385 {
386 while (not Policy::isLive()) // wait for thread-ID to become visible
387 std::this_thread::yield();// (typically happens when debugging)
388 Policy::handle_begin_thread();
389 Policy::markThreadStart();
390 Policy::perform_thread_function (forward<ARGS> (args)...);
391 Policy::markThreadEnd();
392 Policy::handle_after_thread();
393 }
394
395
396 protected:
398 {
399 if (Policy::isLive())
400 Policy::handle_loose_thread();
401 }
402
405 : Policy{}
406 { }
407
408 public:
413 template<class W, class...INVO>
414 static auto
415 buildInvocation (W& wrapper, tuple<INVO...>&& invocation)
416 { //the thread-main function
417 return tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction<INVO...>
418 , &wrapper} // passing the wrapper as instance-this
419 ,move (invocation)); //...invokeThreadFunction() in turn delegates
420 } // to the user-provided thread-operation
421
435 template<class...INVO>
436 static auto
437 buildLauncher (INVO&& ...args)
438 {
439 tuple<decay_t<INVO>...> argCopy{forward<INVO> (args)...};
440 return [invocation = move(argCopy)]// Note: functor+args bound by-value into the λ
441 (ThreadLifecycle& wrapper)
442 { // special treatment for launchDetached
443 auto boundInvocation = lateBindInstance (wrapper, move (invocation));
444 wrapper.launchThread (buildInvocation (wrapper, move(boundInvocation)));
445 };
446 }
447
448
455 struct Launch
457 {
458 using Act = function<void(ThreadLifecycle&)>;
459
461 string id;
462
463 template<class FUN, typename...ARGS>
464 Launch (FUN&& threadFunction, ARGS&& ...args)
465 : launch{buildLauncher (forward<FUN>(threadFunction), forward<ARGS>(args)...)}
466 { }
467
468 template<class TAR, typename...ARGS>
469 Launch (RES (TAR::*memFun) (ARGS...), ARGS ...args)
470 : Launch{move (memFun)
471 ,lib::meta::InstancePlaceholder<TAR>{}
472 ,forward<ARGS> (args)... }
473 { }
474
475 Launch&&
476 threadID (string const& threadID)
477 {
478 id = threadID;
479 return move(*this);
480 }
481
482 Launch&&
484 {
485 id = Policy::decorate_with_global_count (id);
486 return move(*this);
487 }
488
489 template<typename HOOK>
490 Launch&&
491 atStart (HOOK&& hook)
492 {
493 return addHook (&Policy::hook_beginThread, forward<HOOK> (hook));
494 }
495
496 template<typename HOOK>
497 Launch&&
498 atExit (HOOK&& hook)
499 {
500 return addHook (&Policy::hook_afterThread, forward<HOOK> (hook));
501 }
502
503 template<typename HOOK>
504 Launch&&
505 onOrphan (HOOK&& hook)
506 {
507 return addHook (&Policy::hook_looseThread, forward<HOOK> (hook));
508 }
509
510 private:
519 template<typename HOOK, class FUN>
520 auto
521 adaptedHook (FUN Policy::*, HOOK&& hook)
522 {
523 static_assert(1 == _Fun<FUN>::ARITY);
524 static_assert(1 >= _Fun<HOOK>::ARITY);
525 // argument type expected by the hooks down in the policy class
526 using Arg = _Fun<FUN>::Args::List::Head;
527 // distinguish if user provided functor takes zero or one argument
528 if constexpr (0 == _Fun<HOOK>::ARITY)
529 return [hook = forward<HOOK>(hook)](Arg){ hook(); };
530 else
531 { // instance type expected by the user-provided hook
532 using Target = _Fun<HOOK>::Args::List::Head;
533 return [hook = forward<HOOK>(hook)]
534 (Arg& threadWrapper)
535 { // build a two-step cast path from the low-level wrapper to user type
536 ThreadLifecycle& base = static_cast<ThreadLifecycle&> (threadWrapper);
537 Target& target = static_cast<Target&> (base);
538 hook (target);
539 };
540 }
541 }
542
544 template<typename HOOK, class FUN>
545 Launch&&
546 addHook (FUN Policy::*storedHook, HOOK&& hook)
547 {
548 return addLayer ([storedHook, hook = adaptedHook (storedHook, forward<HOOK> (hook))]
549 (ThreadLifecycle& wrapper)
550 {
551 wrapper.*storedHook = move (hook);
552 });
553 }
554
556 Launch&&
557 addLayer (Act action)
558 {
559 launch = [action=move(action), chain=move(launch)]
560 (ThreadLifecycle& wrapper)
561 {
562 action(wrapper);
563 chain (wrapper);
564 };
565 return move(*this);
566 }
567 };
568
569
576 : Policy{launcher.id}
577 {
578 launcher.launch (*this);
579 }
580
590 template<class FUN, typename...ARGS>
591 ThreadLifecycle (string const& threadID, FUN&& threadFunction, ARGS&& ...args)
593 Launch{forward<FUN> (threadFunction), forward<ARGS> (args)...}
594 .threadID(threadID)}
595 { }
596
601 template<class SUB, typename...ARGS>
602 ThreadLifecycle (RES (SUB::*memFun) (ARGS...), ARGS ...args)
604 Launch{std::move (memFun)
605 ,static_cast<SUB*> (this)
606 ,forward<ARGS> (args)...
607 }
608 .threadID(util::joinDash (typeSymbol<SUB>(), args...))}
609 { }
610
611
612
619 explicit
620 operator bool() const
621 {
622 return Policy::isLive();
623 }
624
626 using Policy::invokedWithinThread;
627 };
628
629 }//(End)base implementation.
630
631
632
633
634
635
636
637 /************************************************************************/
648 class Thread
649 : public thread::ThreadLifecycle<thread::PolicyLaunchOnly>
650 {
651 public:
652 using ThreadLifecycle::ThreadLifecycle;
653 };
654
655
656
657
658 /************************************************************************/
667 template<typename RES =void>
669 : public thread::ThreadLifecycle<thread::PolicyResultJoin, RES>
670 {
672 public:
673 using Impl::Impl;
674
686 {
687 if (not Impl::threadImpl_.joinable())
688 throw lumiera::error::Logic ("joining on an already terminated thread");
689
690 Impl::threadImpl_.join();
691
692 return Impl::result_;
693 }
694 };
695
697 template<typename FUN, typename...ARGS>
698 ThreadJoinable (string const&, FUN&&, ARGS&&...) -> ThreadJoinable<std::invoke_result_t<FUN,ARGS...>>;
699
700
701
702 /************************************************************************/
717 : public thread::ThreadLifecycle<thread::PolicyLifecycleHook>
718 {
719 public:
720 using ThreadLifecycle::ThreadLifecycle;
721 };
722
723
724
740 template<class TAR = ThreadHookable>
741 inline void
742 launchDetached (ThreadHookable::Launch&& launchBuilder)
743 {
745
746 new TAR{move(launchBuilder)
747 .atExit([](TAR& selfAllocation)
748 {
749 delete &selfAllocation;
750 })
751 .onOrphan([](thread::ThreadWrapper& wrapper)
752 {
754 })};
755 // Note: allocation tossed on the heap deliberately
756 } // The thread-function will pick up and manage *this
757
763 template<class TAR = ThreadHookable, typename...INVO>
764 inline void
765 launchDetached (string const& threadID, INVO&& ...args)
766 {
767 using Launch = TAR::Launch;
768 launchDetached<TAR> (Launch{forward<INVO> (args)...}
769 .threadID (threadID));
770 }
771
773 template<class TAR, typename...ARGS>
774 inline void
775 launchDetached (string const& threadID, void (TAR::*memFun) (ARGS...), ARGS ...args)
776 {
777 using Launch = TAR::Launch;
778 launchDetached<TAR> (Launch{std::move (memFun)
780 ,forward<ARGS> (args)...
781 }
782 .threadID (threadID));
783 }
784
786 template<class TAR, typename...ARGS>
787 inline void
788 launchDetached (void (TAR::*memFun) (ARGS...), ARGS ...args)
789 {
791 ,memFun
792 ,forward<ARGS> (args)...
793 );
794 }
795
796
797} // namespace lib
798#endif /*LIB_THREAD_H*/
Representation of the result of some operation, EITHER a value or a failure.
Definition result.hpp:133
Extended variant of the standard case, allowing to install callbacks (hook functions) to be invoked d...
Definition thread.hpp:718
Variant of the standard case, requiring to wait and join() on the termination of this thread.
Definition thread.hpp:670
lib::Result< RES > join()
put the caller into a blocking wait until this thread has terminated
Definition thread.hpp:685
A thin convenience wrapper to simplify thread-handling.
Definition thread.hpp:650
Policy-based configuration of thread lifecycle.
Definition thread.hpp:379
static auto buildInvocation(W &wrapper, tuple< INVO... > &&invocation)
Build the invocation tuple, using invokeThreadFunction to delegate to the user-provided functor and a...
Definition thread.hpp:415
ThreadLifecycle()
derived classes may create a disabled thread
Definition thread.hpp:404
ThreadLifecycle(string const &threadID, FUN &&threadFunction, ARGS &&...args)
Create a new thread to execute the given operation.
Definition thread.hpp:591
POL< ThreadWrapper, RES > Policy
Definition thread.hpp:380
ThreadLifecycle(RES(SUB::*memFun)(ARGS...), ARGS ...args)
Special variant to bind a subclass member function as thread operation.
Definition thread.hpp:602
ThreadLifecycle(Launch launcher)
Primary constructor: Launch the new thread with flexible configuration.
Definition thread.hpp:575
void invokeThreadFunction(ARGS &&...args)
Definition thread.hpp:384
static auto buildLauncher(INVO &&...args)
Build a λ actually to launch the given thread operation later, after the thread-wrapper-object is ful...
Definition thread.hpp:437
Types marked with this mix-in may be moved but not copied.
Definition nocopy.hpp:50
Lumiera error handling (C++ interface).
#define ERROR_LOG_AND_IGNORE(_FLAG_, _OP_DESCR_)
convenience shortcut for a sequence of catch blocks just logging and consuming an error.
Definition error.hpp:267
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
Metaprogramming tools for detecting and transforming function types.
This header is for including and configuring NoBug.
enable_if_c< Cond::value, T >::type enable_if
SFINAE helper to control the visibility of specialisations and overloads.
Definition meta/util.hpp:87
std::string typeSymbol(TY const *obj=nullptr)
simple expressive symbol to designate a type
constexpr auto lateBindInstance(W &instance, TUP &&invocation)
Fix-up the arguments for a member-function invocation, allowing to inject the actual this instance in...
Definition function.hpp:387
Implementation namespace for support and library code.
void launchDetached(ThreadHookable::Launch &&launchBuilder)
Launch an autonomous self-managing thread (and forget about it).
Definition thread.hpp:742
LumieraError< LERR_(LOGIC)> Logic
Definition error.hpp:207
STL namespace.
std::string sanitise(std::string const &)
produce an identifier based on the given string.
Definition util.cpp:57
string joinDash(ARGS const &...args)
shortcut: join directly with dashes
bool isnil(lib::time::Duration const &dur)
Mix-Ins to allow or prohibit various degrees of copying and cloning.
Intermediary value object to represent »either« an operation result or a failure.
Trait template for uniform access to function signature types.
Definition function.hpp:144
Thread Lifecycle Policy:
Definition thread.hpp:250
void perform_thread_function(FUN &&callable, ARGS &&...args)
Definition thread.hpp:255
Thread Lifecycle Policy Extension: invoke user-provided callbacks from within thread lifecycle.
Definition thread.hpp:287
function< void(Self &)> Hook
Definition thread.hpp:292
Thread Lifecycle Policy:
Definition thread.hpp:338
void perform_thread_function(FUN &&callable, ARGS &&...args)
Definition thread.hpp:347
lib::Result< RES > result_
Wrapper to capture a success/failure indicator and possibly a computation result.
Definition thread.hpp:342
Configuration builder to define the operation running within the thread, and possibly configure furth...
Definition thread.hpp:457
Launch && threadID(string const &threadID)
Definition thread.hpp:476
function< void(ThreadLifecycle &)> Act
Definition thread.hpp:458
Launch && addLayer(Act action)
generic helper to add another »onion layer« to this config builder
Definition thread.hpp:557
Launch && addHook(FUN Policy::*storedHook, HOOK &&hook)
add a config layer to store a user-provided functor into the polic baseclass(es)
Definition thread.hpp:546
Launch && atExit(HOOK &&hook)
Definition thread.hpp:498
Launch && atStart(HOOK &&hook)
Definition thread.hpp:491
Launch(RES(TAR::*memFun)(ARGS...), ARGS ...args)
Definition thread.hpp:469
Launch(FUN &&threadFunction, ARGS &&...args)
Definition thread.hpp:464
Launch && onOrphan(HOOK &&hook)
Definition thread.hpp:505
auto adaptedHook(FUN Policy::*, HOOK &&hook)
Helper to adapt a user provided hook to be usable as lifecycle hook.
Definition thread.hpp:521
void waitGracePeriod() noexcept
Definition thread.cpp:97
void handle_loose_thread()
called when destroying wrapper on still running thread
Definition thread.hpp:219
void detach_thread_from_wrapper()
allow to detach explicitly — independent from thread-function's state.
Definition thread.hpp:231
void launchThread(tuple< INVO... > &&invocation)
Definition thread.hpp:201
ThreadWrapper(string const &threadID)
Definition thread.hpp:180
bool invokedWithinThread() const
detect if the currently executing code runs within this thread
Definition thread.cpp:65
void handle_after_thread()
called immediately before end of thread
Definition thread.hpp:218
static string decorate_with_global_count(string const &)
Helper to create a suffix to the thread-ID with running count.
Definition thread.cpp:56
void handle_begin_thread()
called immediately at start of thread
Definition thread.hpp:217
Helpers for type detection, type rewriting and metaprogramming.