SObjectizer-5 Extra
pub.hpp
Go to the documentation of this file.
1 /*!
2  * \file
3  * \brief Implementation of Asio's Thread Pool dispatcher.
4  *
5  * \since
6  * v.1.0.2
7  */
8 
9 #pragma once
10 
11 #include <so_5_extra/error_ranges.hpp>
12 
13 #include <so_5/rt/h/disp_binder.hpp>
14 #include <so_5/rt/h/send_functions.hpp>
15 
16 #include <so_5/disp/reuse/h/work_thread_activity_tracking.hpp>
17 #include <so_5/disp/reuse/h/disp_binder_helpers.hpp>
18 #include <so_5/disp/reuse/h/data_source_prefix_helpers.hpp>
19 
20 #include <so_5/rt/stats/h/repository.hpp>
21 #include <so_5/rt/stats/h/messages.hpp>
22 #include <so_5/rt/stats/h/std_names.hpp>
23 #include <so_5/rt/stats/impl/h/activity_tracking.hpp>
24 
25 #include <so_5/details/h/invoke_noexcept_code.hpp>
26 #include <so_5/details/h/rollback_on_exception.hpp>
27 #include <so_5/details/h/abort_on_fatal_error.hpp>
28 
29 #include <so_5/h/outliving.hpp>
30 
31 #include <asio/io_context.hpp>
32 #include <asio/io_context_strand.hpp>
33 #include <asio/post.hpp>
34 
35 namespace so_5 {
36 
37 namespace extra {
38 
39 namespace disp {
40 
41 namespace asio_thread_pool {
42 
43 namespace errors {
44 
45 //! Asio IoService is not set for asio_thread_pool dispatcher.
48 
49 } /* namespace errors */
50 
51 //
52 // disp_params_t
53 //
54 /*!
55  * \brief Parameters for %asio_thread_pool dispatcher.
56  *
57  * \since
58  * v.1.0.2
59  */
62  {
65 
66  public :
67  //! Default constructor.
68  disp_params_t() = default;
69  //! Copy constructor.
74  {}
75  //! Move constructor.
80  {}
81 
82  friend inline void
84  disp_params_t & a, disp_params_t & b ) noexcept
85  {
86  swap(
87  static_cast< activity_tracking_mixin_t & >(a),
88  static_cast< activity_tracking_mixin_t & >(b) );
89 
90  std::swap( a.m_thread_count, b.m_thread_count );
91  std::swap( a.m_io_context, b.m_io_context );
92  }
93 
94  //! Copy operator.
96  operator=( const disp_params_t & o )
97  {
98  disp_params_t tmp{ o };
99  swap( *this, tmp );
100  return *this;
101  }
102  //! Move operator.
103  disp_params_t &
105  {
106  disp_params_t tmp{ std::move(o) };
107  swap( *this, tmp );
108  return *this;
109  }
110 
111  //! Setter for thread count.
112  disp_params_t &
113  thread_count( std::size_t count )
114  {
115  m_thread_count = count;
116  return *this;
117  }
118 
119  //! Getter for thread count.
120  std::size_t
121  thread_count() const
122  {
123  return m_thread_count;
124  }
125 
126  //! Use external Asio io_context object with dispatcher.
127  /*!
128  * Usage example:
129  * \code
130  * int main() {
131  * asio::io_context svc;
132  * so_5::launch( [&](so_5::environment_t & env) {
133  * namespace asio_tp = so_5::extra::disp::asio_thread_pool;
134  * auto disp = asio_tp::create_private_disp(
135  * env, "asio_tp",
136  * asio_tp::disp_params_t{}.use_external_io_context(
137  * so_5::outliving_mutable(svc) ) );
138  * ...
139  * } );
140  * }
141  * \endcode
142  */
143  disp_params_t &
145  ::asio::io_context & service )
146  {
147  m_io_context = std::shared_ptr< ::asio::io_context >(
148  std::addressof( service ),
149  // Empty deleter.
150  [](::asio::io_context *) {} );
151  return *this;
152  }
153 
154  //! Use external Asio io_context object with dispatcher.
155  /*!
156  * \note
157  * Ownership of this io_context object must be shared with
158  * others.
159  */
160  disp_params_t &
162  std::shared_ptr< ::asio::io_context > service )
163  {
164  m_io_context = std::move(service);
165  return *this;
166  }
167 
168  //! Use own Asio io_context object.
169  /*!
170  * Note this object will be dynamically created at the start
171  * of the dispatcher. And will be destroyed with the dispatcher object.
172  *
173  * A created io_context can be accessed later via io_context() method.
174  */
175  disp_params_t &
177  {
178  m_io_context = std::make_shared< ::asio::io_context >();
179  return *this;
180  }
181 
182  //! Get the io_context.
184  io_context() const noexcept
185  {
186  return m_io_context;
187  }
188 
189  private :
190  //! Count of working threads.
191  /*!
192  * Value 0 means that actual thread will be detected automatically.
193  */
195 
196  //! Asio's io_context which must be used with this dispatcher.
198  };
199 
200 //
201 // private_dispatcher_t
202 //
203 
204 /*!
205  * \brief An interface for %asio_thread_pool private dispatcher.
206  *
207  * \since
208  * v.1.0.2
209  */
211  {
212  public :
213  virtual ~private_dispatcher_t() = default;
214 
215  //! Create a binder for that private dispatcher.
217  binder( ::asio::io_context::strand & ) = 0;
218 
219  //! Get reference to io_context from that dispatcher.
220  virtual ::asio::io_context &
221  io_context() noexcept = 0;
222  };
223 
224 /*!
225  * \brief A handle for the %asio_thread_pool private dispatcher.
226  *
227  * \since
228  * v.1.0.2
229  */
232 
233 namespace impl {
234 
235 //
236 // demands_counter_t
237 //
238 /*!
239  * \brief Type of atomic counter for counting waiting demands.
240  *
241  * \since
242  * v.1.0.2
243  */
245 
246 //
247 // actual_disp_iface_t
248 //
249 /*!
250  * \brief An actual interface of thread pool dispatcher.
251  *
252  * \since
253  * v.1.0.2
254  */
256  {
257  public :
258  //! Notification about binding of yet another agent.
259  virtual void
260  agent_bound() noexcept = 0;
261 
262  //! Notification about unbinding of an agent.
263  virtual void
264  agent_unbound() noexcept = 0;
265 
266  //! Get a reference for counter of pending demands.
267  virtual demands_counter_t &
268  demands_counter_reference() noexcept = 0;
269  };
270 
271 //
272 // thread_local_ptr_holder_t
273 //
274 /*!
275  * \brief A helper for declaration of static and thread_local pointer
276  * in a header file.
277  *
278  * If non-template class will define a static member in a header file
279  * then there is a possibility to get a link-time error about multiple
280  * definition of that member. But if a static member is defined for
281  * template class then there won't be such problem.
282  *
283  * A typical usage intended to be:
284  * \code
285  * class some_useful_class_t : public thread_local_ptr_holder_t<some_useful_class_t> {
286  * ...
287  * };
288  * \endcode
289  *
290  * \since
291  * v.1.0.2
292  */
293 template< class T >
295  {
296  private :
297  //! Value of the pointer which need to be stored.
298  static thread_local T * m_ptr;
299 
300  protected :
301  //! Access to the current value of the pointer.
302  static T *
303  ptr() noexcept { return m_ptr; }
304 
305  //! Setter for the pointer.
306  static void
307  set_ptr( T * p ) noexcept { m_ptr = p; }
308  };
309 
310 template< class T >
311 thread_local T * thread_local_ptr_holder_t<T>::m_ptr = nullptr;
312 
313 //
314 // work_thread_t
315 //
316 /*!
317  * \brief Base type for implementations of work thread wrappers.
318  *
319  * Work thread wrapper creates an instance of some type on the stack
320  * of the new thread. Then the pointer of this instance is stored in
321  * thread_local variable (as a pointer to work_thread_t). This pointer
322  * then can be retrieved later by demand handlers to get access to
323  * some dispatcher-specific data.
324  *
325  * It is assumed that there will be two derived classes:
326  * 1. One for the case when thread activity should not be tracked.
327  * 2. Another for the case when thread activity must be tracked.
328  *
329  * These derived classes will reuse some functionality from
330  * work_thread_t. And should implement on_demand() method for
331  * actual demands processing.
332  *
333  * \since
334  * v.1.0.2
335  */
337  {
338  private :
339  //! ID of the work thread.
340  /*!
341  * Gets its value in the constructor and doesn't changed later.
342  */
344 
345  protected :
346  // Constructor and destructor are accessible for derived classes only.
349  {}
350 
351  // Just to make compilers happy.
352  virtual ~work_thread_t() = default;
353 
354  //! Actual processing of the demand.
355  /*!
356  * Must be implemented in derived classes.
357  */
358  virtual void
359  on_demand( execution_demand_t demand ) noexcept = 0;
360 
361  //! ID of the work thread.
363  thread_id() const noexcept
364  {
365  return m_thread_id;
366  }
367 
368  public :
369  //! Lauch processing of demand on the context of current thread.
370  /*!
371  * Creates an instance of Derived class, stores pointer to it into
372  * a thread_local static variable, then calls io_svc.run() method.
373  *
374  * \attention
375  * Terminates the whole application if an exception will be thrown.
376  *
377  * \tparam Derived Type of an object to be created on the stack.
378  * \tparam Args Types of arguments for Derived's constructor.
379  */
380  template< typename Derived, typename... Args >
381  static void
383  //! SObjectizer Environment for which work thread was created.
384  environment_t & env,
385  //! Asio IoService to be run on the context of that thread.
386  ::asio::io_context & io_svc,
387  //! Arguments to Derived's constructor.
388  Args &&... args )
389  {
390  // We don't expect any errors here.
391  // But if something happens then there is no way to
392  // recover and the whole application should be aborted.
393  try
394  {
396  // actual_handler must be accessible via thread_local variable.
398 
399  // Prevent return from io_context::run() if there is no
400  // more Asio's events.
401  auto work = ::asio::make_work_guard( io_svc );
402  io_svc.run();
403  }
404  catch( const std::exception & x )
405  {
408  log_stream << "An exception caught in work thread "
409  "of so_5::extra::disp::asio_thread_pool dispatcher."
410  " Exception: "
411  << x.what() << std::endl;
412  }
413  } );
414  }
415  catch( ... )
416  {
419  log_stream << "An unknown exception caught in work thread "
420  "of so_5::extra::disp::asio_thread_pool dispatcher."
421  << std::endl;
422  }
423  } );
424  }
425  }
426 
427  //! An interface method for passing a demand to processing.
428  static void
429  handle_demand( execution_demand_t demand )
430  {
431  ptr()->on_demand( std::move(demand) );
432  }
433  };
434 
435 //
436 // work_thread_without_activity_tracking_t
437 //
438 /*!
439  * \brief An implementation of work thread stuff for the case when
440  * thread activity tracking is not needed.
441  *
442  * \since
443  * v.1.0.2
444  */
445 class work_thread_without_activity_tracking_t final : public work_thread_t
446  {
447  public :
449  ~work_thread_without_activity_tracking_t() override = default;
450 
451  protected :
452  virtual void
453  on_demand( execution_demand_t demand ) noexcept override
454  {
455  demand.call_handler( thread_id() );
456  }
457  };
458 
459 //
460 // work_thread_activity_collector_t
461 //
462 /*!
463  * \brief Type of collector of work thread activity data.
464  *
465  * Objects of this class store also an ID of work thread. This ID is
466  * necessary for so_5::stats::messages::work_thread_activity message.
467  * Because of that a work thread must call setup_thread_id() method
468  * before use of activity collector.
469  *
470  * \since
471  * v.1.0.2
472  */
474  {
475  private :
476  //! ID of thread for which activity stats is collected.
478 
479  //! Collected activity stats.
483 
484  public :
485  /*!
486  * \brief Setup ID of the current work thread.
487  *
488  * \attention
489  * Must be called as soon as possible after the start of the work thread.
490  */
491  void
492  setup_thread_id( current_thread_id_t tid )
493  {
494  m_thread_id = std::move(tid);
495  }
496 
497  /*!
498  * \brief Get the ID of the thread.
499  *
500  * \attention
501  * Returns actual value only after call to setup_thread_id.
502  */
504  thread_id() const noexcept { return m_thread_id; }
505 
506  /*!
507  * \brief Mark start point of new activity.
508  */
509  void
510  activity_started() noexcept
511  {
512  m_work_activity.start();
513  }
514 
515  /*!
516  * \brief Mark completion of the current activity.
517  */
518  void
519  activity_finished() noexcept
520  {
521  m_work_activity.stop();
522  }
523 
524  /*!
525  * \brief Get the current stats.
526  */
529  {
530  ::so_5::stats::work_thread_activity_stats_t result;
531  result.m_working_stats = m_work_activity.take_stats();
532 
533  return result;
534  }
535  };
536 
537 //
538 // work_thread_with_activity_tracking_t
539 //
540 /*!
541  * \brief An implementation of work thread stuff for the case when
542  * thread activity tracking must be used.
543  *
544  * \since
545  * v.1.0.2
546  */
547 class work_thread_with_activity_tracking_t final : public work_thread_t
548  {
549  private :
550  //! Activity statistics.
552 
553  public :
555  outliving_reference_t< work_thread_activity_collector_t > activity_stats )
557  {
558  // Collector must receive ID of this thread.
559  m_activity_stats.get().setup_thread_id( thread_id() );
560  }
561 
562  ~work_thread_with_activity_tracking_t() override = default;
563 
564  protected :
565  virtual void
566  on_demand( execution_demand_t demand ) noexcept override
567  {
568  m_activity_stats.get().activity_started();
569 
570  demand.call_handler( thread_id() );
571 
572  m_activity_stats.get().activity_finished();
573  }
574  };
575 
576 //
577 // basic_dispatcher_skeleton_t
578 //
579 /*!
580  * \brief Basic stuff for all implementations of dispatcher.
581  *
582  * Derived classes should implement the following virtual methods:
583  * - data_source();
584  * - launch_work_threads();
585  * - wait_work_threads().
586  *
587  * \since
588  * v.1.0.2
589  */
591  {
592  protected :
594  friend class disp_data_source_t;
595 
596  public:
598  disp_params_t params )
601  {
602  }
603 
604  virtual void
605  start( environment_t & env ) override
606  {
607  data_source().start( outliving_mutable(env.stats_repository()) );
608 
609  ::so_5::details::do_with_rollback_on_exception(
610  [&] { launch_work_threads(env); },
611  [this] { data_source().stop(); } );
612  }
613 
614  virtual void
615  shutdown() override
616  {
617  ::so_5::details::invoke_noexcept_code( [this] {
618  // Stopping Asio IO service.
619  m_io_context->stop();
620  } );
621  }
622 
623  virtual void
624  wait() override
625  {
626  ::so_5::details::invoke_noexcept_code( [this] {
627  // Waiting for complete stop of all work threads.
628  wait_work_threads();
629  // Stopping data source.
630  data_source().stop();
631  } );
632  }
633 
634  virtual void
636  const std::string & name_base ) override
637  {
638  data_source().set_data_sources_name_base( name_base );
639  }
640 
641  virtual void
642  agent_bound() noexcept override
643  {
644  ++m_agents_bound;
645  }
646 
647  virtual void
648  agent_unbound() noexcept override
649  {
650  --m_agents_bound;
651  }
652 
653  virtual demands_counter_t &
654  demands_counter_reference() noexcept override
655  {
656  return m_demands_counter;
657  }
658 
659  ::asio::io_context &
660  io_context() const noexcept { return *m_io_context; }
661 
662  protected :
663  /*!
664  * \brief Get the count of work threads to be created.
665  */
666  std::size_t
667  thread_count() const noexcept { return m_thread_count; }
668 
669  /*!
670  * \brief Get access to actual data source instance for that
671  * dispatcher.
672  */
673  virtual disp_data_source_t &
674  data_source() noexcept = 0;
675 
676 
677 #if defined(__clang__)
678 #pragma clang diagnostic push
679 #pragma clang diagnostic ignored "-Wnon-virtual-dtor"
680 #endif
681 
682  /*!
683  * \brief Data source for run-time monitoring of whole dispatcher.
684  *
685  * \since
686  * v.1.0.2
687  */
690  {
691  //! Dispatcher to work with.
693 
694  //! Basic prefix for data sources.
696 
697  protected :
698  //! Access to data source prefix for derived classes.
699  const ::so_5::stats::prefix_t &
700  base_prefix() const noexcept { return m_base_prefix; }
701 
702  public :
704  : m_dispatcher( disp )
705  {}
706 
707  virtual void
708  distribute( const mbox_t & mbox ) override
709  {
710  const auto agents_count = m_dispatcher.m_agents_bound.load(
711  std::memory_order_acquire );
712 
713  const auto demands_count = m_dispatcher.m_demands_counter.load(
714  std::memory_order_acquire );
715 
716  send< ::so_5::stats::messages::quantity< std::size_t > >(
717  mbox,
718  m_base_prefix,
719  ::so_5::stats::suffixes::agent_count(),
720  agents_count );
721 
722  // Note: because there is no way to detect on which thread a
723  // demand will be handled, the total number of waiting
724  // demands is destributed for the whole dispatcher.
725  send< ::so_5::stats::messages::quantity< std::size_t > >(
726  mbox,
727  m_base_prefix,
728  ::so_5::stats::suffixes::work_thread_queue_size(),
729  demands_count );
730  }
731 
732  void
734  const std::string & name_base )
735  {
736  using namespace ::so_5::disp::reuse;
737 
738  m_base_prefix = make_disp_prefix(
739  "ext-asio-tp",
740  name_base,
741  &m_dispatcher );
742  }
743  };
744 
745 #if defined(__clang__)
746 #pragma clang diagnostic pop
747 #endif
748 
749  private:
750  //! Count of work threads.
752 
753  //! IO Service to work with.
755 
756  //! Count of agents bound to that dispatcher.
758 
759  //! Count of waiting demands.
761 
762  //! Start all working threads.
763  virtual void
765  //! SObjectizer Environment for which threads will be created.
766  environment_t & env ) = 0;
767 
768  //! Wait for finish of all threads.
769  /*!
770  * It is a blocking call. The current thread will be stopped until
771  * all work thread will finish their work.
772  */
773  virtual void
774  wait_work_threads() noexcept = 0;
775  };
776 
777 //
778 // dispatcher_skeleton_without_thread_activity_tracking_t
779 //
780 /*!
781  * \brief Extension of basic dispatcher skeleton for the case when
782  * work thread activity is not collected.
783  *
784  * This class contains disp_data_source_t instance and implements
785  * virtual method data_source() for accessing this instance.
786  *
787  * It also provides static method run_work_thread() which must be called
788  * at the beginnig of work thread.
789  *
790  * \since
791  * v.1.0.2
792  */
795  {
796  public :
798  disp_params_t params )
800  , m_data_source( *this )
801  {}
802 
803  protected :
804  virtual disp_data_source_t &
805  data_source() noexcept { return m_data_source; }
806 
807  //! Implementation of main function for a work thread.
808  static void
810  environment_t & env,
811  ::asio::io_context & io_svc,
813  std::size_t /*index*/ )
814  {
815  work_thread_t::run< work_thread_without_activity_tracking_t >(
816  env, io_svc );
817  }
818 
819  private :
820  //! Actual data source instance.
822  };
823 
824 //
825 // dispatcher_skeleton_with_thread_activity_tracking_t
826 //
827 /*!
828  * \brief Extension of basic dispatcher skeleton for the case when
829  * work thread activity must be collected.
830  *
831  * This class defines its own actual_disp_data_source_t type and
832  * contains an instance of that type. There is also implementation
833  * of data_source() virtual method for accessing this instance.
834  *
835  * It provides static method run_work_thread() which must be called
836  * at the beginnig of work thread.
837  *
838  * \since
839  * v.1.0.2
840  */
843  {
844 #if defined(__clang__)
845 #pragma clang diagnostic push
846 #pragma clang diagnostic ignored "-Wnon-virtual-dtor"
847 #endif
848  /*!
849  * \brief Actual data source type for dispatcher with
850  * work thread activity tracking.
851  *
852  * \since
853  * v.1.0.2
854  */
856  : public disp_data_source_t
857  {
858  private :
859  //! Collectors for run-time stats for every thread.
860  std::vector<
863 
864  public :
867  std::size_t thread_count )
868  : disp_data_source_t( disp )
870  {
871  for( auto & c : m_collectors )
872  c = std::make_unique< work_thread_activity_collector_t >();
873  }
874 
875  virtual void
876  distribute( const mbox_t & mbox ) override
877  {
878  disp_data_source_t::distribute( mbox );
879 
880  for( std::size_t i = 0; i != m_collectors.size(); ++i )
881  distribute_stats_for_work_thread_at( mbox, i );
882  }
883 
884  /*!
885  * \note \a index is not checked for validity!
886  */
888  collector_at( std::size_t index ) noexcept
889  {
890  return *(m_collectors[index]);
891  }
892 
893  private :
894  void
896  const mbox_t & mbox,
897  std::size_t index )
898  {
899  std::ostringstream ss;
900  ss << base_prefix().c_str() << "/wt-" << index;
901 
902  const ::so_5::stats::prefix_t prefix{ ss.str() };
903  auto & collector = collector_at( index );
904 
905  so_5::send< ::so_5::stats::messages::work_thread_activity >(
906  mbox,
907  prefix,
908  ::so_5::stats::suffixes::work_thread_activity(),
909  collector.thread_id(),
910  collector.take_activity_stats() );
911  }
912  };
913 
914 #if defined(__clang__)
915 #pragma clang diagnostic pop
916 #endif
917 
918  public :
920  disp_params_t params )
923  {}
924 
925  protected :
926  virtual disp_data_source_t &
927  data_source() noexcept override { return m_actual_data_source; }
928 
929  //! Implementation of main function for a work thread.
930  static void
932  //! SObjectizer Environment for which the work thread is created.
933  environment_t & env,
934  //! Asio IoService to be used.
935  ::asio::io_context & io_svc,
936  //! Dispatcher who owns this thread.
938  //! Ordinal number of this thread.
939  std::size_t index )
940  {
941  work_thread_t::run< work_thread_with_activity_tracking_t >(
942  env,
943  io_svc,
944  outliving_mutable(
945  self.m_actual_data_source.collector_at(index) ) );
946  }
947 
948  private :
949  //! Data source instance.
951  };
952 
953 //
954 // dispatcher_template_t
955 //
956 /*!
957  * \brief Template-based implementation of dispatcher.
958  *
959  * Implements virual methods launch_work_threads() and wait_work_threads()
960  * from basic_dispatcher_skeleton_t.
961  *
962  * \tparam Traits Traits-type to be used.
963  * \tparam Basic_Skeleton A specific skeleton to be used as base type.
964  * It expected to be dispatcher_skeleton_with_thread_activity_tracking_t or
965  * dispatcher_skeleton_without_thread_activity_tracking_t.
966  *
967  * \since
968  * v.1.0.2
969  */
970 template<
971  typename Traits,
972  typename Basic_Skeleton >
973 class dispatcher_template_t final : public Basic_Skeleton
974  {
975  public:
976  using Basic_Skeleton::Basic_Skeleton;
977 
978  private:
979  //! An alias for actual thread type.
980  using thread_t = typename Traits::thread_type;
981  //! An alias for unique_ptr to thread.
983 
984  //! Working threads.
986 
987  virtual void
989  environment_t & env ) override
990  {
991  using namespace std;
992 
993  m_threads.resize( this->thread_count() );
994 
996  for( std::size_t i = 0u; i != this->thread_count(); ++i )
997  {
998  m_threads[ i ] = this->make_work_thread( env, i );
999  }
1000  },
1001  [&] {
1002  ::so_5::details::invoke_noexcept_code( [&] {
1003  this->io_context().stop();
1004 
1005  // Shutdown all started threads.
1006  for( auto & t : m_threads )
1007  if( t )
1008  {
1009  t->join();
1010  t.reset();
1011  }
1012  else
1013  // No more started threads.
1014  break;
1015  } );
1016  } );
1017  }
1018 
1019  virtual void
1020  wait_work_threads() noexcept override
1021  {
1022  for( auto & t : m_threads )
1023  {
1024  t->join();
1025  t.reset();
1026  }
1027  }
1028 
1031  environment_t & env,
1032  std::size_t index )
1033  {
1034  Basic_Skeleton * self = this;
1035  return std::make_unique< thread_t >(
1036  [&env, io_svc = &this->io_context(), self, index]()
1037  {
1039  } );
1040  }
1041  };
1042 
1043 //
1044 // pseudo_event_queue_t
1045 //
1046 /*!
1047  * \brief An implementation of event_queue concept for the case of
1048  * %asio_thread_pool dispatcher.
1049  *
1050  * There is no such thing as event_queue for %asio_thread_pool dispacher.
1051  * All execution demands will be stored inside Asio IoServce and dispatched
1052  * for execution via asio::post mechanism. But SObjectizer requires
1053  * an implementation of event_queue which must be used for agents bound
1054  * to %asio_thread_pool dispatcher. This class implements this event_queue
1055  * concepts.
1056  *
1057  * Instances of that class will be created for every dispatcher binder.
1058  *
1059  * \since
1060  * v.1.0.2
1061  */
1062 class pseudo_event_queue_t final : public event_queue_t
1063  {
1064  public :
1065  //! Initializing constructor.
1067  //! Demands counter of the dispatcher. This counter will be
1068  //! incremented when demand is posted via asio::post() and will
1069  //! be decremented when demand is handled on the context of
1070  //! some work thread.
1071  outliving_reference_t< demands_counter_t > demands_counter,
1072  //! Strand to be used with this event_queue.
1073  outliving_reference_t< ::asio::io_context::strand > strand )
1075  , m_strand{ strand }
1076  {}
1077 
1078  virtual void
1079  push( execution_demand_t demand ) override
1080  {
1081  demands_counter_t * counter = &(m_demands_counter.get());
1082  // Another demand will wait for processing.
1083  ++(*counter);
1084 
1085  asio::post( m_strand.get(),
1086  [d = std::move(demand), counter]() mutable {
1087  // Another demand will be processed.
1088  --(*counter);
1089 
1090  // Delegate processing of the demand to actual
1091  // work thread.
1092  work_thread_t::handle_demand( std::move(d) );
1093  } );
1094  }
1095 
1096  private :
1097  //! Demands counter for the dispatcher.
1099  //! Strand to be used with this event_queue.
1101  };
1102 
1103 //
1104 // binding_actions_mixin_t
1105 //
1106 /*!
1107  * \brief Implementation of binding actions to be reused
1108  * in various binder implementation.
1109  *
1110  * \note
1111  * Holds an instance of pseudo_event_queue_t. It means that all
1112  * agents which will be bound via that binder will have the same
1113  * strand (cannot work in parallel).
1114  *
1115  * \since
1116  * v.1.0.2
1117  */
1119  {
1120  protected :
1122  outliving_reference_t< demands_counter_t > demands_counter,
1123  outliving_reference_t< ::asio::io_context::strand > strand )
1125  {}
1126 
1130  agent_ref_t agent )
1131  {
1132  auto result = [agent, &disp, this]() {
1133  disp.agent_bound();
1134 
1135  ::so_5::details::invoke_noexcept_code( [&] {
1136  agent->so_bind_to_dispatcher( m_event_queue );
1137  } );
1138  };
1139 
1140  return result;
1141  }
1142 
1143  void
1145  actual_disp_iface_t & disp,
1146  agent_ref_t /*agent*/ )
1147  {
1148  disp.agent_unbound();
1149  }
1150 
1151  private :
1152  pseudo_event_queue_t m_event_queue;
1153  };
1154 
1155 //
1156 // private_dispatcher_binder_t
1157 //
1158 
1159 /*!
1160  * \brief A binder for the private %asio_thread_pool dispatcher.
1161  *
1162  * \since
1163  * v.1.0.2
1164  */
1170 
1171 //
1172 // real_private_dispatcher_t
1173 //
1174 /*!
1175  * \brief A real implementation of private_dispatcher interface.
1176  *
1177  * Holds an actual dispatcher instance as a member.
1178  *
1179  * Starts the actual dispatcher in the constructor and stops in the destructor.
1180  *
1181  * \tparam Traits A type with traits for the dispatcher.
1182  * \tparam Basic_Skeleton A specific skeleton to be used for actual
1183  * dispatcher implementation.
1184  * It expected to be dispatcher_skeleton_with_thread_activity_tracking_t or
1185  * dispatcher_skeleton_without_thread_activity_tracking_t.
1186  *
1187  * \since
1188  * v.1.0.2
1189  */
1190 template<
1191  typename Traits,
1192  typename Basic_Skeleton >
1194  {
1195  //! An alias for actual dispatcher type.
1196  using actual_dispatcher_t =
1197  dispatcher_template_t< Traits, Basic_Skeleton >;
1198 
1199  public :
1200  /*!
1201  * Constructor creates a dispatcher instance and launches it.
1202  */
1204  //! SObjectizer Environment to work in.
1205  environment_t & env,
1206  //! Value for creating names of data sources for
1207  //! run-time monitoring.
1208  const std::string & data_sources_name_base,
1209  //! Parameters for the dispatcher.
1210  disp_params_t params )
1211  : m_disp( std::move(params) )
1212  {
1214  m_disp.start( env );
1215  }
1216 
1217  /*!
1218  * Destructors shuts an instance down and waits for it.
1219  */
1221  {
1222  m_disp.shutdown();
1223  m_disp.wait();
1224  }
1225 
1226  virtual disp_binder_unique_ptr_t
1227  binder( ::asio::io_context::strand & strand ) override
1228  {
1229  return disp_binder_unique_ptr_t(
1232  m_disp,
1235  }
1236 
1237  virtual ::asio::io_context &
1238  io_context() noexcept override
1239  {
1240  return m_disp.io_context();
1241  }
1242 
1243  private :
1244  //! An instance of the actual dispatcher.
1245  actual_dispatcher_t m_disp;
1246  };
1247 
1248 } /* namespace impl */
1249 
1250 //
1251 // default_thread_pool_size
1252 //
1253 /*!
1254  * \brief A helper function for detecting default thread count for
1255  * thread pool.
1256  *
1257  * \since
1258  * v.1.0.2
1259  */
1260 inline std::size_t
1262  {
1263  auto c = std::thread::hardware_concurrency();
1264  if( !c )
1265  c = 2;
1266 
1267  return c;
1268  }
1269 
1270 //
1271 // default_traits_t
1272 //
1273 /*!
1274  * \brief Default traits of %asio_thread_pool dispatcher.
1275  *
1276  * \since
1277  * v.1.0.2
1278  */
1280  {
1281  //! Type of thread.
1283  };
1284 
1285 //
1286 // create_private_disp
1287 //
1288 /*!
1289  * \brief A function for creation an instance of private dispatcher.
1290  *
1291  * Usage examples:
1292  * \code
1293  * // Dispatcher which uses own Asio IoService and default traits.
1294  * namespace asio_tp = so_5::extra::disp::asio_thread_pool;
1295  * asio_tp::disp_params_t params;
1296  * params.use_own_io_context(); // Asio IoService object will be created here.
1297  * // This object will be accessible later via
1298  * // private_dispatcher_t::io_context() method.
1299  * auto disp = asio_tp::create_private_disp(
1300  * env,
1301  * "my_asio_tp",
1302  * std::move(disp_params) );
1303  *
1304  *
1305  * // Dispatcher which uses external Asio IoService and default traits.
1306  * asio::io_context & io_svc = ...;
1307  * namespace asio_tp = so_5::extra::disp::asio_thread_pool;
1308  * asio_tp::disp_params_t params;
1309  * params.use_external_io_context( io_svc );
1310  * auto disp = asio_tp::create_private_disp(
1311  * env,
1312  * "my_asio_tp",
1313  * std::move(disp_params) );
1314  *
1315  *
1316  * // Dispatcher which uses own Asio IoService and custom traits.
1317  * struct my_traits
1318  * {
1319  * using thread_type = my_custom_thread_type;
1320  * };
1321  * namespace asio_tp = so_5::extra::disp::asio_thread_pool;
1322  * asio_tp::disp_params_t params;
1323  * params.use_own_io_context();
1324  * auto disp = asio_tp::create_private_disp< my_traits >(
1325  * env,
1326  * "my_asio_tp",
1327  * std::move(disp_params) );
1328  * \endcode
1329  *
1330  * \par Requirements for traits type
1331  * Traits type must define a type which looks like:
1332  * \code
1333  * struct traits
1334  * {
1335  * // Name of type to be used for thread class.
1336  * using thread_type = ...;
1337  * };
1338  * \endcode
1339  *
1340  * \par Requirements for custom thread type
1341  * By default std::thread is used as a class for working with threads.
1342  * But user can specify its own custom thread type via \a Traits::thread_type
1343  * parameter. A custom thread type must be a class which looks like:
1344  * \code
1345  * class custom_thread_type {
1346  * public :
1347  * // Must provide this constructor.
1348  * // F -- is a type of functional object which can be converted
1349  * // into std::function<void()>.
1350  * template<typename F>
1351  * custom_thread_type(F && f) {...}
1352  *
1353  * // Destructor must join thread if it is not joined yet.
1354  * ~custom_thread_type() noexcept {...}
1355  *
1356  * // The same semantic like std::thread::join.
1357  * void join() noexcept {...}
1358  * };
1359  * \endcode
1360  * This class doesn't need to be DefaultConstructible, CopyConstructible,
1361  * MoveConstructible, Copyable or Moveable.
1362  *
1363  * \tparam Traits Type with traits for a dispatcher. For the requirements
1364  * for \a Traits type see the section "Requirements for traits type" above.
1365  *
1366  * \since
1367  * v.1.0.2
1368  */
1369 template< typename Traits = default_traits_t >
1372  //! SObjectizer Environment to work in.
1373  environment_t & env,
1374  //! Value for creating names of data sources for
1375  //! run-time monitoring.
1377  //! Parameters for the dispatcher.
1379  {
1380  const auto io_svc_ptr = disp_params.io_context();
1381  if( !io_svc_ptr )
1384  "io_context is not set in disp_params" );
1385 
1386  if( !disp_params.thread_count() )
1388 
1391  // Type of result pointer.
1393  // Actual type of dispatcher without thread activity tracking.
1395  Traits,
1397  // Actual type of dispatcher with thread activity tracking.
1399  Traits,
1401  // Args for create_appropriate_disp.
1402  env,
1403  disp_params,
1404  // Args for real_private_dispatcher_t constructors.
1405  env,
1407  disp_params );
1408 
1409  return { disp.release() };
1410  }
1411 
1412 } /* namespace asio_thread_pool */
1413 
1414 } /* namespace disp */
1415 
1416 } /* namespace extra */
1417 
1418 } /* namespace so_5 */
A helper for declaration of static and thread_local pointer in a header file.
Definition: pub.hpp:294
::so_5::stats::prefix_t m_base_prefix
Basic prefix for data sources.
Definition: pub.hpp:695
Parameters for asio_thread_pool dispatcher.
Definition: pub.hpp:60
virtual void push(execution_demand_t demand) override
Definition: pub.hpp:1079
disp_params_t & operator=(const disp_params_t &o)
Copy operator.
Definition: pub.hpp:96
static void run_work_thread(environment_t &env, ::asio::io_context &io_svc, dispatcher_skeleton_without_thread_activity_tracking_t &, std::size_t)
Implementation of main function for a work thread.
Definition: pub.hpp:809
virtual void launch_work_threads(environment_t &env) override
Definition: pub.hpp:988
private_dispatcher_handle_t create_private_disp(environment_t &env, const std::string &data_sources_name_base, disp_params_t disp_params)
A function for creation an instance of private dispatcher.
Definition: pub.hpp:1371
std::shared_ptr< ::asio::io_context > io_context() const noexcept
Get the io_context.
Definition: pub.hpp:184
std::size_t thread_count() const noexcept
Get the count of work threads to be created.
Definition: pub.hpp:667
::so_5::stats::activity_tracking_stuff::stats_collector_t< ::so_5::stats::activity_tracking_stuff::internal_lock > m_work_activity
Collected activity stats.
Definition: pub.hpp:482
std::vector< std::unique_ptr< work_thread_activity_collector_t > > m_collectors
Collectors for run-time stats for every thread.
Definition: pub.hpp:862
Basic stuff for all implementations of dispatcher.
Definition: pub.hpp:590
virtual void start(environment_t &env) override
Definition: pub.hpp:605
static void set_ptr(T *p) noexcept
Setter for the pointer.
Definition: pub.hpp:307
void activity_finished() noexcept
Mark completion of the current activity.
Definition: pub.hpp:519
disp_params_t(const disp_params_t &o)
Copy constructor.
Definition: pub.hpp:70
Base type for implementations of work thread wrappers.
Definition: pub.hpp:336
virtual demands_counter_t & demands_counter_reference() noexcept=0
Get a reference for counter of pending demands.
virtual disp_binder_unique_ptr_t binder(::asio::io_context::strand &)=0
Create a binder for that private dispatcher.
const std::shared_ptr< ::asio::io_context > m_io_context
IO Service to work with.
Definition: pub.hpp:754
Template-based implementation of dispatcher.
Definition: pub.hpp:973
disp_params_t & thread_count(std::size_t count)
Setter for thread count.
Definition: pub.hpp:113
disp_params_t & operator=(disp_params_t &&o)
Move operator.
Definition: pub.hpp:104
virtual disp_data_source_t & data_source() noexcept=0
Get access to actual data source instance for that dispatcher.
virtual void set_data_sources_name_base(const std::string &name_base) override
Definition: pub.hpp:635
const std::size_t m_thread_count
Count of work threads.
Definition: pub.hpp:751
virtual void agent_unbound() noexcept override
Notification about unbinding of an agent.
Definition: pub.hpp:648
An interface for asio_thread_pool private dispatcher.
Definition: pub.hpp:210
virtual void on_demand(execution_demand_t demand) noexcept override
Actual processing of the demand.
Definition: pub.hpp:453
pseudo_event_queue_t(outliving_reference_t< demands_counter_t > demands_counter, outliving_reference_t< ::asio::io_context::strand > strand)
Initializing constructor.
Definition: pub.hpp:1066
static thread_local T * m_ptr
Value of the pointer which need to be stored.
Definition: pub.hpp:298
static void run(environment_t &env, ::asio::io_context &io_svc, Args &&... args)
Lauch processing of demand on the context of current thread.
Definition: pub.hpp:382
std::size_t thread_count() const
Getter for thread count.
Definition: pub.hpp:121
A real implementation of private_dispatcher interface.
Definition: pub.hpp:1193
Ranges for error codes of each submodules.
Definition: details.hpp:14
disp_params_t(disp_params_t &&o)
Move constructor.
Definition: pub.hpp:76
void setup_thread_id(current_thread_id_t tid)
Setup ID of the current work thread.
Definition: pub.hpp:492
std::shared_ptr< ::asio::io_context > m_io_context
Asio&#39;s io_context which must be used with this dispatcher.
Definition: pub.hpp:197
static T * ptr() noexcept
Access to the current value of the pointer.
Definition: pub.hpp:303
std::vector< thread_unique_ptr_t > m_threads
Working threads.
Definition: pub.hpp:985
outliving_reference_t< ::asio::io_context::strand > m_strand
Strand to be used with this event_queue.
Definition: pub.hpp:1100
virtual void on_demand(execution_demand_t demand) noexcept=0
Actual processing of the demand.
outliving_reference_t< work_thread_activity_collector_t > m_activity_stats
Activity statistics.
Definition: pub.hpp:551
demands_counter_t m_demands_counter
Count of waiting demands.
Definition: pub.hpp:760
virtual demands_counter_t & demands_counter_reference() noexcept override
Get a reference for counter of pending demands.
Definition: pub.hpp:654
binding_actions_mixin_t(outliving_reference_t< demands_counter_t > demands_counter, outliving_reference_t< ::asio::io_context::strand > strand)
Definition: pub.hpp:1121
virtual disp_binder_unique_ptr_t binder(::asio::io_context::strand &strand) override
Create a binder for that private dispatcher.
Definition: pub.hpp:1227
disp_params_t & use_own_io_context()
Use own Asio io_context object.
Definition: pub.hpp:176
disp_params_t & use_external_io_context(::asio::io_context &service)
Use external Asio io_context object with dispatcher.
Definition: pub.hpp:144
static void run_work_thread(environment_t &env, ::asio::io_context &io_svc, dispatcher_skeleton_with_thread_activity_tracking_t &self, std::size_t index)
Implementation of main function for a work thread.
Definition: pub.hpp:931
real_private_dispatcher_t(environment_t &env, const std::string &data_sources_name_base, disp_params_t params)
Definition: pub.hpp:1203
virtual void launch_work_threads(environment_t &env)=0
Start all working threads.
static void handle_demand(execution_demand_t demand)
An interface method for passing a demand to processing.
Definition: pub.hpp:429
std::size_t default_thread_pool_size()
A helper function for detecting default thread count for thread pool.
Definition: pub.hpp:1261
Data source for run-time monitoring of whole dispatcher.
Definition: pub.hpp:688
friend void swap(disp_params_t &a, disp_params_t &b) noexcept
Definition: pub.hpp:83
virtual disp_data_source_t & data_source() noexcept override
Get access to actual data source instance for that dispatcher.
Definition: pub.hpp:927
work_thread_with_activity_tracking_t(outliving_reference_t< work_thread_activity_collector_t > activity_stats)
Definition: pub.hpp:554
disp_params_t & use_external_io_context(std::shared_ptr< ::asio::io_context > service)
Use external Asio io_context object with dispatcher.
Definition: pub.hpp:161
Default traits of asio_thread_pool dispatcher.
Definition: pub.hpp:1279
Implementation of binding actions to be reused in various binder implementation.
Definition: pub.hpp:1118
outliving_reference_t< demands_counter_t > m_demands_counter
Demands counter for the dispatcher.
Definition: pub.hpp:1098
std::atomic< std::size_t > m_agents_bound
Count of agents bound to that dispatcher.
Definition: pub.hpp:757
current_thread_id_t thread_id() const noexcept
Get the ID of the thread.
Definition: pub.hpp:504
void do_unbind(actual_disp_iface_t &disp, agent_ref_t)
Definition: pub.hpp:1144
An actual interface of thread pool dispatcher.
Definition: pub.hpp:255
actual_dispatcher_t m_disp
An instance of the actual dispatcher.
Definition: pub.hpp:1245
const ::so_5::stats::prefix_t & base_prefix() const noexcept
Access to data source prefix for derived classes.
Definition: pub.hpp:700
Extension of basic dispatcher skeleton for the case when work thread activity must be collected...
Definition: pub.hpp:841
Type of collector of work thread activity data.
Definition: pub.hpp:473
virtual ::asio::io_context & io_context() noexcept=0
Get reference to io_context from that dispatcher.
virtual disp_data_source_t & data_source() noexcept
Get access to actual data source instance for that dispatcher.
Definition: pub.hpp:805
virtual void agent_bound() noexcept=0
Notification about binding of yet another agent.
current_thread_id_t m_thread_id
ID of the work thread.
Definition: pub.hpp:343
Extension of basic dispatcher skeleton for the case when work thread activity is not collected...
Definition: pub.hpp:793
::so_5::stats::work_thread_activity_stats_t take_activity_stats() noexcept
Get the current stats.
Definition: pub.hpp:528
virtual void agent_bound() noexcept override
Notification about binding of yet another agent.
Definition: pub.hpp:642
virtual void wait_work_threads() noexcept=0
Wait for finish of all threads.
disp_params_t()=default
Default constructor.
virtual void on_demand(execution_demand_t demand) noexcept override
Actual processing of the demand.
Definition: pub.hpp:566
const int rc_io_context_is_not_set
Asio IoService is not set for asio_thread_pool dispatcher.
Definition: pub.hpp:46
current_thread_id_t thread_id() const noexcept
ID of the work thread.
Definition: pub.hpp:363
void activity_started() noexcept
Mark start point of new activity.
Definition: pub.hpp:510
disp_binding_activator_t do_bind(actual_disp_iface_t &disp, agent_ref_t agent)
Definition: pub.hpp:1128
virtual ::asio::io_context & io_context() noexcept override
Get reference to io_context from that dispatcher.
Definition: pub.hpp:1238
virtual void agent_unbound() noexcept=0
Notification about unbinding of an agent.
current_thread_id_t m_thread_id
ID of thread for which activity stats is collected.
Definition: pub.hpp:477
std::size_t m_thread_count
Count of working threads.
Definition: pub.hpp:194
basic_dispatcher_skeleton_t & m_dispatcher
Dispatcher to work with.
Definition: pub.hpp:692
thread_unique_ptr_t make_work_thread(environment_t &env, std::size_t index)
Definition: pub.hpp:1030