1 /*
2 	SObjectizer 5.
3 */
4 
5 #include <so_5/coop.hpp>
6 
7 #include <so_5/impl/internal_env_iface.hpp>
8 #include <so_5/impl/internal_agent_iface.hpp>
9 #include <so_5/impl/agent_ptr_compare.hpp>
10 
11 #include <so_5/details/abort_on_fatal_error.hpp>
12 
13 #include <so_5/exception.hpp>
14 #include <so_5/environment.hpp>
15 
16 #include <exception>
17 #include <algorithm>
18 
19 namespace so_5
20 {
21 
22 //
23 // coop_reg_notificators_container_t
24 //
25 void
call_all(environment_t & env,const coop_handle_t & coop) const26 coop_reg_notificators_container_t::call_all(
27 	environment_t & env,
28 	const coop_handle_t & coop ) const noexcept
29 	{
30 		for( auto & n : m_notificators )
31 			n( env, coop );
32 	}
33 
34 //
35 // coop_dereg_notificators_container_t
36 //
37 void
call_all(environment_t & env,const coop_handle_t & coop,const coop_dereg_reason_t & reason) const38 coop_dereg_notificators_container_t::call_all(
39 	environment_t & env,
40 	const coop_handle_t & coop,
41 	const coop_dereg_reason_t & reason ) const noexcept
42 	{
43 		for( auto & n : m_notificators )
44 			n( env, coop, reason );
45 	}
46 
47 namespace impl
48 {
49 
50 //
51 // coop_impl_t
52 //
53 void
destroy_content(coop_t & coop)54 coop_impl_t::destroy_content(
55 	coop_t & coop ) noexcept
56 	{
57 		using std::swap;
58 
59 		// Initiate deleting of agents by hand to guarantee that
60 		// agents will be destroyed before return from coop_t
61 		// destructor.
62 		//
63 		// NOTE: because agents are stored here by smart references
64 		// for some agents this operation will lead only to reference
65 		// counter descrement. Not to deletion of agent.
66 		decltype(coop.m_agent_array) agents;
67 		swap( coop.m_agent_array, agents );
68 
69 		agents.clear();
70 
71 		// Now all user resources should be destroyed.
72 		decltype(coop.m_resource_deleters) resources;
73 		swap( coop.m_resource_deleters, resources );
74 
75 		for( auto & d : resources )
76 			d();
77 	}
78 
79 void
do_add_agent(coop_t & coop,agent_ref_t agent_ref)80 coop_impl_t::do_add_agent(
81 	coop_t & coop,
82 	agent_ref_t agent_ref )
83 	{
84 		coop.m_agent_array.emplace_back(
85 				std::move(agent_ref), coop.m_coop_disp_binder );
86 	}
87 
88 void
do_add_agent(coop_t & coop,agent_ref_t agent_ref,disp_binder_shptr_t disp_binder)89 coop_impl_t::do_add_agent(
90 	coop_t & coop,
91 	agent_ref_t agent_ref,
92 	disp_binder_shptr_t disp_binder )
93 	{
94 		coop.m_agent_array.emplace_back(
95 				std::move(agent_ref), std::move(disp_binder) );
96 	}
97 
98 namespace
99 {
100 	/*!
101 	 * \since
102 	 * v.5.2.3
103 	 *
104 	 * \brief Helper function for notificator addition.
105 	 */
106 	template< class C, class N >
107 	inline void
do_add_notificator_to(intrusive_ptr_t<C> & to,N notificator)108 	do_add_notificator_to(
109 		intrusive_ptr_t< C > & to,
110 		N notificator )
111 	{
112 		if( !to )
113 		{
114 			to = intrusive_ptr_t< C >( new C() );
115 		}
116 
117 		to->add( std::move(notificator) );
118 	}
119 
120 } /* namespace anonymous */
121 
122 void
add_reg_notificator(coop_t & coop,coop_reg_notificator_t notificator)123 coop_impl_t::add_reg_notificator(
124 	coop_t & coop,
125 	coop_reg_notificator_t notificator )
126 	{
127 		do_add_notificator_to(
128 				coop.m_reg_notificators,
129 				std::move(notificator) );
130 	}
131 
132 void
add_dereg_notificator(coop_t & coop,coop_dereg_notificator_t notificator)133 coop_impl_t::add_dereg_notificator(
134 	coop_t & coop,
135 	coop_dereg_notificator_t notificator )
136 	{
137 		do_add_notificator_to(
138 				coop.m_dereg_notificators,
139 				std::move(notificator) );
140 	}
141 
142 [[nodiscard]]
143 exception_reaction_t
exception_reaction(const coop_t & coop)144 coop_impl_t::exception_reaction(
145 	const coop_t & coop ) noexcept
146 	{
147 		if( inherit_exception_reaction == coop.m_exception_reaction )
148 			{
149 				const auto parent = so_5::low_level_api::to_shptr_noexcept(
150 						coop.m_parent );
151 				if( parent )
152 					return parent->exception_reaction();
153 				else
154 					return coop.environment().exception_reaction();
155 			}
156 
157 		return coop.m_exception_reaction;
158 	}
159 
160 void
do_decrement_reference_count(coop_t & coop)161 coop_impl_t::do_decrement_reference_count(
162 	coop_t & coop ) noexcept
163 	{
164 		// If it is the last working agent then Environment should be
165 		// informed that the cooperation is ready to be deregistered.
166 		if( 0 == --coop.m_reference_count )
167 			{
168 				// NOTE: usage counter incremented and decremented during
169 				// registration process even if registration of cooperation failed.
170 				// So decrement_usage_count() could be called when cooperation
171 				// has coop_not_registered status.
172 				//
173 				// It is possible that reference counter become 0 several times.
174 				// For example when a child coop is being registered while
175 				// the parent coop is in deregistration process.
176 				// Because of that it is necessary to check the current status
177 				// of the coop.
178 				//
179 				// If the coop should be deregistered finally its status should
180 				// be changed to deregistration_in_final_stage.
181 				const auto should_finalize = [&] {
182 					std::lock_guard< std::mutex > lock{ coop.m_lock };
183 
184 					using status_t = coop_t::registration_status_t;
185 					if( status_t::coop_registered == coop.m_registration_status ||
186 						status_t::coop_deregistering == coop.m_registration_status )
187 						{
188 							coop.m_registration_status =
189 									status_t::deregistration_in_final_stage;
190 							return true;
191 						}
192 					else
193 						return false;
194 				};
195 
196 				if( should_finalize() )
197 					{
198 						impl::internal_env_iface_t{ coop.m_env.get() }
199 								.ready_to_deregister_notify( coop.shared_from_this() );
200 					}
201 			}
202 	}
203 
204 class coop_impl_t::registration_performer_t
205 	{
206 		coop_t & m_coop;
207 
208 		void
perform_actions_without_rollback_on_exception()209 		perform_actions_without_rollback_on_exception()
210 			{
211 				reorder_agents_with_respect_to_priorities();
212 				bind_agents_to_coop();
213 				preallocate_disp_resources();
214 			}
215 
216 		void
perform_actions_with_rollback_on_exception()217 		perform_actions_with_rollback_on_exception()
218 			{
219 				so_5::details::do_with_rollback_on_exception( [this] {
220 						define_all_agents();
221 
222 						// Coop's lock should be acquired before notification
223 						// of the parent coop.
224 						std::lock_guard< std::mutex > lock{ m_coop.m_lock };
225 						make_relation_with_parent_coop();
226 
227 						// These actions shouldn't throw.
228 						details::invoke_noexcept_code( [&] {
229 							// This operation shouldn't throw because dispatchers
230 							// allocated resources for agents.
231 							//
232 							// But it is possible that an exception will be throw
233 							// during an attempt to send evt_start message to agents.
234 							// In that case it is simpler to call std::terminate().
235 							bind_agents_to_disp();
236 
237 							// Cooperation should assume that it is registered now.
238 							m_coop.m_registration_status =
239 									coop_t::registration_status_t::coop_registered;
240 
241 							// Increment reference count to reflect that cooperation
242 							// is registered. This is necessary in v.5.5.12 to prevent
243 							// automatic deregistration of the cooperation right after
244 							// finish of registration process for empty cooperation.
245 							m_coop.increment_usage_count();
246 						} );
247 					},
248 					[this] {
249 						// NOTE: we use the fact that actual binding of agents to
250 						// dispatchers can't throw. It means that exception was thrown
251 						// at earlier stages (in define_all_agents() or
252 						// make_relation_with_parent_coop()).
253 						deallocate_disp_resources();
254 					} );
255 			}
256 
257 		void
reorder_agents_with_respect_to_priorities()258 		reorder_agents_with_respect_to_priorities() noexcept
259 			{
260 				std::sort(
261 						std::begin(m_coop.m_agent_array),
262 						std::end(m_coop.m_agent_array),
263 						[]( const auto & a, const auto & b ) noexcept {
264 							return special_agent_ptr_compare(
265 									*a.m_agent_ref, *b.m_agent_ref );
266 						} );
267 			}
268 
269 		void
bind_agents_to_coop()270 		bind_agents_to_coop()
271 			{
272 				for( auto & i : m_coop.m_agent_array )
273 					internal_agent_iface_t{ *i.m_agent_ref }.bind_to_coop( m_coop );
274 			}
275 
276 		void
preallocate_disp_resources()277 		preallocate_disp_resources()
278 			{
279 				// In case of an exception we should undo preallocation only for
280 				// those agents for which preallocation was successful.
281 				coop_t::agent_array_t::iterator it;
282 				try
283 					{
284 						for( it = m_coop.m_agent_array.begin();
285 								it != m_coop.m_agent_array.end();
286 								++it )
287 							{
288 								it->m_binder->preallocate_resources(
289 										*(it->m_agent_ref) );
290 							}
291 					}
292 				catch( const std::exception & x )
293 					{
294 						// All preallocated resources should be returned back.
295 						for( auto it2 = m_coop.m_agent_array.begin();
296 								it2 != it;
297 								++it2 )
298 							{
299 								it2->m_binder->undo_preallocation(
300 										*(it2->m_agent_ref) );
301 							}
302 
303 						SO_5_THROW_EXCEPTION(
304 								rc_agent_to_disp_binding_failed,
305 								std::string{
306 										"an exception during the first stage of "
307 										"binding agent to the dispatcher, exception: " }
308 								+ x.what() );
309 					}
310 			}
311 
312 		void
define_all_agents()313 		define_all_agents()
314 			{
315 				try
316 					{
317 						for( auto & info : m_coop.m_agent_array )
318 							internal_agent_iface_t{ *info.m_agent_ref }
319 									.initiate_agent_definition();
320 					}
321 				catch( const exception_t & )
322 					{
323 						throw;
324 					}
325 				catch( const std::exception & ex )
326 					{
327 						SO_5_THROW_EXCEPTION(
328 							rc_coop_define_agent_failed,
329 							ex.what() );
330 					}
331 				catch( ... )
332 					{
333 						SO_5_THROW_EXCEPTION(
334 							rc_coop_define_agent_failed,
335 							"exception of unknown type has been thrown in "
336 							"so_define_agent()" );
337 					}
338 			}
339 
340 		void
make_relation_with_parent_coop()341 		make_relation_with_parent_coop()
342 			{
343 				so_5::low_level_api::to_shptr(m_coop.m_parent)->add_child(
344 						m_coop.shared_from_this() );
345 			}
346 
347 		void
bind_agents_to_disp()348 		bind_agents_to_disp() noexcept
349 			{
350 				for( auto & info : m_coop.m_agent_array )
351 					info.m_binder->bind( *info.m_agent_ref );
352 			}
353 
354 		void
deallocate_disp_resources()355 		deallocate_disp_resources() noexcept
356 			{
357 				for( auto & info : m_coop.m_agent_array )
358 					info.m_binder->undo_preallocation( *(info.m_agent_ref) );
359 			}
360 
361 	public :
registration_performer_t(coop_t & coop)362 		explicit registration_performer_t( coop_t & coop ) noexcept
363 			:	m_coop{ coop }
364 			{}
365 
366 		void
perform()367 		perform()
368 			{
369 				// On first phase we perform actions that don't require
370 				// any rollback on exception.
371 				perform_actions_without_rollback_on_exception();
372 
373 				// Then we should perform some actions that require some
374 				// rollback in the case of an exception.
375 				perform_actions_with_rollback_on_exception();
376 			}
377 	};
378 
379 void
do_registration_specific_actions(coop_t & coop)380 coop_impl_t::do_registration_specific_actions( coop_t & coop )
381 	{
382 		registration_performer_t{ coop }.perform();
383 	}
384 
385 //
386 // deregistration_performer_t
387 //
388 //! A helper for coop's deregistration procedure.
389 class coop_impl_t::deregistration_performer_t
390 	{
391 		coop_t & m_coop;
392 		const coop_dereg_reason_t m_reason;
393 
394 		enum class phase1_result_t
395 			{
396 				dereg_initiated,
397 				dereg_already_in_progress
398 			};
399 
400 		phase1_result_t
perform_phase1()401 		perform_phase1() noexcept
402 			{
403 				// The first phase should be performed on locked object.
404 				std::lock_guard< std::mutex > lock{ m_coop.m_lock };
405 
406 				if( coop_t::registration_status_t::coop_registered !=
407 						m_coop.m_registration_status )
408 					// Deregistration is already in progress.
409 					// Nothing to do.
410 					return phase1_result_t::dereg_already_in_progress;
411 
412 				// Deregistration process should be started.
413 				m_coop.m_registration_status =
414 						coop_t::registration_status_t::coop_deregistering;
415 				m_coop.m_dereg_reason = m_reason;
416 
417 				initiate_deregistration_for_children();
418 
419 				return phase1_result_t::dereg_initiated;
420 			}
421 
422 		void
shutdown_all_agents()423 		shutdown_all_agents() noexcept
424 			{
425 				for( auto & info : m_coop.m_agent_array )
426 					internal_agent_iface_t{ *info.m_agent_ref }.shutdown_agent();
427 			}
428 
429 		void
initiate_deregistration_for_children()430 		initiate_deregistration_for_children() noexcept
431 			{
432 				m_coop.for_each_child( []( coop_t & coop ) {
433 						coop.deregister( dereg_reason::parent_deregistration );
434 					} );
435 			}
436 
437 	public :
deregistration_performer_t(coop_t & coop,coop_dereg_reason_t reason)438 		deregistration_performer_t(
439 			coop_t & coop,
440 			coop_dereg_reason_t reason ) noexcept
441 			:	m_coop{ coop }
442 			,	m_reason{ reason }
443 			{}
444 
445 		void
perform()446 		perform() noexcept
447 			{
448 				auto result = perform_phase1();
449 
450 				if( phase1_result_t::dereg_initiated == result )
451 					{
452 						// Deregistration is initiated the first time.
453 
454 						// All agents should be shut down.
455 						shutdown_all_agents();
456 
457 						// Reference count to this coop can be decremented.
458 						// If there is no more uses of that coop then the coop
459 						// will be deregistered completely.
460 						m_coop.decrement_usage_count();
461 					}
462 			}
463 	};
464 
465 void
do_deregistration_specific_actions(coop_t & coop,coop_dereg_reason_t reason)466 coop_impl_t::do_deregistration_specific_actions(
467 	coop_t & coop,
468 	coop_dereg_reason_t reason ) noexcept
469 	{
470 		deregistration_performer_t{ coop, reason }.perform();
471 	}
472 
473 void
do_final_deregistration_actions(coop_t & coop)474 coop_impl_t::do_final_deregistration_actions(
475 	coop_t & coop )
476 	{
477 		// Agents should be unbound from their dispatchers.
478 		for( auto & info : coop.m_agent_array )
479 			info.m_binder->unbind( *info.m_agent_ref );
480 
481 		// Now the coop can be removed from it's parent.
482 		// We don't except an exception here because m_parent should
483 		// contain an actual value.
484 		// But if not then we have a serious problem and it is better
485 		// to terminate the application.
486 		so_5::low_level_api::to_shptr(coop.m_parent)->remove_child( coop );
487 	}
488 
489 void
do_add_child(coop_t & parent,coop_shptr_t child)490 coop_impl_t::do_add_child(
491 	coop_t & parent,
492 	coop_shptr_t child )
493 	{
494 		// Count of users on this coop is incremented.
495 		parent.increment_usage_count();
496 
497 		// If an exception is throw below then usage count for the parent
498 		// coop should be decremented.
499 		so_5::details::do_with_rollback_on_exception( [&] {
500 				// Modification of parent-child relationship must be performed
501 				// on locked object.
502 				std::lock_guard< std::mutex > lock{ parent.m_lock };
503 
504 				// A new coop can't be added as a child if coop is being
505 				// deregistered.
506 				if( coop_t::registration_status_t::coop_registered !=
507 						parent.m_registration_status )
508 					SO_5_THROW_EXCEPTION(
509 							rc_coop_is_not_in_registered_state,
510 							"add_child() can be processed only when coop "
511 							"is registered" );
512 
513 				// New child will be inserted to the head of children list.
514 				if( parent.m_first_child )
515 					parent.m_first_child->m_prev_sibling = child;
516 
517 				child->m_next_sibling = std::move(parent.m_first_child);
518 
519 				parent.m_first_child = std::move(child);
520 			},
521 			[&parent] {
522 				// Something went wrong. Count of references should be
523 				// returned back.
524 				parent.decrement_usage_count();
525 			} );
526 	}
527 
528 void
do_remove_child(coop_t & parent,coop_t & child)529 coop_impl_t::do_remove_child(
530 	coop_t & parent,
531 	coop_t & child )
532 	{
533 		{
534 			// Modification of parent-child relationship must be performed
535 			// on locked object.
536 			std::lock_guard< std::mutex > lock{ parent.m_lock };
537 
538 			if( parent.m_first_child.get() == &child )
539 			{
540 				// Child was a head of children chain. There is no prev-sibling
541 				// for the child to be removed.
542 				parent.m_first_child = child.m_next_sibling;
543 				if( parent.m_first_child )
544 					parent.m_first_child->m_prev_sibling.reset();
545 			}
546 			else
547 			{
548 				child.m_prev_sibling->m_next_sibling = child.m_next_sibling;
549 				if( child.m_next_sibling )
550 					child.m_next_sibling->m_prev_sibling = child.m_prev_sibling;
551 			}
552 		}
553 
554 		// Count of references to the parent coop can be decremented now.
555 		parent.decrement_usage_count();
556 	}
557 
558 } /* namespace impl */
559 
560 } /* namespace so_5 */
561 
562