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