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