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) Lumiera.org
5  2008, 2010 Hermann Vosseler <Ichthyostega@web.de>
6  Christian Thaeter <ct@pipapo.org>
7 
8  This program is free software; you can redistribute it and/or
9  modify it under the terms of the GNU General Public License as
10  published by the Free Software Foundation; either version 2 of
11  the License, or (at your option) any later version.
12 
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with this program; if not, write to the Free Software
20  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 
22 */
23 
24 
123 #ifndef LIB_THREAD_H
124 #define LIB_THREAD_H
125 
126 
127 #include "lib/error.hpp"
128 #include "lib/nocopy.hpp"
129 #include "include/logging.h"
130 #include "lib/meta/trait.hpp"
131 #include "lib/meta/function.hpp"
132 #include "lib/format-util.hpp"
133 #include "lib/result.hpp"
134 
135 #include <utility>
136 #include <thread>
137 #include <string>
138 #include <tuple>
139 
140 
141 namespace util {
142  std::string sanitise (std::string const&);
143 }
144 namespace lib {
145 
146  using std::string;
147  using std::tuple;
148 
149 
150 
151  namespace thread {// Thread-wrapper base implementation...
152 
154  using lib::meta::typeSymbol;
155  using lib::meta::_Fun;
156  using util::isnil;
157  using std::function;
158  using std::forward;
159  using std::move;
160  using std::decay_t;
161  using std::invoke_result_t;
162  using std::is_constructible;
163  using std::make_from_tuple;
164  using std::tuple_cat;
165  using std::is_same;
166  using std::__or_;
167 
175  {
176  const string threadID_;
177  std::thread threadImpl_;
178 
179  bool isLive() const { return threadImpl_.joinable(); }
180 
181 
184  : threadID_{util::BOTTOM_INDICATOR}
185  , threadImpl_{}
186  { }
187 
188  ThreadWrapper (string const& threadID)
189  : threadID_{isnil(threadID)? "sub-thread" : util::sanitise (threadID)}
190  , threadImpl_{} //Note: deliberately not starting the thread yet...
191  { }
192 
207  template<class...INVO>
208  void
209  launchThread (tuple<INVO...>&& invocation)
210  {
211  ASSERT (not isLive(), "Thread already running");
212  threadImpl_ = make_from_tuple<std::thread> (invocation);
213  };
214 
215 
217  bool invokedWithinThread() const;
218 
219  void markThreadStart();
220  void markThreadEnd ();
221  void setThreadName ();
222  void waitGracePeriod() noexcept;
223 
224  /* empty implementation for some policy methods */
228 
229  static string decorate_with_global_count (string const&);
230 
240  {
241  if (isLive())
242  threadImpl_.detach();
243  }
244  };
245 
246 
247 
255  template<class BAS, typename=void>
257  : BAS
258  {
259  using BAS::BAS;
260 
261  template<class FUN, typename...ARGS>
262  void
263  perform_thread_function(FUN&& callable, ARGS&& ...args)
264  {
265  try {
266  // execute the actual operation in this new thread
267  std::invoke (forward<FUN> (callable), forward<ARGS> (args)...);
268  }
269  ERROR_LOG_AND_IGNORE (thread, "Thread function")
270  }
271 
272  void
273  handle_after_thread()
274  {
275  BAS::detach_thread_from_wrapper();
276  }
277 
278  void
279  handle_loose_thread()
280  {
281  BAS::waitGracePeriod();
282  }
283  };
284 
285 
292  template<class BAS, class TAR>
294  : PolicyLaunchOnly<BAS>
295  {
297  using BasePol::BasePol;
298 
299  using Self = PolicyLifecycleHook;
300  using Hook = function<void(Self&)>;
301 
302  Hook hook_beginThread{};
303  Hook hook_afterThread{};
304  Hook hook_looseThread{};
305 
306  void
307  handle_begin_thread()
308  {
309  if (hook_beginThread)
310  hook_beginThread (*this);
311  else
312  BasePol::handle_begin_thread();
313  }
314 
315  void
316  handle_after_thread()
317  {
318  if (hook_afterThread)
319  hook_afterThread (*this);
320  // Note: ensure thread is detached at end
321  BAS::detach_thread_from_wrapper();
322  }
323 
324  void
325  handle_loose_thread()
326  {
327  if (hook_looseThread)
328  hook_looseThread (*this);
329  else
330  BasePol::handle_loose_thread();
331  }
332  };
333 
334 
343  template<class BAS, typename RES>
345  : BAS
346  {
347  using BAS::BAS;
348 
350  lib::Result<RES> result_{error::Logic{"No result yet, thread still running; need to join() first."}};
351 
352 
353  template<class FUN, typename...ARGS>
354  void
355  perform_thread_function(FUN&& callable, ARGS&& ...args)
356  {
357  static_assert (__or_<is_same<RES,void>
358  ,is_constructible<RES, invoke_result_t<FUN,ARGS...>>>());
359 
360  // perform the given operation (failsafe) within this thread and capture result...
361  result_ = std::move (
362  lib::Result{forward<FUN>(callable)
363  ,forward<ARGS>(args)...});
364  }
365 
366  void
367  handle_after_thread()
368  {
369  /* do nothing -- thread must be joined manually */;
370  }
371 
372  void
373  handle_loose_thread()
374  {
375  ALERT (thread, "Thread '%s' was not joined. Abort.", BAS::threadID_.c_str());
376  }
377  };
378 
379 
380 
384  template<template<class,class> class POL, typename RES =void>
386  : protected POL<ThreadWrapper, RES>
387  {
388  using Policy = POL<ThreadWrapper,RES>;
389 
390  template<typename...ARGS>
391  void
392  invokeThreadFunction (ARGS&& ...args)
393  {
394  while (not Policy::isLive()) // wait for thread-ID to become visible
395  std::this_thread::yield();// (typically happens when debugging)
396  Policy::handle_begin_thread();
397  Policy::markThreadStart();
398  Policy::perform_thread_function (forward<ARGS> (args)...);
399  Policy::markThreadEnd();
400  Policy::handle_after_thread();
401  }
402 
403 
404  protected:
405  ~ThreadLifecycle()
406  {
407  if (Policy::isLive())
408  Policy::handle_loose_thread();
409  }
410 
413  : Policy{}
414  { }
415 
416  public:
421  template<class W, class...INVO>
422  static auto
423  buildInvocation (W& wrapper, tuple<INVO...>&& invocation)
424  { //the thread-main function
425  return tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction<INVO...>
426  , &wrapper} // passing the wrapper as instance-this
427  ,move (invocation)); //...invokeThreadFunction() in turn delegates
428  } // to the user-provided thread-operation
429 
443  template<class...INVO>
444  static auto
445  buildLauncher (INVO&& ...args)
446  {
447  tuple<decay_t<INVO>...> argCopy{forward<INVO> (args)...};
448  return [invocation = move(argCopy)]// Note: functor+args bound by-value into the λ
449  (ThreadLifecycle& wrapper)
450  { // special treatment for launchDetached
451  auto boundInvocation = lateBindInstance (wrapper, move (invocation));
452  wrapper.launchThread (buildInvocation (wrapper, move(boundInvocation)));
453  };
454  }
455 
456 
463  struct Launch
465  {
466  using Act = function<void(ThreadLifecycle&)>;
467 
468  Act launch;
469  string id;
470 
471  template<class FUN, typename...ARGS>
472  Launch (FUN&& threadFunction, ARGS&& ...args)
473  : launch{buildLauncher (forward<FUN>(threadFunction), forward<ARGS>(args)...)}
474  { }
475 
476  template<class TAR, typename...ARGS>
477  Launch (RES (TAR::*memFun) (ARGS...), ARGS ...args)
478  : Launch{move (memFun)
480  ,forward<ARGS> (args)... }
481  { }
482 
483  Launch&&
484  threadID (string const& threadID)
485  {
486  id = threadID;
487  return move(*this);
488  }
489 
490  Launch&&
491  decorateCounter()
492  {
493  id = Policy::decorate_with_global_count (id);
494  return move(*this);
495  }
496 
497  template<typename HOOK>
498  Launch&&
499  atStart (HOOK&& hook)
500  {
501  return addHook (&Policy::hook_beginThread, forward<HOOK> (hook));
502  }
503 
504  template<typename HOOK>
505  Launch&&
506  atExit (HOOK&& hook)
507  {
508  return addHook (&Policy::hook_afterThread, forward<HOOK> (hook));
509  }
510 
511  template<typename HOOK>
512  Launch&&
513  onOrphan (HOOK&& hook)
514  {
515  return addHook (&Policy::hook_looseThread, forward<HOOK> (hook));
516  }
517 
518  private:
527  template<typename HOOK, class FUN>
528  auto
529  adaptedHook (FUN Policy::*, HOOK&& hook)
530  {
531  static_assert(1 == _Fun<FUN>::ARITY);
532  static_assert(1 >= _Fun<HOOK>::ARITY);
533  // argument type expected by the hooks down in the policy class
534  using Arg = typename _Fun<FUN>::Args::List::Head;
535  // distinguish if user provided functor takes zero or one argument
536  if constexpr (0 == _Fun<HOOK>::ARITY)
537  return [hook = forward<HOOK>(hook)](Arg){ hook(); };
538  else
539  { // instance type expected by the user-provided hook
540  using Target = typename _Fun<HOOK>::Args::List::Head;
541  return [hook = forward<HOOK>(hook)]
542  (Arg& threadWrapper)
543  { // build a two-step cast path from the low-level wrapper to user type
544  ThreadLifecycle& base = static_cast<ThreadLifecycle&> (threadWrapper);
545  Target& target = static_cast<Target&> (base);
546  hook (target);
547  };
548  }
549  }
550 
552  template<typename HOOK, class FUN>
553  Launch&&
554  addHook (FUN Policy::*storedHook, HOOK&& hook)
555  {
556  return addLayer ([storedHook, hook = adaptedHook (storedHook, forward<HOOK> (hook))]
557  (ThreadLifecycle& wrapper)
558  {
559  wrapper.*storedHook = move (hook);
560  });
561  }
562 
564  Launch&&
565  addLayer (Act action)
566  {
567  launch = [action=move(action), chain=move(launch)]
568  (ThreadLifecycle& wrapper)
569  {
570  action(wrapper);
571  chain (wrapper);
572  };
573  return move(*this);
574  }
575  };
576 
577 
583  ThreadLifecycle (Launch launcher)
584  : Policy{launcher.id}
585  {
586  launcher.launch (*this);
587  }
588 
598  template<class FUN, typename...ARGS>
599  ThreadLifecycle (string const& threadID, FUN&& threadFunction, ARGS&& ...args)
600  : ThreadLifecycle{
601  Launch{forward<FUN> (threadFunction), forward<ARGS> (args)...}
602  .threadID(threadID)}
603  { }
604 
609  template<class SUB, typename...ARGS>
610  ThreadLifecycle (RES (SUB::*memFun) (ARGS...), ARGS ...args)
611  : ThreadLifecycle{
612  Launch{std::move (memFun)
613  ,static_cast<SUB*> (this)
614  ,forward<ARGS> (args)...
615  }
616  .threadID(util::joinDash (typeSymbol<SUB>(), args...))}
617  { }
618 
619 
620 
627  explicit
628  operator bool() const
629  {
630  return Policy::isLive();
631  }
632 
634  using Policy::invokedWithinThread;
635  };
636 
637  }//(End)base implementation.
638 
639 
640 
641 
642 
643 
644 
645  /************************************************************************/
656  class Thread
657  : public thread::ThreadLifecycle<thread::PolicyLaunchOnly>
658  {
659  public:
660  using ThreadLifecycle::ThreadLifecycle;
661  };
662 
663 
664 
665 
666  /************************************************************************/
675  template<typename RES =void>
677  : public thread::ThreadLifecycle<thread::PolicyResultJoin, RES>
678  {
680  public:
681  using Impl::Impl;
682 
693  join ()
694  {
695  if (not Impl::threadImpl_.joinable())
696  throw lumiera::error::Logic ("joining on an already terminated thread");
697 
698  Impl::threadImpl_.join();
699 
700  return Impl::result_;
701  }
702  };
703 
705  template<typename FUN, typename...ARGS>
706  ThreadJoinable (string const&, FUN&&, ARGS&&...) -> ThreadJoinable<std::invoke_result_t<FUN,ARGS...>>;
707 
708 
709 
710  /************************************************************************/
725  : public thread::ThreadLifecycle<thread::PolicyLifecycleHook>
726  {
727  public:
728  using ThreadLifecycle::ThreadLifecycle;
729  };
730 
731 
732 
748  template<class TAR = ThreadHookable>
749  inline void
750  launchDetached (ThreadHookable::Launch&& launchBuilder)
751  {
753 
754  new TAR{move(launchBuilder)
755  .atExit([](TAR& selfAllocation)
756  {
757  delete &selfAllocation;
758  })
759  .onOrphan([](thread::ThreadWrapper& wrapper)
760  {
761  wrapper.detach_thread_from_wrapper();
762  })};
763  // Note: allocation tossed on the heap deliberately
764  } // The thread-function will pick up and manage *this
765 
771  template<class TAR = ThreadHookable, typename...INVO>
772  inline void
773  launchDetached (string const& threadID, INVO&& ...args)
774  {
775  using Launch = typename TAR::Launch;
776  launchDetached<TAR> (Launch{forward<INVO> (args)...}
777  .threadID (threadID));
778  }
779 
781  template<class TAR, typename...ARGS>
782  inline void
783  launchDetached (string const& threadID, void (TAR::*memFun) (ARGS...), ARGS ...args)
784  {
785  using Launch = typename TAR::Launch;
786  launchDetached<TAR> (Launch{std::move (memFun)
788  ,forward<ARGS> (args)...
789  }
790  .threadID (threadID));
791  }
792 
794  template<class TAR, typename...ARGS>
795  inline void
796  launchDetached (void (TAR::*memFun) (ARGS...), ARGS ...args)
797  {
798  launchDetached (util::joinDash (lib::meta::typeSymbol<TAR>(), args...)
799  ,memFun
800  ,forward<ARGS> (args)...
801  );
802  }
803 
804 
805 } // namespace lib
806 #endif /*LIB_THREAD_H*/
Launch && addLayer(Act action)
generic helper to add another »onion layer« to this config builder
Definition: thread.hpp:565
Variant of the standard case, requiring to wait and join() on the termination of this thread...
Definition: thread.hpp:676
void launchThread(tuple< INVO... > &&invocation)
Definition: thread.hpp:209
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:327
void handle_after_thread()
called immediately before end of thread
Definition: thread.hpp:226
Representation of the result of some operation, EITHER a value or a failure.
Definition: result.hpp:106
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:554
#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:275
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:423
Launch(RES(TAR::*memFun)(ARGS...), ARGS ...args)
Definition: thread.hpp:477
Types marked with this mix-in may be moved but not copied.
Definition: nocopy.hpp:58
ThreadLifecycle(RES(SUB::*memFun)(ARGS...), ARGS ...args)
Special variant to bind a subclass member function as thread operation.
Definition: thread.hpp:610
Helper for uniform access to function signature types.
Definition: function.hpp:108
std::string sanitise(std::string const &)
produce an identifier based on the given string.
Definition: util.cpp:65
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:445
Implementation namespace for support and library code.
ThreadLifecycle()
derived classes may create a disabled thread
Definition: thread.hpp:412
ThreadLifecycle(string const &threadID, FUN &&threadFunction, ARGS &&...args)
Create a new thread to execute the given operation.
Definition: thread.hpp:599
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:199
Thread Lifecycle Policy:
Definition: thread.hpp:344
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:293
Metaprogramming tools for transforming functor types.
Configuration builder to define the operation running within the thread, and possibly configure furth...
Definition: thread.hpp:463
lib::Result< RES > join()
put the caller into a blocking wait until this thread has terminated
Definition: thread.hpp:693
Thread Lifecycle Policy Extension: invoke user-provided callbacks from within thread lifecycle...
Definition: thread.hpp:293
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:87
auto adaptedHook(FUN Policy::*, HOOK &&hook)
Helper to adapt a user provided hook to be usable as lifecycle hook.
Definition: thread.hpp:529
void handle_begin_thread()
called immediately at start of thread
Definition: thread.hpp:225
Extended variant of the standard case, allowing to install callbacks (hook functions) to be invoked d...
Definition: thread.hpp:724
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:796
A thin convenience wrapper to simplify thread-handling.
Definition: thread.hpp:656
void handle_loose_thread()
called when destroying wrapper on still running thread
Definition: thread.hpp:227
Thread Lifecycle Policy:
Definition: thread.hpp:256
verify compliance to an interface by subtype check
Definition: trait.hpp:318
Policy-based configuration of thread lifecycle.
Definition: thread.hpp:385
ThreadLifecycle(Launch launcher)
Primary constructor: Launch the new thread with flexible configuration.
Definition: thread.hpp:583
void detach_thread_from_wrapper()
allow to detach explicitly — independent from thread-function&#39;s state.
Definition: thread.hpp:239