1 /*
2  * SObjectizer-5
3  */
4 
5 /*!
6  * \file
7  * \brief Repository of stop_guards.
8  *
9  * \since
10  * v.5.5.19.2
11  */
12 
13 #pragma once
14 
15 #include <so_5/stop_guard.hpp>
16 
17 #include <so_5/details/sync_helpers.hpp>
18 #include <so_5/details/invoke_noexcept_code.hpp>
19 
20 #include <mutex>
21 #include <algorithm>
22 #include <iterator>
23 
24 namespace so_5 {
25 
26 namespace impl {
27 
28 //
29 // stop_guard_repository_t
30 //
31 /*!
32  * \brief Repository of stop_guards.
33  *
34  * \note
35  * Performs multitheading defense by very simple way: via std::mutex object.
36  *
37  * \since
38  * v.5.5.19.2
39  */
40 class stop_guard_repository_t
41 	: public ::so_5::details::lock_holder_detector< std::mutex >::type
42 	{
43 	public :
44 		//! Action which must be performed as result of operation on repository.
45 		enum class action_t
46 			{
47 				//! Nothing to do. Stop operation is not started.
48 				do_nothing,
49 				//! Stop operation is started but can't be finished right now.
50 				wait_for_completion,
51 				//! Stop operation must be finished.
52 				do_actual_stop
53 			};
54 
stop_guard_repository_t()55 		stop_guard_repository_t()
56 			{}
57 		stop_guard_repository_t( const stop_guard_repository_t & ) = delete;
58 		stop_guard_repository_t( stop_guard_repository_t && ) = delete;
59 
60 		//! Setup new stop_guard.
61 		/*!
62 		 * \note
63 		 * Uniqueness of stop_guard is not checked. It means that the same
64 		 * stop_guard can be added to repository several times.
65 		 *
66 		 * \retval stop_guard_t::setup_result_t::ok if stop is not in progress
67 		 * and new stop_guard has been set up successfully.
68 		 * \retval stop_guard_t::setup_result_t::stop_already_in_progress new
69 		 * stop_guard is not set because stop is already in progress.
70 		 */
71 		stop_guard_t::setup_result_t
setup_guard(stop_guard_shptr_t guard)72 		setup_guard( stop_guard_shptr_t guard )
73 			{
74 				return this->lock_and_perform( [&] {
75 					auto ret_value = stop_guard_t::setup_result_t::ok;
76 					if( status_t::not_started != m_status )
77 						ret_value = stop_guard_t::setup_result_t::stop_already_in_progress;
78 					else
79 						{
80 							auto it = std::lower_bound(
81 									std::begin(m_guards), std::end(m_guards), guard );
82 							m_guards.insert( it, std::move(guard) );
83 						}
84 
85 					return ret_value;
86 				} );
87 			}
88 
89 		//! Remove stop_guard.
90 		/*!
91 		 * \retval action_t::do_nothing there is no stop in progress and nothing
92 		 * need to be done.
93 		 * \retval action_t::wait_for_completion there is stop in progress but
94 		 * there are also other stop_guards which do not complete their
95 		 * actions yet.
96 		 * \retval action_t::do_actual_stop stop operation needs to be completed.
97 		 */
98 		action_t
remove_guard(stop_guard_shptr_t guard)99 		remove_guard( stop_guard_shptr_t guard ) noexcept
100 			{
101 				return so_5::details::invoke_noexcept_code( [&] {
102 					return this->lock_and_perform( [&] {
103 						auto ret_value = action_t::do_nothing;
104 
105 						const auto it = std::lower_bound(
106 								std::begin(m_guards),
107 								std::end(m_guards),
108 								guard );
109 						if( it != std::end(m_guards) && *it == guard )
110 							m_guards.erase(it);
111 
112 						if( status_t::started == m_status )
113 							ret_value = m_guards.empty() ?
114 									action_t::do_actual_stop :
115 									action_t::wait_for_completion;
116 
117 						return ret_value;
118 					} );
119 				} );
120 			}
121 
122 		//! Initiate stop operation.
123 		/*!
124 		 * \note
125 		 * It is safe to call this method when stop is already in progress.
126 		 *
127 		 * \retval action_t::wait_for_completion there are some stop_guards
128 		 * which do not complete their actions yet.
129 		 * \retval action_t::do_actual_stop stop operation needs to be completed.
130 		 */
131 		action_t
initiate_stop()132 		initiate_stop() noexcept
133 			{
134 				return so_5::details::invoke_noexcept_code( [&] {
135 					auto ret_value = action_t::wait_for_completion;
136 
137 					// Copy of actual guards at this moment.
138 					guards_container_t guards;
139 
140 					// The first stage: change status and take a copy
141 					// of actual guards list if necessary.
142 					this->lock_and_perform( [&] {
143 						if( status_t::not_started == m_status )
144 							{
145 								// The stop operation is not started yet.
146 								m_status = status_t::start_in_progress;
147 								guards = m_guards;
148 							}
149 					} );
150 
151 					// The second stage: calling stop() for all stop_guards.
152 					// If guards is empty then nothing will be called.
153 					for( auto & g : guards )
154 						g->stop();
155 
156 					// The third stage: check for possibitility to complete
157 					// the stop operation right now.
158 					// It is possible that all stop_guards will be removed
159 					// on the second stage.
160 					this->lock_and_perform( [&] {
161 						if( m_guards.empty() )
162 							{
163 								m_status = status_t::completed;
164 								ret_value = action_t::do_actual_stop;
165 							}
166 						else
167 							// The stop operation is started. We must wait
168 							// while all live stop_guards finish their work.
169 							m_status = status_t::started;
170 					} );
171 
172 					return ret_value;
173 				} );
174 			}
175 
176 	private :
177 		//! Status of the stop operation.
178 		enum class status_t
179 			{
180 				not_started,
181 				start_in_progress,
182 				started,
183 				completed
184 			};
185 
186 		//! The current status of the stop operation
187 		status_t m_status = status_t::not_started;
188 
189 		//! Type of the stop_guards list.
190 		using guards_container_t = std::vector< stop_guard_shptr_t >;
191 
192 		//! List of actual stop_guards.
193 		guards_container_t m_guards;
194 	};
195 
196 } /* namespace impl */
197 
198 } /* namespace so_5 */
199 
200