Lightweight 0.20260921.0
Loading...
Searching...
No Matches
StrandExecutor.hpp
1// SPDX-License-Identifier: Apache-2.0
2#pragma once
3
4#include "../Api.hpp"
5#include "Executor.hpp"
6#include "detail/ExecutorQueues.hpp"
7
8#include <memory>
9
10namespace Lightweight::Async
11{
12
13/// @ingroup Async
14/// Serializes work over an underlying executor (an Asio-style "strand").
15///
16/// All work posted to a given strand runs one item at a time, in FIFO order, even though
17/// the items execute on the underlying executor's worker threads. This guarantees that a
18/// single ODBC connection — which is not safe for concurrent use — is touched by only one
19/// thread at a time. A strand does not own a thread; it borrows the underlying executor.
20///
21/// The strand's mutable state lives in a heap @c State held by a @c std::shared_ptr. Each
22/// in-flight drain closure keeps a copy of that pointer, so the state outlives the
23/// @c StrandExecutor wrapper itself until the last drain returns. This makes the strand safe
24/// to destroy (or to replace, e.g. via @c SqlConnection::EnableAsync) while a drain is still
25/// running on a worker thread — the closure only ever touches @c State, never the wrapper.
26class LIGHTWEIGHT_API StrandExecutor final: public IExecutor, public IResumeScheduler
27{
28 public:
29 /// Constructs a strand layered over @p underlying.
30 ///
31 /// @param underlying The executor that actually runs the serialized work.
32 explicit StrandExecutor(IExecutor& underlying);
33
34 StrandExecutor(StrandExecutor const&) = delete;
35 StrandExecutor& operator=(StrandExecutor const&) = delete;
37 StrandExecutor& operator=(StrandExecutor&&) = delete;
38 ~StrandExecutor() override = default;
39
40 void Post(Work work) override;
41 void Resume(std::coroutine_handle<> handle) override;
42
43 private:
44 /// Mutable strand state, heap-allocated so in-flight drain closures can keep it alive
45 /// independently of the @c StrandExecutor wrapper's lifetime. The serialized FIFO and its
46 /// drain-active flag live in @ref detail::SerialDrainQueue, which guards both under one lock.
47 struct State
48 {
49 IExecutor& underlying; ///< Borrowed executor that runs the serialized work.
50 detail::SerialDrainQueue queue;
51
52 explicit State(IExecutor& underlyingExecutor) noexcept:
53 underlying { underlyingExecutor }
54 {
55 }
56 };
57
58 /// Schedules a single drain closure on @p state->underlying that drains @p state->pending
59 /// to completion. The closure captures a copy of @p state, keeping it alive while it runs.
60 static void ScheduleDrain(std::shared_ptr<State> state);
61
62 std::shared_ptr<State> _state;
63};
64
65} // namespace Lightweight::Async
StrandExecutor(IExecutor &underlying)
void Resume(std::coroutine_handle<> handle) override
void Post(Work work) override