| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) | ||
| 3 | // | ||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
| 6 | // | ||
| 7 | // Official repository: https://github.com/cppalliance/capy | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef BOOST_CAPY_EXECUTOR_HPP | ||
| 11 | #define BOOST_CAPY_EXECUTOR_HPP | ||
| 12 | |||
| 13 | #include <boost/capy/detail/config.hpp> | ||
| 14 | #include <boost/capy/detail/call_traits.hpp> | ||
| 15 | #include <boost/capy/async_op.hpp> | ||
| 16 | #include <boost/system/result.hpp> | ||
| 17 | #include <cstddef> | ||
| 18 | #include <exception> | ||
| 19 | #include <memory> | ||
| 20 | #include <new> | ||
| 21 | #include <type_traits> | ||
| 22 | #include <utility> | ||
| 23 | |||
| 24 | namespace boost { | ||
| 25 | namespace capy { | ||
| 26 | |||
| 27 | #if 0 | ||
| 28 | class execution_context | ||
| 29 | { | ||
| 30 | public: | ||
| 31 | private: | ||
| 32 | void post(work* w); | ||
| 33 | }; | ||
| 34 | #endif | ||
| 35 | |||
| 36 | /** A lightweight handle for submitting work to an execution context. | ||
| 37 | |||
| 38 | This class provides a value-type interface for submitting | ||
| 39 | work to be executed asynchronously. It supports two modes: | ||
| 40 | |||
| 41 | @li **Reference mode**: Non-owning reference to an execution | ||
| 42 | context. The caller must ensure the context outlives all | ||
| 43 | executors that reference it. Created via the constructor. | ||
| 44 | |||
| 45 | @li **Owning mode**: Shared ownership of a value-type executor. | ||
| 46 | The executor is stored internally and its lifetime is | ||
| 47 | managed automatically. Created via the `wrap()` factory. | ||
| 48 | |||
| 49 | @par Thread Safety | ||
| 50 | Distinct objects may be accessed concurrently. Shared objects | ||
| 51 | require external synchronization. | ||
| 52 | |||
| 53 | @par Implementing an Execution Context | ||
| 54 | |||
| 55 | Both execution contexts (for reference mode) and value-type | ||
| 56 | executors (for owning mode) must declare | ||
| 57 | `friend struct executor::access` and provide three private | ||
| 58 | member functions: | ||
| 59 | |||
| 60 | @li `void* allocate(std::size_t size, std::size_t align)` — | ||
| 61 | Allocate storage for a work item. May throw. | ||
| 62 | |||
| 63 | @li `void deallocate(void* p, std::size_t size, std::size_t align)` — | ||
| 64 | Free storage previously returned by allocate. Must not throw. | ||
| 65 | |||
| 66 | @li `void submit(executor::work* w)` — | ||
| 67 | Take ownership of the work item and arrange for execution. | ||
| 68 | The context must eventually call `w->invoke()`, then | ||
| 69 | `w->~work()`, then deallocate the storage. | ||
| 70 | |||
| 71 | All three functions must be safe to call concurrently. | ||
| 72 | |||
| 73 | @par Example (Reference Mode) | ||
| 74 | @code | ||
| 75 | class my_pool | ||
| 76 | { | ||
| 77 | friend struct executor::access; | ||
| 78 | |||
| 79 | std::mutex mutex_; | ||
| 80 | std::queue<executor::work*> queue_; | ||
| 81 | |||
| 82 | public: | ||
| 83 | void run_one() | ||
| 84 | { | ||
| 85 | executor::work* w = nullptr; | ||
| 86 | { | ||
| 87 | std::lock_guard<std::mutex> lock(mutex_); | ||
| 88 | if(!queue_.empty()) | ||
| 89 | { | ||
| 90 | w = queue_.front(); | ||
| 91 | queue_.pop(); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | if(w) | ||
| 95 | { | ||
| 96 | w->invoke(); | ||
| 97 | std::size_t size = w->size; | ||
| 98 | std::size_t align = w->align; | ||
| 99 | w->~work(); | ||
| 100 | deallocate(w, size, align); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | private: | ||
| 105 | void* allocate(std::size_t size, std::size_t) | ||
| 106 | { | ||
| 107 | return std::malloc(size); | ||
| 108 | } | ||
| 109 | |||
| 110 | void deallocate(void* p, std::size_t, std::size_t) | ||
| 111 | { | ||
| 112 | std::free(p); | ||
| 113 | } | ||
| 114 | |||
| 115 | void submit(executor::work* w) | ||
| 116 | { | ||
| 117 | std::lock_guard<std::mutex> lock(mutex_); | ||
| 118 | queue_.push(w); | ||
| 119 | } | ||
| 120 | }; | ||
| 121 | |||
| 122 | // Usage: reference mode | ||
| 123 | my_pool pool; | ||
| 124 | executor ex(pool); // pool must outlive ex | ||
| 125 | @endcode | ||
| 126 | |||
| 127 | @par Example (Owning Mode) | ||
| 128 | @code | ||
| 129 | struct my_strand | ||
| 130 | { | ||
| 131 | friend struct executor::access; | ||
| 132 | |||
| 133 | // ... internal state ... | ||
| 134 | |||
| 135 | private: | ||
| 136 | void* allocate(std::size_t size, std::size_t) | ||
| 137 | { | ||
| 138 | return std::malloc(size); | ||
| 139 | } | ||
| 140 | |||
| 141 | void deallocate(void* p, std::size_t, std::size_t) | ||
| 142 | { | ||
| 143 | std::free(p); | ||
| 144 | } | ||
| 145 | |||
| 146 | void submit(executor::work* w) | ||
| 147 | { | ||
| 148 | // ... queue and serialize work ... | ||
| 149 | } | ||
| 150 | }; | ||
| 151 | |||
| 152 | // Usage: owning mode | ||
| 153 | executor ex = executor::from(my_strand{}); // executor owns the strand | ||
| 154 | @endcode | ||
| 155 | */ | ||
| 156 | class executor | ||
| 157 | { | ||
| 158 | struct ops; | ||
| 159 | |||
| 160 | template<class T> | ||
| 161 | struct ops_for; | ||
| 162 | |||
| 163 | template<class Exec> | ||
| 164 | struct holder; | ||
| 165 | |||
| 166 | std::shared_ptr<const ops> ops_; | ||
| 167 | void* obj_; | ||
| 168 | |||
| 169 | public: | ||
| 170 | /** Abstract base for type-erased work. | ||
| 171 | |||
| 172 | Implementations derive from this to wrap callable | ||
| 173 | objects for submission through the executor. | ||
| 174 | |||
| 175 | @par Lifecycle | ||
| 176 | |||
| 177 | When work is submitted via an executor: | ||
| 178 | @li Storage is allocated via the context's allocate() | ||
| 179 | @li A work-derived object is constructed in place | ||
| 180 | @li Ownership transfers to the context via submit() | ||
| 181 | @li The context calls invoke() to execute the work | ||
| 182 | @li The context destroys and deallocates the work | ||
| 183 | |||
| 184 | @note Work objects must not be copied or moved after | ||
| 185 | construction. They are always destroyed in place. | ||
| 186 | |||
| 187 | @note Execution contexts are responsible for tracking | ||
| 188 | the size and alignment of allocated work objects for | ||
| 189 | deallocation. A common pattern is to prepend metadata | ||
| 190 | to the allocation. | ||
| 191 | */ | ||
| 192 | struct BOOST_SYMBOL_VISIBLE work | ||
| 193 | { | ||
| 194 | 120 | virtual ~work() = default; | |
| 195 | virtual void invoke() = 0; | ||
| 196 | }; | ||
| 197 | |||
| 198 | class factory; | ||
| 199 | |||
| 200 | /** Accessor for execution context private members. | ||
| 201 | |||
| 202 | Execution contexts should declare this as a friend to | ||
| 203 | allow the executor machinery to call their private | ||
| 204 | allocate, deallocate, and submit members: | ||
| 205 | |||
| 206 | @code | ||
| 207 | class my_context | ||
| 208 | { | ||
| 209 | friend struct executor::access; | ||
| 210 | // ... | ||
| 211 | private: | ||
| 212 | void* allocate(std::size_t, std::size_t); | ||
| 213 | void deallocate(void*, std::size_t, std::size_t); | ||
| 214 | void submit(executor::work*); | ||
| 215 | }; | ||
| 216 | @endcode | ||
| 217 | */ | ||
| 218 | struct access | ||
| 219 | { | ||
| 220 | template<class T> | ||
| 221 | static void* | ||
| 222 | 84 | allocate(T& ctx, std::size_t size, std::size_t align) | |
| 223 | { | ||
| 224 | 84 | return ctx.allocate(size, align); | |
| 225 | } | ||
| 226 | |||
| 227 | template<class T> | ||
| 228 | static void | ||
| 229 | 2 | deallocate(T& ctx, void* p, std::size_t size, std::size_t align) | |
| 230 | { | ||
| 231 | 2 | ctx.deallocate(p, size, align); | |
| 232 | 2 | } | |
| 233 | |||
| 234 | template<class T> | ||
| 235 | static void | ||
| 236 | 82 | submit(T& ctx, work* w) | |
| 237 | { | ||
| 238 | 82 | ctx.submit(w); | |
| 239 | 82 | } | |
| 240 | }; | ||
| 241 | |||
| 242 | /** Construct an executor referencing an execution context. | ||
| 243 | |||
| 244 | Creates an executor in reference mode. The executor holds | ||
| 245 | a non-owning reference to the context. | ||
| 246 | |||
| 247 | The implementation type must provide: | ||
| 248 | - `void* allocate(std::size_t size, std::size_t align)` | ||
| 249 | - `void deallocate(void* p, std::size_t size, std::size_t align)` | ||
| 250 | - `void submit(executor::work* w)` | ||
| 251 | |||
| 252 | @param ctx The execution context to reference. | ||
| 253 | The context must outlive this executor and all copies. | ||
| 254 | |||
| 255 | @see from | ||
| 256 | */ | ||
| 257 | template< | ||
| 258 | class T, | ||
| 259 | class = typename std::enable_if< | ||
| 260 | !std::is_same< | ||
| 261 | typename std::decay<T>::type, | ||
| 262 | executor>::value>::type> | ||
| 263 | executor(T& ctx) noexcept; | ||
| 264 | |||
| 265 | /** Constructor | ||
| 266 | |||
| 267 | Default-constructed executors are empty. | ||
| 268 | */ | ||
| 269 | 17 | executor() noexcept | |
| 270 | 17 | : ops_() | |
| 271 | 17 | , obj_(nullptr) | |
| 272 | { | ||
| 273 | 17 | } | |
| 274 | |||
| 275 | /** Create an executor with shared ownership of a value-type executor. | ||
| 276 | |||
| 277 | Creates an executor in owning mode. The provided executor | ||
| 278 | is moved into shared storage and its lifetime is managed | ||
| 279 | automatically via reference counting. | ||
| 280 | |||
| 281 | The executor type must provide: | ||
| 282 | - `void* allocate(std::size_t size, std::size_t align)` | ||
| 283 | - `void deallocate(void* p, std::size_t size, std::size_t align)` | ||
| 284 | - `void submit(executor::work* w)` | ||
| 285 | |||
| 286 | @param ex The executor to wrap (moved). | ||
| 287 | |||
| 288 | @return An executor that shares ownership of the wrapped executor. | ||
| 289 | |||
| 290 | @par Example | ||
| 291 | @code | ||
| 292 | // Wrap a value-type executor | ||
| 293 | executor ex = executor::wrap(my_strand{}); | ||
| 294 | |||
| 295 | // Copies share ownership (reference counted) | ||
| 296 | executor exec2 = ex; // both reference the same strand | ||
| 297 | @endcode | ||
| 298 | */ | ||
| 299 | template<class Exec> | ||
| 300 | static executor | ||
| 301 | wrap(Exec ex); | ||
| 302 | |||
| 303 | /** Return true if the executor references an execution context. | ||
| 304 | */ | ||
| 305 | explicit | ||
| 306 | 22 | operator bool() const noexcept | |
| 307 | { | ||
| 308 | 22 | return ops_ != nullptr; | |
| 309 | } | ||
| 310 | |||
| 311 | /** Submit work for execution (fire-and-forget). | ||
| 312 | |||
| 313 | This overload uses the allocation-aware factory | ||
| 314 | mechanism, allowing the implementation to control | ||
| 315 | memory allocation strategy. | ||
| 316 | |||
| 317 | @param f The callable to execute. | ||
| 318 | */ | ||
| 319 | template<class F> | ||
| 320 | void | ||
| 321 | post(F&& f) const; | ||
| 322 | |||
| 323 | /** Submit work and invoke a handler on completion. | ||
| 324 | |||
| 325 | The work function is executed asynchronously. When it | ||
| 326 | completes, the handler is invoked with the result or | ||
| 327 | any exception that was thrown. | ||
| 328 | |||
| 329 | The handler must be invocable with the signature: | ||
| 330 | @code | ||
| 331 | void handler( system::result<T, std::exception_ptr> ); | ||
| 332 | @endcode | ||
| 333 | where `T` is the return type of `f`. | ||
| 334 | |||
| 335 | @param f The work function to execute. | ||
| 336 | |||
| 337 | @param handler The completion handler invoked with | ||
| 338 | the result or exception. | ||
| 339 | */ | ||
| 340 | template<class F, class Handler> | ||
| 341 | auto | ||
| 342 | submit(F&& f, Handler&& handler) const -> | ||
| 343 | typename std::enable_if<! std::is_void< | ||
| 344 | typename detail::call_traits<typename | ||
| 345 | std::decay<F>::type>::return_type>::value>::type; | ||
| 346 | |||
| 347 | /** Submit work and invoke a handler on completion. | ||
| 348 | |||
| 349 | The work function is executed asynchronously. When it | ||
| 350 | completes, the handler is invoked with success or any | ||
| 351 | exception that was thrown. | ||
| 352 | |||
| 353 | The handler must be invocable with the signature: | ||
| 354 | @code | ||
| 355 | void handler( system::result<void, std::exception_ptr> ); | ||
| 356 | @endcode | ||
| 357 | |||
| 358 | @param f The work function to execute. | ||
| 359 | |||
| 360 | @param handler The completion handler invoked with | ||
| 361 | the result or exception. | ||
| 362 | */ | ||
| 363 | template<class F, class Handler> | ||
| 364 | auto | ||
| 365 | submit(F&& f, Handler&& handler) const -> | ||
| 366 | typename std::enable_if<std::is_void<typename | ||
| 367 | detail::call_traits<typename std::decay<F>::type | ||
| 368 | >::return_type>::value>::type; | ||
| 369 | |||
| 370 | #ifdef BOOST_CAPY_HAS_CORO | ||
| 371 | |||
| 372 | /** Submit work and return an awaitable result. | ||
| 373 | |||
| 374 | The work function is executed asynchronously. The | ||
| 375 | returned async_op can be awaited in a coroutine | ||
| 376 | to obtain the result. | ||
| 377 | |||
| 378 | @param f The work function to execute. | ||
| 379 | |||
| 380 | @return An awaitable that produces the result of the work. | ||
| 381 | */ | ||
| 382 | template<class F> | ||
| 383 | auto | ||
| 384 | submit(F&& f) const -> | ||
| 385 | async_op<std::invoke_result_t<std::decay_t<F>>> | ||
| 386 | requires (!std::is_void_v<std::invoke_result_t<std::decay_t<F>>>); | ||
| 387 | |||
| 388 | /** Submit work and return an awaitable result. | ||
| 389 | |||
| 390 | The work function is executed asynchronously. The returned | ||
| 391 | async_op can be awaited in a coroutine to wait | ||
| 392 | for completion. | ||
| 393 | |||
| 394 | @param f The work function to execute. | ||
| 395 | |||
| 396 | @return An awaitable that completes when the work finishes. | ||
| 397 | */ | ||
| 398 | template<class F> | ||
| 399 | auto | ||
| 400 | submit(F&& f) const -> | ||
| 401 | async_op<void> | ||
| 402 | requires std::is_void_v<std::invoke_result_t<std::decay_t<F>>>; | ||
| 403 | |||
| 404 | #endif | ||
| 405 | }; | ||
| 406 | |||
| 407 | //----------------------------------------------------------------------------- | ||
| 408 | |||
| 409 | /** Static vtable for type-erased executor operations. | ||
| 410 | */ | ||
| 411 | struct executor::ops | ||
| 412 | { | ||
| 413 | void* (*allocate)(void* obj, std::size_t size, std::size_t align); | ||
| 414 | void (*deallocate)(void* obj, void* p, std::size_t size, std::size_t align); | ||
| 415 | void (*submit)(void* obj, work* w); | ||
| 416 | }; | ||
| 417 | |||
| 418 | /** Type-specific operation implementations. | ||
| 419 | |||
| 420 | For each concrete type T, this provides static functions | ||
| 421 | that cast the void* back to T* and forward via access. | ||
| 422 | */ | ||
| 423 | template<class T> | ||
| 424 | struct executor::ops_for | ||
| 425 | { | ||
| 426 | static void* | ||
| 427 | 68 | allocate(void* obj, std::size_t size, std::size_t align) | |
| 428 | { | ||
| 429 | 68 | return access::allocate(*static_cast<T*>(obj), size, align); | |
| 430 | } | ||
| 431 | |||
| 432 | static void | ||
| 433 | 2 | deallocate(void* obj, void* p, std::size_t size, std::size_t align) | |
| 434 | { | ||
| 435 | 2 | access::deallocate(*static_cast<T*>(obj), p, size, align); | |
| 436 | 2 | } | |
| 437 | |||
| 438 | static void | ||
| 439 | 66 | submit(void* obj, work* w) | |
| 440 | { | ||
| 441 | 66 | access::submit(*static_cast<T*>(obj), w); | |
| 442 | 66 | } | |
| 443 | |||
| 444 | static constexpr ops table = { | ||
| 445 | &allocate, | ||
| 446 | &deallocate, | ||
| 447 | &submit | ||
| 448 | }; | ||
| 449 | }; | ||
| 450 | |||
| 451 | template<class T> | ||
| 452 | constexpr executor::ops executor::ops_for<T>::table; | ||
| 453 | |||
| 454 | //----------------------------------------------------------------------------- | ||
| 455 | |||
| 456 | /** Holder for value-type executors in owning mode. | ||
| 457 | |||
| 458 | Stores the executor by value and provides the vtable | ||
| 459 | implementation that forwards to the held executor. | ||
| 460 | */ | ||
| 461 | template<class Exec> | ||
| 462 | struct executor::holder | ||
| 463 | { | ||
| 464 | Exec ex; | ||
| 465 | |||
| 466 | explicit | ||
| 467 | 11 | holder(Exec e) | |
| 468 | 11 | : ex(std::move(e)) | |
| 469 | { | ||
| 470 | 11 | } | |
| 471 | |||
| 472 | static void* | ||
| 473 | 8 | allocate(void* obj, std::size_t size, std::size_t align) | |
| 474 | { | ||
| 475 | 8 | return access::allocate( | |
| 476 | 8 | static_cast<holder*>(obj)->ex, size, align); | |
| 477 | } | ||
| 478 | |||
| 479 | static void | ||
| 480 | ✗ | deallocate(void* obj, void* p, std::size_t size, std::size_t align) | |
| 481 | { | ||
| 482 | ✗ | access::deallocate( | |
| 483 | ✗ | static_cast<holder*>(obj)->ex, p, size, align); | |
| 484 | ✗ | } | |
| 485 | |||
| 486 | static void | ||
| 487 | 8 | submit(void* obj, work* w) | |
| 488 | { | ||
| 489 | 8 | access::submit( | |
| 490 | 8 | static_cast<holder*>(obj)->ex, w); | |
| 491 | 8 | } | |
| 492 | |||
| 493 | static constexpr ops table = { | ||
| 494 | &allocate, | ||
| 495 | &deallocate, | ||
| 496 | &submit | ||
| 497 | }; | ||
| 498 | }; | ||
| 499 | |||
| 500 | template<class Exec> | ||
| 501 | constexpr executor::ops executor::holder<Exec>::table; | ||
| 502 | |||
| 503 | //----------------------------------------------------------------------------- | ||
| 504 | |||
| 505 | namespace detail { | ||
| 506 | |||
| 507 | // Null deleter for shared_ptr pointing to static storage | ||
| 508 | struct null_deleter | ||
| 509 | { | ||
| 510 | 30 | void operator()(const void*) const noexcept {} | |
| 511 | }; | ||
| 512 | |||
| 513 | } // detail | ||
| 514 | |||
| 515 | template<class T, class> | ||
| 516 | 46 | executor:: | |
| 517 | executor(T& ctx) noexcept | ||
| 518 | 46 | : ops_( | |
| 519 | &ops_for<typename std::decay<T>::type>::table, | ||
| 520 | detail::null_deleter()) | ||
| 521 | 46 | , obj_(const_cast<void*>(static_cast<void const*>(std::addressof(ctx)))) | |
| 522 | { | ||
| 523 | 46 | } | |
| 524 | |||
| 525 | template<class Exec> | ||
| 526 | executor | ||
| 527 | 11 | executor:: | |
| 528 | wrap(Exec ex0) | ||
| 529 | { | ||
| 530 | typedef typename std::decay<Exec>::type exec_type; | ||
| 531 | typedef holder<exec_type> holder_type; | ||
| 532 | |||
| 533 |
1/1✓ Branch 1 taken 11 times.
|
11 | std::shared_ptr<holder_type> h = |
| 534 | 11 | std::make_shared<holder_type>(std::move(ex0)); | |
| 535 | |||
| 536 | 11 | executor ex; | |
| 537 | // Use aliasing constructor: share ownership with h, | ||
| 538 | // but point to the static vtable | ||
| 539 | 11 | ex.ops_ = std::shared_ptr<const ops>(h, &holder_type::table); | |
| 540 | 11 | ex.obj_ = h.get(); | |
| 541 | 22 | return ex; | |
| 542 | 11 | } | |
| 543 | |||
| 544 | //----------------------------------------------------------------------------- | ||
| 545 | |||
| 546 | /** RAII factory for constructing and submitting work. | ||
| 547 | |||
| 548 | This class manages the multi-phase process of: | ||
| 549 | 1. Allocating storage from the executor implementation | ||
| 550 | 2. Constructing work in-place via placement-new | ||
| 551 | 3. Submitting the work for execution | ||
| 552 | |||
| 553 | If an exception occurs before commit(), the destructor | ||
| 554 | will clean up any allocated resources. | ||
| 555 | |||
| 556 | @par Exception Safety | ||
| 557 | Strong guarantee. If any operation throws, all resources | ||
| 558 | are properly released. | ||
| 559 | */ | ||
| 560 | class executor::factory | ||
| 561 | { | ||
| 562 | ops const* ops_; | ||
| 563 | void* obj_; | ||
| 564 | void* storage_; | ||
| 565 | std::size_t size_; | ||
| 566 | std::size_t align_; | ||
| 567 | bool committed_; | ||
| 568 | |||
| 569 | public: | ||
| 570 | /** Construct a factory bound to an executor. | ||
| 571 | |||
| 572 | @param ex The executor to submit work to. | ||
| 573 | */ | ||
| 574 | explicit | ||
| 575 | 61 | factory(executor const& ex) noexcept | |
| 576 | 61 | : ops_(ex.ops_.get()) | |
| 577 | 61 | , obj_(ex.obj_) | |
| 578 | 61 | , storage_(nullptr) | |
| 579 | 61 | , size_(0) | |
| 580 | 61 | , align_(0) | |
| 581 | 61 | , committed_(false) | |
| 582 | { | ||
| 583 | 61 | } | |
| 584 | |||
| 585 | /** Destructor. Releases resources if not committed. | ||
| 586 | */ | ||
| 587 | 61 | ~factory() | |
| 588 | { | ||
| 589 |
3/4✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 60 times.
|
61 | if(storage_ && !committed_) |
| 590 | 1 | ops_->deallocate(obj_, storage_, size_, align_); | |
| 591 | 61 | } | |
| 592 | |||
| 593 | factory(factory const&) = delete; | ||
| 594 | factory& operator=(factory const&) = delete; | ||
| 595 | |||
| 596 | /** Allocate storage for work of given size and alignment. | ||
| 597 | |||
| 598 | @param size The size in bytes required. | ||
| 599 | @param align The alignment required. | ||
| 600 | @return Pointer to uninitialized storage. | ||
| 601 | */ | ||
| 602 | void* | ||
| 603 | 61 | allocate(std::size_t size, std::size_t align) | |
| 604 | { | ||
| 605 | 61 | storage_ = ops_->allocate(obj_, size, align); | |
| 606 | 61 | size_ = size; | |
| 607 | 61 | align_ = align; | |
| 608 | 61 | return storage_; | |
| 609 | } | ||
| 610 | |||
| 611 | /** Submit constructed work for execution. | ||
| 612 | |||
| 613 | After calling commit(), the factory releases ownership | ||
| 614 | and the destructor becomes a no-op. | ||
| 615 | |||
| 616 | @param w Pointer to the constructed work object | ||
| 617 | (must reside in the allocated storage). | ||
| 618 | */ | ||
| 619 | void | ||
| 620 | 60 | commit(work* w) | |
| 621 | { | ||
| 622 | 60 | committed_ = true; | |
| 623 | 60 | ops_->submit(obj_, w); | |
| 624 | 60 | } | |
| 625 | }; | ||
| 626 | |||
| 627 | //----------------------------------------------------------------------------- | ||
| 628 | |||
| 629 | template<class F> | ||
| 630 | void | ||
| 631 | 118 | executor:: | |
| 632 | post(F&& f) const | ||
| 633 | { | ||
| 634 | struct callable : work | ||
| 635 | { | ||
| 636 | typename std::decay<F>::type f_; | ||
| 637 | |||
| 638 | explicit | ||
| 639 | 59 | callable(F&& f) | |
| 640 | 59 | : f_(std::forward<F>(f)) | |
| 641 | { | ||
| 642 | 59 | } | |
| 643 | |||
| 644 | void | ||
| 645 | 59 | invoke() override | |
| 646 | { | ||
| 647 | 59 | f_(); | |
| 648 | 59 | } | |
| 649 | }; | ||
| 650 | |||
| 651 | 118 | factory fac(*this); | |
| 652 |
1/1✓ Branch 1 taken 59 times.
|
118 | void* p = fac.allocate(sizeof(callable), alignof(callable)); |
| 653 | 118 | callable* w = ::new(p) callable(std::forward<F>(f)); | |
| 654 |
1/1✓ Branch 1 taken 59 times.
|
118 | fac.commit(w); |
| 655 | 118 | } | |
| 656 | |||
| 657 | //----------------------------------------------------------------------------- | ||
| 658 | |||
| 659 | template<class F, class Handler> | ||
| 660 | auto | ||
| 661 | 7 | executor:: | |
| 662 | submit(F&& f, Handler&& handler) const -> | ||
| 663 | typename std::enable_if<! std::is_void<typename | ||
| 664 | detail::call_traits<typename std::decay<F>::type | ||
| 665 | >::return_type>::value>::type | ||
| 666 | { | ||
| 667 | using T = typename detail::call_traits< | ||
| 668 | typename std::decay<F>::type>::return_type; | ||
| 669 | using result_type = system::result<T, std::exception_ptr>; | ||
| 670 | |||
| 671 | struct callable | ||
| 672 | { | ||
| 673 | typename std::decay<F>::type f; | ||
| 674 | typename std::decay<Handler>::type handler; | ||
| 675 | |||
| 676 | 4 | void operator()() | |
| 677 | { | ||
| 678 | try | ||
| 679 | { | ||
| 680 |
1/1✓ Branch 3 taken 1 times.
|
4 | handler(result_type(f())); |
| 681 | } | ||
| 682 | 1 | catch(...) | |
| 683 | { | ||
| 684 |
0/1✗ Branch 3 not taken.
|
1 | handler(result_type(std::current_exception())); |
| 685 | } | ||
| 686 | 4 | } | |
| 687 | }; | ||
| 688 | |||
| 689 |
1/1✓ Branch 3 taken 4 times.
|
7 | post(callable{std::forward<F>(f), std::forward<Handler>(handler)}); |
| 690 | 7 | } | |
| 691 | |||
| 692 | template<class F, class Handler> | ||
| 693 | auto | ||
| 694 | 2 | executor:: | |
| 695 | submit(F&& f, Handler&& handler) const -> | ||
| 696 | typename std::enable_if<std::is_void<typename | ||
| 697 | detail::call_traits<typename std::decay<F>::type | ||
| 698 | >::return_type>::value>::type | ||
| 699 | { | ||
| 700 | using result_type = system::result<void, std::exception_ptr>; | ||
| 701 | |||
| 702 | struct callable | ||
| 703 | { | ||
| 704 | typename std::decay<F>::type f; | ||
| 705 | typename std::decay<Handler>::type handler; | ||
| 706 | |||
| 707 | 2 | void operator()() | |
| 708 | { | ||
| 709 | try | ||
| 710 | { | ||
| 711 | 2 | f(); | |
| 712 | 2 | handler(result_type()); | |
| 713 | } | ||
| 714 | catch(...) | ||
| 715 | { | ||
| 716 | handler(result_type(std::current_exception())); | ||
| 717 | } | ||
| 718 | 2 | } | |
| 719 | }; | ||
| 720 | |||
| 721 |
1/1✓ Branch 3 taken 2 times.
|
2 | post(callable{std::forward<F>(f), std::forward<Handler>(handler)}); |
| 722 | 2 | } | |
| 723 | |||
| 724 | #ifdef BOOST_CAPY_HAS_CORO | ||
| 725 | |||
| 726 | template<class F> | ||
| 727 | auto | ||
| 728 | executor:: | ||
| 729 | submit(F&& f) const -> | ||
| 730 | async_op<std::invoke_result_t<std::decay_t<F>>> | ||
| 731 | requires (!std::is_void_v<std::invoke_result_t<std::decay_t<F>>>) | ||
| 732 | { | ||
| 733 | using T = std::invoke_result_t<std::decay_t<F>>; | ||
| 734 | |||
| 735 | return make_async_op<T>( | ||
| 736 | [ex = *this, f = std::forward<F>(f)](auto on_done) mutable | ||
| 737 | { | ||
| 738 | ex.post( | ||
| 739 | [f = std::move(f), | ||
| 740 | on_done = std::move(on_done)]() mutable | ||
| 741 | { | ||
| 742 | on_done(f()); | ||
| 743 | }); | ||
| 744 | }); | ||
| 745 | } | ||
| 746 | |||
| 747 | template<class F> | ||
| 748 | auto | ||
| 749 | executor:: | ||
| 750 | submit(F&& f) const -> | ||
| 751 | async_op<void> | ||
| 752 | requires std::is_void_v<std::invoke_result_t<std::decay_t<F>>> | ||
| 753 | { | ||
| 754 | return make_async_op<void>( | ||
| 755 | [ex = *this, f = std::forward<F>(f)](auto on_done) mutable | ||
| 756 | { | ||
| 757 | ex.post( | ||
| 758 | [f = std::move(f), | ||
| 759 | on_done = std::move(on_done)]() mutable | ||
| 760 | { | ||
| 761 | f(); | ||
| 762 | on_done(); | ||
| 763 | }); | ||
| 764 | }); | ||
| 765 | } | ||
| 766 | |||
| 767 | #endif | ||
| 768 | |||
| 769 | } // capy | ||
| 770 | } // boost | ||
| 771 | |||
| 772 | #endif | ||
| 773 |