Lumiera  0.pre.03
»edit your freedom«
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 
133 namespace util {
134  std::string sanitise (std::string const&);
135 }
136 namespace lib {
137 
138  using std::string;
139  using std::tuple;
140 
141 
142 
143  namespace thread {// Thread-wrapper base implementation...
144 
146  using lib::meta::typeSymbol;
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
265  handle_after_thread()
266  {
267  BAS::detach_thread_from_wrapper();
268  }
269 
270  void
271  handle_loose_thread()
272  {
273  BAS::waitGracePeriod();
274  }
275  };
276 
277 
284  template<class BAS, class TAR>
286  : PolicyLaunchOnly<BAS>
287  {
289  using BasePol::BasePol;
290 
291  using Self = PolicyLifecycleHook;
292  using Hook = function<void(Self&)>;
293 
294  Hook hook_beginThread{};
295  Hook hook_afterThread{};
296  Hook hook_looseThread{};
297 
298  void
299  handle_begin_thread()
300  {
301  if (hook_beginThread)
302  hook_beginThread (*this);
303  else
304  BasePol::handle_begin_thread();
305  }
306 
307  void
308  handle_after_thread()
309  {
310  if (hook_afterThread)
311  hook_afterThread (*this);
312  // Note: ensure thread is detached at end
313  BAS::detach_thread_from_wrapper();
314  }
315 
316  void
317  handle_loose_thread()
318  {
319  if (hook_looseThread)
320  hook_looseThread (*this);
321  else
322  BasePol::handle_loose_thread();
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
359  handle_after_thread()
360  {
361  /* do nothing -- thread must be joined manually */;
362  }
363 
364  void
365  handle_loose_thread()
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:
397  ~ThreadLifecycle()
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 
460  Act launch;
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)
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&&
483  decorateCounter()
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 = typename _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 = typename _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 
575  ThreadLifecycle (Launch launcher)
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)
592  : ThreadLifecycle{
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)
603  : ThreadLifecycle{
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 
685  join ()
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  {
753  wrapper.detach_thread_from_wrapper();
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 = typename 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 = typename 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  {
790  launchDetached (util::joinDash (lib::meta::typeSymbol<TAR>(), args...)
791  ,memFun
792  ,forward<ARGS> (args)...
793  );
794  }
795 
796 
797 } // namespace lib
798 #endif /*LIB_THREAD_H*/
Launch && addLayer(Act action)
generic helper to add another »onion layer« to this config builder
Definition: thread.hpp:557
Variant of the standard case, requiring to wait and join() on the termination of this thread...
Definition: thread.hpp:668
void launchThread(tuple< INVO... > &&invocation)
Definition: thread.hpp:201
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:318
void handle_after_thread()
called immediately before end of thread
Definition: thread.hpp:218
Representation of the result of some operation, EITHER a value or a failure.
Definition: result.hpp:97
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
#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:266
static auto buildInvocation(W &wrapper, tuple< INVO... > &&invocation)
Build the invocation tuple, using #invokeThreadFunction to delegate to the user-provided functor and ...
Definition: thread.hpp:415
Launch(RES(TAR::*memFun)(ARGS...), ARGS ...args)
Definition: thread.hpp:469
Types marked with this mix-in may be moved but not copied.
Definition: nocopy.hpp:49
ThreadLifecycle(RES(SUB::*memFun)(ARGS...), ARGS ...args)
Special variant to bind a subclass member function as thread operation.
Definition: thread.hpp:602
Helper for uniform access to function signature types.
Definition: function.hpp:99
std::string sanitise(std::string const &)
produce an identifier based on the given string.
Definition: util.cpp:56
Intermediary value object to represent »either« an operation result or a failure. ...
This header is for including and configuring NoBug.
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
Implementation namespace for support and library code.
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
ThreadJoinable(string const &, FUN &&, ARGS &&...) -> ThreadJoinable< std::invoke_result_t< FUN, ARGS... >>
deduction guide: find out about result value to capture from a generic callable.
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:190
Thread Lifecycle Policy:
Definition: thread.hpp:336
Mix-Ins to allow or prohibit various degrees of copying and cloning.
Placeholder marker for a special argument position to be supplied later.
Definition: function.hpp:284
Metaprogramming tools for transforming functor types.
Configuration builder to define the operation running within the thread, and possibly configure furth...
Definition: thread.hpp:455
lib::Result< RES > join()
put the caller into a blocking wait until this thread has terminated
Definition: thread.hpp:685
Thread Lifecycle Policy Extension: invoke user-provided callbacks from within thread lifecycle...
Definition: thread.hpp:285
Helpers for type detection, type rewriting and metaprogramming.
Lumiera error handling (C++ interface).
string typeSymbol()
Short readable type identifier, not necessarily unique or complete.
Definition: genfunc.hpp:78
auto adaptedHook(FUN Policy::*, HOOK &&hook)
Helper to adapt a user provided hook to be usable as lifecycle hook.
Definition: thread.hpp:521
void handle_begin_thread()
called immediately at start of thread
Definition: thread.hpp:217
Extended variant of the standard case, allowing to install callbacks (hook functions) to be invoked d...
Definition: thread.hpp:716
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
void launchDetached(void(TAR::*memFun)(ARGS...), ARGS ...args)
Special variant without explicitly given thread-ID.
Definition: thread.hpp:788
A thin convenience wrapper to simplify thread-handling.
Definition: thread.hpp:648
void handle_loose_thread()
called when destroying wrapper on still running thread
Definition: thread.hpp:219
Thread Lifecycle Policy:
Definition: thread.hpp:248
verify compliance to an interface by subtype check
Definition: trait.hpp:323
Policy-based configuration of thread lifecycle.
Definition: thread.hpp:377
ThreadLifecycle(Launch launcher)
Primary constructor: Launch the new thread with flexible configuration.
Definition: thread.hpp:575
void detach_thread_from_wrapper()
allow to detach explicitly — independent from thread-function&#39;s state.
Definition: thread.hpp:231