1 /*!
2  * \file
3  * \brief Implementation of Asio's One Thread dispatcher.
4  *
5  * \since
6  * v.1.4.1
7  */
8 
9 #pragma once
10 
11 #include <so_5_extra/error_ranges.hpp>
12 
13 #include <so_5/disp_binder.hpp>
14 #include <so_5/send_functions.hpp>
15 
16 #include <so_5/disp/reuse/work_thread_activity_tracking.hpp>
17 #include <so_5/disp/reuse/data_source_prefix_helpers.hpp>
18 
19 #include <so_5/stats/repository.hpp>
20 #include <so_5/stats/messages.hpp>
21 #include <so_5/stats/std_names.hpp>
22 #include <so_5/stats/impl/activity_tracking.hpp>
23 
24 #include <so_5/details/invoke_noexcept_code.hpp>
25 #include <so_5/details/rollback_on_exception.hpp>
26 #include <so_5/details/abort_on_fatal_error.hpp>
27 
28 #include <so_5/impl/thread_join_stuff.hpp>
29 
30 #include <so_5/outliving.hpp>
31 
32 #include <asio/io_context.hpp>
33 #include <asio/post.hpp>
34 
35 namespace so_5 {
36 
37 namespace extra {
38 
39 namespace disp {
40 
41 namespace asio_one_thread {
42 
43 namespace errors {
44 
45 //! Asio IoService is not set for asio_thread_pool dispatcher.
46 const int rc_io_context_is_not_set =
47 		so_5::extra::errors::asio_one_thread_errors;
48 
49 } /* namespace errors */
50 
51 /*!
52  * \brief An alias for shared-pointer to io_context object.
53  *
54  * \since v.1.4.1
55  */
56 using io_context_shptr_t = std::shared_ptr< asio::io_context >;
57 
58 //
59 // disp_params_t
60 //
61 /*!
62  * \brief Parameters for %asio_one_thread dispatcher.
63  */
64 class disp_params_t
65 	:	public ::so_5::disp::reuse::work_thread_activity_tracking_flag_mixin_t< disp_params_t >
66 	{
67 		using activity_tracking_mixin_t = ::so_5::disp::reuse::
68 				work_thread_activity_tracking_flag_mixin_t< disp_params_t >;
69 
70 	public :
71 		//! Default constructor.
72 		disp_params_t() = default;
73 
74 		friend inline void
swap(disp_params_t & a,disp_params_t & b)75 		swap(
76 			disp_params_t & a, disp_params_t & b ) noexcept
77 			{
78 				using std::swap;
79 
80 				swap(
81 						static_cast< activity_tracking_mixin_t & >(a),
82 						static_cast< activity_tracking_mixin_t & >(b) );
83 
84 				swap( a.m_io_context, b.m_io_context );
85 			}
86 
87 		//! Use external Asio io_context object with dispatcher.
88 		/*!
89 		 * Usage example:
90 		 * \code
91 		 * int main() {
92 		 * 	asio::io_context svc;
93 		 * 	so_5::launch( [&](so_5::environment_t & env) {
94 		 * 		namespace asio_ot = so_5::extra::disp::asio_one_thread;
95 		 * 		auto disp = asio_ot::create_private_disp(
96 		 * 				env, "asio_ot",
97 		 * 				asio_tp::disp_params_t{}.use_external_io_context(svc) );
98 		 * 		...
99 		 * 	} );
100 		 * }
101 		 * \endcode
102 		 */
103 		disp_params_t &
use_external_io_context(::asio::io_context & service)104 		use_external_io_context(
105 			::asio::io_context & service )
106 			{
107 				m_io_context = std::shared_ptr< ::asio::io_context >(
108 						std::addressof( service ),
109 						// Empty deleter.
110 						[](::asio::io_context *) {} );
111 				return *this;
112 			}
113 
114 		//! Use external Asio io_context object with dispatcher.
115 		/*!
116 		 * \note
117 		 * Ownership of this io_context object must be shared with
118 		 * others.
119 		 */
120 		disp_params_t &
use_external_io_context(std::shared_ptr<::asio::io_context> service)121 		use_external_io_context(
122 			std::shared_ptr< ::asio::io_context > service )
123 			{
124 				m_io_context = std::move(service);
125 				return *this;
126 			}
127 
128 		//! Use own Asio io_context object.
129 		/*!
130 		 * Note this object will be dynamically created at the start
131 		 * of the dispatcher. And will be destroyed with the dispatcher object.
132 		 *
133 		 * A created io_context can be accessed later via io_context() method.
134 		 */
135 		disp_params_t &
use_own_io_context()136 		use_own_io_context()
137 			{
138 				m_io_context = std::make_shared< ::asio::io_context >();
139 				return *this;
140 			}
141 
142 		//! Get the io_context.
143 		std::shared_ptr< ::asio::io_context >
io_context() const144 		io_context() const noexcept
145 			{
146 				return m_io_context;
147 			}
148 
149 	private :
150 		//! Asio's io_context which must be used with this dispatcher.
151 		std::shared_ptr< ::asio::io_context > m_io_context;
152 	};
153 
154 namespace impl {
155 
156 //
157 // actual_disp_binder_t
158 //
159 /*!
160  * \brief An actual interace of disp_binder for asio_one_thread dispatcher.
161  *
162  * That binder should allow to get a reference to io_context objects.
163  *
164  * \since v.1.4.1
165  */
166 class actual_disp_binder_t
167 	:	public disp_binder_t
168 	{
169 	public :
170 		[[nodiscard]]
171 		virtual asio::io_context &
172 		io_context() noexcept = 0;
173 	};
174 
175 //
176 // actual_disp_binder_shptr_t
177 //
178 /*!
179  * \brief An alias for shared-pointer to actual_disp_binder.
180  *
181  * \since v.1.4.1
182  */
183 using actual_disp_binder_shptr_t =
184 		std::shared_ptr< actual_disp_binder_t >;
185 
186 class dispatcher_handle_maker_t;
187 
188 } /* namespace impl */
189 
190 //
191 // dispatcher_handle_t
192 //
193 
194 /*!
195  * \brief A handle for %asio_one_thread dispatcher.
196  *
197  * \since
198  * v.1.4.1
199  */
200 class [[nodiscard]] dispatcher_handle_t
201 	{
202 		friend class impl::dispatcher_handle_maker_t;
203 
204 		//! A reference to actual implementation of a dispatcher.
205 		impl::actual_disp_binder_shptr_t m_binder;
206 
dispatcher_handle_t(impl::actual_disp_binder_shptr_t binder)207 		dispatcher_handle_t(
208 			impl::actual_disp_binder_shptr_t binder ) noexcept
209 			:	m_binder{ std::move(binder) }
210 			{}
211 
212 		//! Is this handle empty?
213 		[[nodiscard]]
214 		bool
empty() const215 		empty() const noexcept { return !m_binder; }
216 
217 	public :
218 		dispatcher_handle_t() noexcept = default;
219 
220 		//! Get a binder for that dispatcher.
221 		/*!
222 		 * Usage example:
223 		 * \code
224 		 * using namespace so_5::extra::disp::asio_one_thread;
225 		 *
226 		 * asio::io_context io_ctx;
227 		 *
228 		 * so_5::environment_t & env = ...;
229 		 * auto disp = make_dispatcher( env, "my_disp", io_ctx );
230 		 *
231 		 * env.introduce_coop( [&]( so_5::coop_t & coop ) {
232 		 * 	coop.make_agent_with_binder< some_agent_type >(disp.binder(), ...);
233 		 * 	...
234 		 * } );
235 		 * \endcode
236 		 *
237 		 * \attention
238 		 * An attempt to call this method on empty handle is UB.
239 		 */
240 		[[nodiscard]]
241 		disp_binder_shptr_t
binder() const242 		binder() const
243 			{
244 				return m_binder;
245 			}
246 
247 		//! Get reference to io_context from that dispatcher.
248 		/*!
249 		 * \attention
250 		 * An attempt to call this method on empty handle is UB.
251 		 */
252 		[[nodiscard]]
253 		::asio::io_context &
io_context()254 		io_context() noexcept
255 			{
256 				return m_binder->io_context();
257 			}
258 
259 		//! Is this handle empty?
260 		[[nodiscard]]
operator bool() const261 		operator bool() const noexcept { return empty(); }
262 
263 		//! Does this handle contain a reference to dispatcher?
264 		[[nodiscard]]
265 		bool
operator !() const266 		operator!() const noexcept { return !empty(); }
267 
268 		//! Drop the content of handle.
269 		void
reset()270 		reset() noexcept { m_binder.reset(); }
271 	};
272 
273 namespace impl {
274 
275 //
276 // demands_counter_t
277 //
278 /*!
279  * \brief Type of atomic counter for counting waiting demands.
280  *
281  * \since
282  * v.1.4.1
283  */
284 using demands_counter_t = std::atomic< std::size_t >;
285 
286 namespace work_thread_details {
287 
288 /*!
289  * \brief A type of holder of data common to all worker thread
290  * implementations.
291  *
292  * \since v.1.4.1
293  */
294 template< typename Thread_Type >
295 struct common_data_t
296 	{
297 		//! Asio's context to be used.
298 		io_context_shptr_t m_io_context;
299 
300 		//! Thread object.
301 		/*!
302 		 * @note
303 		 * Thread object is stored via unique_ptr becase custom thread
304 		 * type can have deleted/disable move constructor/operator.
305 		 */
306 		std::unique_ptr< Thread_Type > m_thread;
307 
308 		//! ID of the work thread.
309 		/*!
310 		 * \note Receives actual value only after successful start
311 		 * of the thread.
312 		 */
313 		so_5::current_thread_id_t m_thread_id;
314 
315 		//! Counter of waiting demands.
316 		demands_counter_t m_demands_counter{ 0u };
317 
common_data_tso_5::extra::disp::asio_one_thread::impl::work_thread_details::common_data_t318 		common_data_t( io_context_shptr_t io_context )
319 			: m_io_context( std::move(io_context) ) {}
320 
321 		[[nodiscard]]
322 		asio::io_context &
io_contextso_5::extra::disp::asio_one_thread::impl::work_thread_details::common_data_t323 		io_context() const noexcept { return *(this->m_io_context); }
324 
325 		[[nodiscard]]
326 		demands_counter_t &
demands_counterso_5::extra::disp::asio_one_thread::impl::work_thread_details::common_data_t327 		demands_counter() noexcept { return this->m_demands_counter; }
328 	};
329 
330 /*!
331  * \brief Base class for implementation of worker thread without
332  * thread activity tracking.
333  *
334  * Methods work_started() and work_finished() are empty.
335  *
336  * \brief v.1.4.1
337  */
338 template< typename Thread_Type >
339 class no_activity_tracking_impl_t : protected common_data_t< Thread_Type >
340 	{
341 		using base_type_t = common_data_t< Thread_Type >;
342 
343 	public :
no_activity_tracking_impl_t(io_context_shptr_t io_context)344 		no_activity_tracking_impl_t( io_context_shptr_t io_context )
345 			:	base_type_t( std::move(io_context) )
346 			{}
347 
348 		using base_type_t::io_context;
349 
350 		using base_type_t::demands_counter;
351 
352 	protected :
353 		void
work_started()354 		work_started() { /* Nothing to do. */ }
355 
356 		void
work_finished()357 		work_finished() { /* Nothing to do. */ }
358 	};
359 
360 /*!
361  * \brief Base class for implementation of worker thread with
362  * thread activity tracking.
363  *
364  * Methods work_started() and work_finished() perform actial activity
365  * tracking.
366  *
367  * This class also provides public method take_activity_stats() to
368  * retrive activity statistics.
369  *
370  * \brief v.1.4.1
371  */
372 template< typename Thread_Type >
373 class with_activity_tracking_impl_t : protected common_data_t< Thread_Type >
374 	{
375 		using base_type_t = common_data_t< Thread_Type >;
376 
377 	public :
with_activity_tracking_impl_t(io_context_shptr_t io_context)378 		with_activity_tracking_impl_t( io_context_shptr_t io_context )
379 			:	base_type_t( std::move(io_context) )
380 			{}
381 
382 		using base_type_t::io_context;
383 
384 		using base_type_t::demands_counter;
385 
386 		[[nodiscard]]
387 		so_5::stats::work_thread_activity_stats_t
take_activity_stats()388 		take_activity_stats()
389 			{
390 				so_5::stats::work_thread_activity_stats_t result;
391 
392 				result.m_working_stats = m_working_stats.take_stats();
393 
394 				return result;
395 			}
396 
397 	protected :
398 		//! Statictics for work activity.
399 		so_5::stats::activity_tracking_stuff::stats_collector_t<
400 				so_5::stats::activity_tracking_stuff::internal_lock >
401 			m_working_stats;
402 
403 		void
work_started()404 		work_started() { m_working_stats.start(); }
405 
406 		void
work_finished()407 		work_finished() { m_working_stats.stop(); }
408 	};
409 
410 } /* namespace work_thread_details */
411 
412 //
413 // work_thread_template_t
414 //
415 /*!
416  * \brief An implementation of worker thread in form of the template class.
417  *
418  * Work_Thread parameter is expected to be
419  * work_thread_details::no_activity_tracking_impl_t or
420  * work_thread_details::with_activity_tracking_impl_t.
421  *
422  * This class is also play a role of event_queue. It's because there
423  * is no real event-queue to be controlled by this class. All
424  * demands are delegated to io_context object.
425  *
426  * \since v.1.4.1
427  */
428 template<
429 	typename Thread_Type,
430 	template<class> class Work_Thread >
431 class work_thread_template_t
432 	:	public Work_Thread< Thread_Type >
433 	,	public event_queue_t
434 	{
435 		using base_type_t = Work_Thread< Thread_Type >;
436 
437 		//! SObjectizer Environment to work in.
438 		/*!
439 		 * We have to store that reference to have an ability to
440 		 * log error messages in thread's body.
441 		 */
442 		environment_t & m_env;
443 
444 	public :
445 		//! Initializing constructor.
work_thread_template_t(environment_t & env,io_context_shptr_t io_context)446 		work_thread_template_t(
447 			environment_t & env,
448 			io_context_shptr_t io_context )
449 			:	base_type_t( std::move(io_context) )
450 			,	m_env( env )
451 			{}
452 
453 		//! Starts a new thread.
454 		/*!
455 		 * Passes all \a thread_init_args to the constructor of
456 		 * of Thread_Type after the lambda-function with thread body.
457 		 * It means that if there is a call:
458 		 * \code
459 		 * work_thread.start( 0, "my-thread", 0.1f );
460 		 * \endcode
461 		 * Then the actual call for Thread_Type's constructor will
462 		 * look like:
463 		 * \code
464 		 * new Thread_Type( [this] {...}, 0, "my-thread", 0.1f );
465 		 * \endcode
466 		 */
467 		template< typename... Thread_Init_Args >
468 		void
start(Thread_Init_Args &&...thread_init_args)469 		start( Thread_Init_Args && ...thread_init_args )
470 			{
471 				this->m_thread = std::make_unique< Thread_Type >(
472 						[this]() { body(); },
473 						std::forward<Thread_Init_Args>(thread_init_args)... );
474 			}
475 
476 		void
stop()477 		stop()
478 			{
479 				this->io_context().stop();
480 			}
481 
482 		void
join()483 		join()
484 			{
485 				if( this->m_thread )
486 					{
487 						so_5::impl::ensure_join_from_different_thread(
488 								this->m_thread_id );
489 						this->m_thread->join();
490 					}
491 			}
492 
493 		so_5::current_thread_id_t
thread_id() const494 		thread_id() const
495 			{
496 				return this->m_thread_id;
497 			}
498 
499 		void
push(execution_demand_t demand)500 		push( execution_demand_t demand ) override
501 			{
502 				// Demand count statistics should be updated.
503 				++(this->demands_counter());
504 
505 				// If posting a demand fails the count of demands
506 				// should be decremented.
507 				::so_5::details::do_with_rollback_on_exception(
508 						[&] {
509 							asio::post( this->io_context(),
510 								[d = std::move(demand), this]() mutable {
511 									this->handle_demand( std::move(d) );
512 								} );
513 						},
514 						[this] {
515 							--this->demands_counter();
516 						} );
517 			}
518 
519 	private :
520 		void
body()521 		body()
522 			{
523 				this->m_thread_id = so_5::query_current_thread_id();
524 
525 				// We don't expect any errors here.
526 				// But if something happens then there is no way to
527 				// recover and the whole application should be aborted.
528 				try
529 					{
530 						// Prevent return from io_context::run() if there is no
531 						// more Asio's events.
532 						auto work = ::asio::make_work_guard( this->io_context() );
533 						this->io_context().run();
534 					}
535 				catch( const std::exception & x )
536 					{
537 						::so_5::details::abort_on_fatal_error( [&] {
538 							SO_5_LOG_ERROR( this->m_env, log_stream ) {
539 								log_stream << "An exception caught in work thread "
540 									"of so_5::extra::disp::asio_one_thread dispatcher."
541 									" Exception: "
542 									<< x.what() << std::endl;
543 							}
544 						} );
545 					}
546 				catch( ... )
547 					{
548 						::so_5::details::abort_on_fatal_error( [&] {
549 							SO_5_LOG_ERROR( this->m_env, log_stream ) {
550 								log_stream << "An unknown exception caught in work thread "
551 									"of so_5::extra::disp::asio_one_thread dispatcher."
552 									<< std::endl;
553 							}
554 						} );
555 					}
556 			}
557 
558 		void
handle_demand(execution_demand_t demand)559 		handle_demand( execution_demand_t demand ) noexcept
560 			{
561 				// Demand count statistics should be updated.
562 				--(this->demands_counter());
563 
564 				this->work_started();
565 				auto work_meter_stopper = so_5::details::at_scope_exit(
566 						[this] { this->work_finished(); } );
567 
568 				demand.call_handler( this->m_thread_id );
569 			}
570 	};
571 
572 //
573 // work_thread_no_activity_tracking_t
574 //
575 template< typename Thread_Type >
576 using work_thread_no_activity_tracking_t =
577 	work_thread_template_t<
578 			Thread_Type,
579 			work_thread_details::no_activity_tracking_impl_t >;
580 
581 template< typename Thread_Type >
582 void
send_thread_activity_stats(const so_5::mbox_t &,const so_5::stats::prefix_t &,work_thread_no_activity_tracking_t<Thread_Type> &)583 send_thread_activity_stats(
584 	const so_5::mbox_t &,
585 	const so_5::stats::prefix_t &,
586 	work_thread_no_activity_tracking_t< Thread_Type > & )
587 	{
588 		/* Nothing to do */
589 	}
590 
591 //
592 // work_thread_with_activity_tracking_t
593 //
594 template< typename Thread_Type >
595 using work_thread_with_activity_tracking_t =
596 	work_thread_template_t<
597 			Thread_Type,
598 			work_thread_details::with_activity_tracking_impl_t >;
599 
600 template< typename Thread_Type >
601 void
send_thread_activity_stats(const so_5::mbox_t & mbox,const so_5::stats::prefix_t & prefix,work_thread_with_activity_tracking_t<Thread_Type> & wt)602 send_thread_activity_stats(
603 	const so_5::mbox_t & mbox,
604 	const so_5::stats::prefix_t & prefix,
605 	work_thread_with_activity_tracking_t< Thread_Type > & wt )
606 	{
607 		so_5::send< so_5::stats::messages::work_thread_activity >(
608 				mbox,
609 				prefix,
610 				so_5::stats::suffixes::work_thread_activity(),
611 				wt.thread_id(),
612 				wt.take_activity_stats() );
613 	}
614 
615 //
616 // dispatcher_template_t
617 //
618 /*!
619  * \brief An implementation of the dispatcher in the form of template class.
620  *
621  * This dispatcher launches worker thread in the constructor and
622  * stops and joins it in the destructor.
623  *
624  * \since v.1.4.1
625  */
626 template< typename Work_Thread >
627 class dispatcher_template_t final : public actual_disp_binder_t
628 	{
629 		friend class disp_data_source_t;
630 
631 	public:
632 		template< typename... Thread_Init_Args >
dispatcher_template_t(outliving_reference_t<environment_t> env,const std::string_view name_base,disp_params_t params,Thread_Init_Args &&...thread_init_args)633 		dispatcher_template_t(
634 			outliving_reference_t< environment_t > env,
635 			const std::string_view name_base,
636 			disp_params_t params,
637 			Thread_Init_Args && ...thread_init_args )
638 			:	m_work_thread{ env.get(), params.io_context() }
639 			,	m_data_source{
640 					outliving_mutable(env.get().stats_repository()),
641 					name_base,
642 					outliving_mutable(*this)
643 				}
644 			{
645 				m_work_thread.start(
646 						std::forward<Thread_Init_Args>(thread_init_args)... );
647 			}
648 
~dispatcher_template_t()649 		~dispatcher_template_t() noexcept override
650 			{
651 				m_work_thread.stop();
652 				m_work_thread.join();
653 			}
654 
655 		void
preallocate_resources(agent_t &)656 		preallocate_resources(
657 			agent_t & /*agent*/ ) override
658 			{
659 				// Nothing to do.
660 			}
661 
662 		void
undo_preallocation(agent_t &)663 		undo_preallocation(
664 			agent_t & /*agent*/ ) noexcept override
665 			{
666 				// Nothing to do.
667 			}
668 
669 		void
bind(agent_t & agent)670 		bind(
671 			agent_t & agent ) noexcept override
672 			{
673 				agent.so_bind_to_dispatcher( m_work_thread );
674 				++m_agents_bound;
675 			}
676 
677 		void
unbind(agent_t &)678 		unbind(
679 			agent_t & /*agent*/ ) noexcept override
680 			{
681 				--m_agents_bound;
682 			}
683 
684 		[[nodiscard]]
685 		asio::io_context &
io_context()686 		io_context() noexcept override
687 			{
688 				return m_work_thread.io_context();
689 			}
690 
691 	private:
692 
693 		/*!
694 		 * \brief Data source for run-time monitoring of whole dispatcher.
695 		 *
696 		 * \since
697 		 * v.1.4.1
698 		 */
699 		class disp_data_source_t : public stats::source_t
700 			{
701 				//! Dispatcher to work with.
702 				outliving_reference_t< dispatcher_template_t > m_dispatcher;
703 
704 				//! Basic prefix for data sources.
705 				stats::prefix_t m_base_prefix;
706 
707 			public :
disp_data_source_t(const std::string_view name_base,outliving_reference_t<dispatcher_template_t> disp)708 				disp_data_source_t(
709 					const std::string_view name_base,
710 					outliving_reference_t< dispatcher_template_t > disp )
711 					:	m_dispatcher{ disp }
712 					,	m_base_prefix{ so_5::disp::reuse::make_disp_prefix(
713 								"ext-asio-ot",
714 								name_base,
715 								&(disp.get()) )
716 						}
717 					{}
718 
719 				void
distribute(const mbox_t & mbox)720 				distribute( const mbox_t & mbox ) override
721 					{
722 						so_5::send< stats::messages::quantity< std::size_t > >(
723 								mbox,
724 								this->m_base_prefix,
725 								stats::suffixes::agent_count(),
726 								m_dispatcher.get().m_agents_bound.load(
727 										std::memory_order_acquire ) );
728 
729 						so_5::send< stats::messages::quantity< std::size_t > >(
730 								mbox,
731 								this->m_base_prefix,
732 								stats::suffixes::work_thread_queue_size(),
733 								m_dispatcher.get().m_work_thread.demands_counter().load(
734 										std::memory_order_acquire ) );
735 
736 						send_thread_activity_stats(
737 								mbox,
738 								this->m_base_prefix,
739 								m_dispatcher.get().m_work_thread );
740 					}
741 
742 			private:
743 			};
744 
745 		//! Working thread for the dispatcher.
746 		Work_Thread m_work_thread;
747 
748 		//! Data source for run-time monitoring.
749 		stats::auto_registered_source_holder_t< disp_data_source_t >
750 				m_data_source;
751 
752 		//! Count of agents bound to this dispatcher.
753 		std::atomic< std::size_t > m_agents_bound = { 0 };
754 	};
755 
756 //
757 // dispatcher_handle_maker_t
758 //
759 /*!
760  * \brief A factory class for creation of dispatcher_handle instances.
761  *
762  * \since v.1.4.1
763  */
764 class dispatcher_handle_maker_t
765 	{
766 	public :
767 		[[nodiscard]]
768 		static dispatcher_handle_t
make(actual_disp_binder_shptr_t binder)769 		make( actual_disp_binder_shptr_t binder ) noexcept
770 			{
771 				return { std::move( binder ) };
772 			}
773 	};
774 
775 //
776 // create_dispatcher
777 //
778 /*!
779  * \brief The actual implementation of dispatcher creation procedure.
780  *
781  * \tparam Traits Type of traits to be used for a new dispatcher.
782  * \tparam Thread_Init_Args Types of arguments to be passed as additional
783  * parameters to the constructor of Traits::thread_type.
784  *
785  * \since v.1.4.1
786  */
787 template<
788 	typename Traits,
789 	typename... Thread_Init_Args >
790 [[nodiscard]]
791 dispatcher_handle_t
create_dispatcher(environment_t & env,const std::string_view data_sources_name_base,disp_params_t params,Thread_Init_Args &&...thread_init_args)792 create_dispatcher(
793 	//! SObjectizer environment to work in.
794 	environment_t & env,
795 	//! Short name for this instance to be used in thread activity stats.
796 	//! Can be empty string. In this case name will be generated automatically.
797 	const std::string_view data_sources_name_base,
798 	//! Parameters for this dispatcher instance.
799 	disp_params_t params,
800 	//! Parameters for initialization of a custom thread.
801 	Thread_Init_Args && ...thread_init_args )
802 	{
803 		using namespace so_5::disp::reuse;
804 
805 		const auto io_svc_ptr = params.io_context();
806 		if( !io_svc_ptr )
807 			SO_5_THROW_EXCEPTION(
808 					errors::rc_io_context_is_not_set,
809 					"io_context is not set in disp_params" );
810 
811 		using thread_type = typename Traits::thread_type;
812 
813 		using dispatcher_no_activity_tracking_t =
814 				dispatcher_template_t<
815 						work_thread_no_activity_tracking_t< thread_type > >;
816 
817 		using dispatcher_with_activity_tracking_t =
818 				dispatcher_template_t<
819 						work_thread_with_activity_tracking_t< thread_type > >;
820 
821 		using so_5::stats::activity_tracking_stuff::create_appropriate_disp;
822 		actual_disp_binder_shptr_t binder =
823 				create_appropriate_disp<
824 						// Type of result pointer.
825 						impl::actual_disp_binder_t,
826 						// Actual type of dispatcher without thread activity tracking.
827 						dispatcher_no_activity_tracking_t,
828 						// Actual type of dispatcher with thread activity tracking.
829 						dispatcher_with_activity_tracking_t >(
830 					outliving_mutable(env),
831 					data_sources_name_base,
832 					std::move(params),
833 					std::forward<Thread_Init_Args>(thread_init_args)... );
834 
835 		return dispatcher_handle_maker_t::make( std::move(binder) );
836 	}
837 
838 } /* namespace impl */
839 
840 //
841 // default_traits_t
842 //
843 /*!
844  * \brief Default traits of %asio_one_thread dispatcher.
845  *
846  * \since
847  * v.1.4.1
848  */
849 struct default_traits_t
850 	{
851 		//! Type of thread.
852 		using thread_type = std::thread;
853 	};
854 
855 //
856 // make_dispatcher
857 //
858 /*!
859  * \brief A function for creation an instance of %asio_one_thread dispatcher.
860  *
861  * Usage examples:
862  * \code
863  * // Dispatcher which uses own Asio IoContext and default traits.
864  * namespace asio_disp = so_5::extra::disp::asio_one_thread;
865  * asio_disp::disp_params_t params;
866  * params.use_own_io_context(); // Asio IoContext object will be created here.
867  * 		// This object will be accessible later via
868  * 		// dispatcher_handle_t::io_context() method.
869  * auto disp = asio_disp::make_dispatcher(
870  * 	env,
871  * 	"my_asio_disp",
872  * 	std::move(disp_params) );
873  * \endcode
874  *
875  * \code
876  * // Dispatcher which uses external Asio IoContext and default traits.
877  * asio::io_context & io_svc = ...;
878  * namespace asio_disp = so_5::extra::disp::asio_one_thread;
879  * asio_disp::disp_params_t params;
880  * params.use_external_io_context( io_svc );
881  * auto disp = asio_disp::make_dispatcher(
882  * 	env,
883  * 	"my_asio_disp",
884  * 	std::move(disp_params) );
885  * \endcode
886  *
887  * \code
888  * // Dispatcher which uses own Asio IoContext and custom traits.
889  * struct my_traits
890  * {
891  * 	using thread_type = my_custom_thread_type;
892  * };
893  * namespace asio_disp = so_5::extra::disp::asio_one_thread;
894  * asio_disp::disp_params_t params;
895  * params.use_own_io_context();
896  * auto disp = asio_disp::make_dispatcher< my_traits >(
897  * 	env,
898  * 	"my_asio_tp",
899  * 	std::move(disp_params) );
900  * \endcode
901  *
902  * \par Requirements for traits type
903  * Traits type must define a type which looks like:
904  * \code
905  * struct traits
906  * {
907  * 	// Name of type to be used for thread class.
908  * 	using thread_type = ...;
909  * };
910  * \endcode
911  *
912  * \par Requirements for custom thread type
913  * By default std::thread is used as a class for working with threads.
914  * But user can specify its own custom thread type via \a Traits::thread_type
915  * parameter. A custom thread type must be a class which looks like:
916  * \code
917  * class custom_thread_type {
918  * public :
919  * 	// Must provide this constructor.
920  * 	// F -- is a type of functional object which can be converted
921  * 	// into std::function<void()>.
922  * 	template<typename F>
923  * 	custom_thread_type(F && f) {...}
924  *
925  * 	// Destructor must join thread if it is not joined yet.
926  * 	~custom_thread_type() noexcept {...}
927  *
928  * 	// The same semantic like std::thread::join.
929  * 	void join() noexcept {...}
930  * };
931  * \endcode
932  * This class doesn't need to be DefaultConstructible, CopyConstructible,
933  * MoveConstructible, Copyable or Moveable.
934  *
935  * \tparam Traits Type with traits for a dispatcher. For the requirements
936  * for \a Traits type see the section "Requirements for traits type" above.
937  *
938  * \since
939  * v.1.4.1
940  */
941 template< typename Traits = default_traits_t >
942 dispatcher_handle_t
make_dispatcher(environment_t & env,const std::string_view data_sources_name_base,disp_params_t params)943 make_dispatcher(
944 	//! SObjectizer environment to work in.
945 	environment_t & env,
946 	//! Short name for this instance to be used in thread activity stats.
947 	//! Can be empty string. In this case name will be generated automatically.
948 	const std::string_view data_sources_name_base,
949 	//! Parameters for this dispatcher instance.
950 	disp_params_t params )
951 	{
952 		return impl::create_dispatcher< Traits >(
953 				env,
954 				data_sources_name_base,
955 				std::move(params) );
956 	}
957 
958 /*!
959  * \brief A function for creation an instance of %asio_one_thread dispatcher
960  * with a set of arguments for custom thread object's constructor.
961  *
962  * Usage example:
963  * \code
964  * // Dispatcher which uses own Asio IoContext and custom traits.
965  * class my_custom_thread_type
966  * {
967  * 	...
968  * public:
969  * 	template< typename F >
970  * 	my_custom_thread_type(
971  * 		F && body,
972  * 		int priority,
973  * 		std::string instance_name,
974  * 		std::size_t stack_size )
975  * 	{...}
976  *
977  * 	...
978  * };
979  * struct my_traits
980  * {
981  * 	using thread_type = my_custom_thread_type;
982  * };
983  * namespace asio_disp = so_5::extra::disp::asio_one_thread;
984  * asio_disp::disp_params_t params;
985  * params.use_own_io_context();
986  * auto disp = asio_disp::make_dispatcher< my_traits >(
987  * 	env,
988  * 	"my_asio_tp",
989  * 	std::move(disp_params),
990  * 	// Those parameters will be passed to the constructor
991  * 	// of my_custom_thread_type.
992  * 	2, "my-asio-one-thread"s, 8192u);
993  * \endcode
994  *
995  * \par Requirements for traits type
996  * Traits type must define a type which looks like:
997  * \code
998  * struct traits
999  * {
1000  * 	// Name of type to be used for thread class.
1001  * 	using thread_type = ...;
1002  * };
1003  * \endcode
1004  *
1005  * \par Requirements for custom thread type
1006  * A custom thread type must be a class which looks like:
1007  * \code
1008  * class custom_thread_type {
1009  * public :
1010  * 	// Must provide this constructor.
1011  * 	// F -- is a type of functional object which can be converted
1012  * 	// into std::function<void()>.
1013  * 	template<
1014  * 		typename F,
1015  * 		typename... Init_Args>
1016  * 	custom_thread_type(F && f, Init_Args && ...init_args) {...}
1017  *
1018  * 	// Destructor must join thread if it is not joined yet.
1019  * 	~custom_thread_type() noexcept {...}
1020  *
1021  * 	// The same semantic like std::thread::join.
1022  * 	void join() noexcept {...}
1023  * };
1024  * \endcode
1025  * This class doesn't need to be DefaultConstructible, CopyConstructible,
1026  * MoveConstructible, Copyable or Moveable.
1027  *
1028  * \tparam Traits Type with traits for a dispatcher. For the requirements
1029  * for \a Traits type see the section "Requirements for traits type" above.
1030  * \tparam Thread_Init_Args Types of parameters to be passed to the
1031  * constructor of Traits::thread_type.
1032  *
1033  * \since
1034  * v.1.4.1
1035  */
1036 template<
1037 	typename Traits,
1038 	typename... Thread_Init_Args >
1039 dispatcher_handle_t
make_dispatcher(environment_t & env,const std::string_view data_sources_name_base,disp_params_t params,Thread_Init_Args &&...thread_init_args)1040 make_dispatcher(
1041 	//! SObjectizer environment to work in.
1042 	environment_t & env,
1043 	//! Short name for this instance to be used in thread activity stats.
1044 	//! Can be empty string. In this case name will be generated automatically.
1045 	const std::string_view data_sources_name_base,
1046 	//! Parameters for this dispatcher instance.
1047 	disp_params_t params,
1048 	//! Parameters for initialization of a custom thread.
1049 	Thread_Init_Args && ...thread_init_args )
1050 	{
1051 		return impl::create_dispatcher< Traits >(
1052 				env,
1053 				data_sources_name_base,
1054 				std::move(params),
1055 				std::forward<Thread_Init_Args>(thread_init_args)... );
1056 	}
1057 
1058 } /* namespace asio_one_thread */
1059 
1060 } /* namespace disp */
1061 
1062 } /* namespace extra */
1063 
1064 } /* namespace so_5 */
1065 
1066