1 //
2 // detail/impl/strand_executor_service.hpp
3 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2021 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6 //
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
9 //
10
11 #ifndef BOOST_ASIO_DETAIL_IMPL_STRAND_EXECUTOR_SERVICE_HPP
12 #define BOOST_ASIO_DETAIL_IMPL_STRAND_EXECUTOR_SERVICE_HPP
13
14 #if defined(_MSC_VER) && (_MSC_VER >= 1200)
15 # pragma once
16 #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
17
18 #include <boost/asio/detail/fenced_block.hpp>
19 #include <boost/asio/detail/handler_invoke_helpers.hpp>
20 #include <boost/asio/detail/recycling_allocator.hpp>
21 #include <boost/asio/executor_work_guard.hpp>
22 #include <boost/asio/defer.hpp>
23 #include <boost/asio/dispatch.hpp>
24 #include <boost/asio/post.hpp>
25
26 #include <boost/asio/detail/push_options.hpp>
27
28 namespace boost {
29 namespace asio {
30 namespace detail {
31
32 template <typename F, typename Allocator>
33 class strand_executor_service::allocator_binder
34 {
35 public:
36 typedef Allocator allocator_type;
37
allocator_binder(BOOST_ASIO_MOVE_ARG (F)f,const Allocator & a)38 allocator_binder(BOOST_ASIO_MOVE_ARG(F) f, const Allocator& a)
39 : f_(BOOST_ASIO_MOVE_CAST(F)(f)),
40 allocator_(a)
41 {
42 }
43
allocator_binder(const allocator_binder & other)44 allocator_binder(const allocator_binder& other)
45 : f_(other.f_),
46 allocator_(other.allocator_)
47 {
48 }
49
50 #if defined(BOOST_ASIO_HAS_MOVE)
allocator_binder(allocator_binder && other)51 allocator_binder(allocator_binder&& other)
52 : f_(BOOST_ASIO_MOVE_CAST(F)(other.f_)),
53 allocator_(BOOST_ASIO_MOVE_CAST(allocator_type)(other.allocator_))
54 {
55 }
56 #endif // defined(BOOST_ASIO_HAS_MOVE)
57
get_allocator() const58 allocator_type get_allocator() const BOOST_ASIO_NOEXCEPT
59 {
60 return allocator_;
61 }
62
operator ()()63 void operator()()
64 {
65 f_();
66 }
67
68 private:
69 F f_;
70 allocator_type allocator_;
71 };
72
73 template <typename Executor>
74 class strand_executor_service::invoker<Executor,
75 typename enable_if<
76 execution::is_executor<Executor>::value
77 >::type>
78 {
79 public:
invoker(const implementation_type & impl,Executor & ex)80 invoker(const implementation_type& impl, Executor& ex)
81 : impl_(impl),
82 executor_(boost::asio::prefer(ex, execution::outstanding_work.tracked))
83 {
84 }
85
invoker(const invoker & other)86 invoker(const invoker& other)
87 : impl_(other.impl_),
88 executor_(other.executor_)
89 {
90 }
91
92 #if defined(BOOST_ASIO_HAS_MOVE)
invoker(invoker && other)93 invoker(invoker&& other)
94 : impl_(BOOST_ASIO_MOVE_CAST(implementation_type)(other.impl_)),
95 executor_(BOOST_ASIO_MOVE_CAST(executor_type)(other.executor_))
96 {
97 }
98 #endif // defined(BOOST_ASIO_HAS_MOVE)
99
100 struct on_invoker_exit
101 {
102 invoker* this_;
103
~on_invoker_exitboost::asio::detail::strand_executor_service::invoker::on_invoker_exit104 ~on_invoker_exit()
105 {
106 if (push_waiting_to_ready(this_->impl_))
107 {
108 recycling_allocator<void> allocator;
109 execution::execute(
110 boost::asio::prefer(
111 boost::asio::require(this_->executor_,
112 execution::blocking.never),
113 execution::allocator(allocator)),
114 BOOST_ASIO_MOVE_CAST(invoker)(*this_));
115 }
116 }
117 };
118
operator ()()119 void operator()()
120 {
121 // Ensure the next handler, if any, is scheduled on block exit.
122 on_invoker_exit on_exit = { this };
123 (void)on_exit;
124
125 run_ready_handlers(impl_);
126 }
127
128 private:
129 typedef typename decay<
130 typename prefer_result<
131 Executor,
132 execution::outstanding_work_t::tracked_t
133 >::type
134 >::type executor_type;
135
136 implementation_type impl_;
137 executor_type executor_;
138 };
139
140 #if !defined(BOOST_ASIO_NO_TS_EXECUTORS)
141
142 template <typename Executor>
143 class strand_executor_service::invoker<Executor,
144 typename enable_if<
145 !execution::is_executor<Executor>::value
146 >::type>
147 {
148 public:
invoker(const implementation_type & impl,Executor & ex)149 invoker(const implementation_type& impl, Executor& ex)
150 : impl_(impl),
151 work_(ex)
152 {
153 }
154
invoker(const invoker & other)155 invoker(const invoker& other)
156 : impl_(other.impl_),
157 work_(other.work_)
158 {
159 }
160
161 #if defined(BOOST_ASIO_HAS_MOVE)
invoker(invoker && other)162 invoker(invoker&& other)
163 : impl_(BOOST_ASIO_MOVE_CAST(implementation_type)(other.impl_)),
164 work_(BOOST_ASIO_MOVE_CAST(executor_work_guard<Executor>)(other.work_))
165 {
166 }
167 #endif // defined(BOOST_ASIO_HAS_MOVE)
168
169 struct on_invoker_exit
170 {
171 invoker* this_;
172
~on_invoker_exitboost::asio::detail::strand_executor_service::invoker::on_invoker_exit173 ~on_invoker_exit()
174 {
175 if (push_waiting_to_ready(this_->impl_))
176 {
177 Executor ex(this_->work_.get_executor());
178 recycling_allocator<void> allocator;
179 ex.post(BOOST_ASIO_MOVE_CAST(invoker)(*this_), allocator);
180 }
181 }
182 };
183
operator ()()184 void operator()()
185 {
186 // Ensure the next handler, if any, is scheduled on block exit.
187 on_invoker_exit on_exit = { this };
188 (void)on_exit;
189
190 run_ready_handlers(impl_);
191 }
192
193 private:
194 implementation_type impl_;
195 executor_work_guard<Executor> work_;
196 };
197
198 #endif // !defined(BOOST_ASIO_NO_TS_EXECUTORS)
199
200 template <typename Executor, typename Function>
execute(const implementation_type & impl,Executor & ex,BOOST_ASIO_MOVE_ARG (Function)function,typename enable_if<can_query<Executor,execution::allocator_t<void>>::value>::type *)201 inline void strand_executor_service::execute(const implementation_type& impl,
202 Executor& ex, BOOST_ASIO_MOVE_ARG(Function) function,
203 typename enable_if<
204 can_query<Executor, execution::allocator_t<void> >::value
205 >::type*)
206 {
207 return strand_executor_service::do_execute(impl, ex,
208 BOOST_ASIO_MOVE_CAST(Function)(function),
209 boost::asio::query(ex, execution::allocator));
210 }
211
212 template <typename Executor, typename Function>
execute(const implementation_type & impl,Executor & ex,BOOST_ASIO_MOVE_ARG (Function)function,typename enable_if<!can_query<Executor,execution::allocator_t<void>>::value>::type *)213 inline void strand_executor_service::execute(const implementation_type& impl,
214 Executor& ex, BOOST_ASIO_MOVE_ARG(Function) function,
215 typename enable_if<
216 !can_query<Executor, execution::allocator_t<void> >::value
217 >::type*)
218 {
219 return strand_executor_service::do_execute(impl, ex,
220 BOOST_ASIO_MOVE_CAST(Function)(function),
221 std::allocator<void>());
222 }
223
224 template <typename Executor, typename Function, typename Allocator>
do_execute(const implementation_type & impl,Executor & ex,BOOST_ASIO_MOVE_ARG (Function)function,const Allocator & a)225 void strand_executor_service::do_execute(const implementation_type& impl,
226 Executor& ex, BOOST_ASIO_MOVE_ARG(Function) function, const Allocator& a)
227 {
228 typedef typename decay<Function>::type function_type;
229
230 // If the executor is not never-blocking, and we are already in the strand,
231 // then the function can run immediately.
232 if (boost::asio::query(ex, execution::blocking) != execution::blocking.never
233 && running_in_this_thread(impl))
234 {
235 // Make a local, non-const copy of the function.
236 function_type tmp(BOOST_ASIO_MOVE_CAST(Function)(function));
237
238 fenced_block b(fenced_block::full);
239 boost_asio_handler_invoke_helpers::invoke(tmp, tmp);
240 return;
241 }
242
243 // Allocate and construct an operation to wrap the function.
244 typedef executor_op<function_type, Allocator> op;
245 typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
246 p.p = new (p.v) op(BOOST_ASIO_MOVE_CAST(Function)(function), a);
247
248 BOOST_ASIO_HANDLER_CREATION((impl->service_->context(), *p.p,
249 "strand_executor", impl.get(), 0, "execute"));
250
251 // Add the function to the strand and schedule the strand if required.
252 bool first = enqueue(impl, p.p);
253 p.v = p.p = 0;
254 if (first)
255 {
256 execution::execute(ex, invoker<Executor>(impl, ex));
257 }
258 }
259
260 template <typename Executor, typename Function, typename Allocator>
dispatch(const implementation_type & impl,Executor & ex,BOOST_ASIO_MOVE_ARG (Function)function,const Allocator & a)261 void strand_executor_service::dispatch(const implementation_type& impl,
262 Executor& ex, BOOST_ASIO_MOVE_ARG(Function) function, const Allocator& a)
263 {
264 typedef typename decay<Function>::type function_type;
265
266 // If we are already in the strand then the function can run immediately.
267 if (running_in_this_thread(impl))
268 {
269 // Make a local, non-const copy of the function.
270 function_type tmp(BOOST_ASIO_MOVE_CAST(Function)(function));
271
272 fenced_block b(fenced_block::full);
273 boost_asio_handler_invoke_helpers::invoke(tmp, tmp);
274 return;
275 }
276
277 // Allocate and construct an operation to wrap the function.
278 typedef executor_op<function_type, Allocator> op;
279 typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
280 p.p = new (p.v) op(BOOST_ASIO_MOVE_CAST(Function)(function), a);
281
282 BOOST_ASIO_HANDLER_CREATION((impl->service_->context(), *p.p,
283 "strand_executor", impl.get(), 0, "dispatch"));
284
285 // Add the function to the strand and schedule the strand if required.
286 bool first = enqueue(impl, p.p);
287 p.v = p.p = 0;
288 if (first)
289 {
290 boost::asio::dispatch(ex,
291 allocator_binder<invoker<Executor>, Allocator>(
292 invoker<Executor>(impl, ex), a));
293 }
294 }
295
296 // Request invocation of the given function and return immediately.
297 template <typename Executor, typename Function, typename Allocator>
post(const implementation_type & impl,Executor & ex,BOOST_ASIO_MOVE_ARG (Function)function,const Allocator & a)298 void strand_executor_service::post(const implementation_type& impl,
299 Executor& ex, BOOST_ASIO_MOVE_ARG(Function) function, const Allocator& a)
300 {
301 typedef typename decay<Function>::type function_type;
302
303 // Allocate and construct an operation to wrap the function.
304 typedef executor_op<function_type, Allocator> op;
305 typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
306 p.p = new (p.v) op(BOOST_ASIO_MOVE_CAST(Function)(function), a);
307
308 BOOST_ASIO_HANDLER_CREATION((impl->service_->context(), *p.p,
309 "strand_executor", impl.get(), 0, "post"));
310
311 // Add the function to the strand and schedule the strand if required.
312 bool first = enqueue(impl, p.p);
313 p.v = p.p = 0;
314 if (first)
315 {
316 boost::asio::post(ex,
317 allocator_binder<invoker<Executor>, Allocator>(
318 invoker<Executor>(impl, ex), a));
319 }
320 }
321
322 // Request invocation of the given function and return immediately.
323 template <typename Executor, typename Function, typename Allocator>
defer(const implementation_type & impl,Executor & ex,BOOST_ASIO_MOVE_ARG (Function)function,const Allocator & a)324 void strand_executor_service::defer(const implementation_type& impl,
325 Executor& ex, BOOST_ASIO_MOVE_ARG(Function) function, const Allocator& a)
326 {
327 typedef typename decay<Function>::type function_type;
328
329 // Allocate and construct an operation to wrap the function.
330 typedef executor_op<function_type, Allocator> op;
331 typename op::ptr p = { detail::addressof(a), op::ptr::allocate(a), 0 };
332 p.p = new (p.v) op(BOOST_ASIO_MOVE_CAST(Function)(function), a);
333
334 BOOST_ASIO_HANDLER_CREATION((impl->service_->context(), *p.p,
335 "strand_executor", impl.get(), 0, "defer"));
336
337 // Add the function to the strand and schedule the strand if required.
338 bool first = enqueue(impl, p.p);
339 p.v = p.p = 0;
340 if (first)
341 {
342 boost::asio::defer(ex,
343 allocator_binder<invoker<Executor>, Allocator>(
344 invoker<Executor>(impl, ex), a));
345 }
346 }
347
348 } // namespace detail
349 } // namespace asio
350 } // namespace boost
351
352 #include <boost/asio/detail/pop_options.hpp>
353
354 #endif // BOOST_ASIO_DETAIL_IMPL_STRAND_EXECUTOR_SERVICE_HPP
355