Skip to content

Commit 4d43adf

Browse files
committed
refactor: introduce scheduler_op to replace execution_context::handler
Add scheduler_op class locally in corosio to decouple from capy's execution_context. The scheduler interface now uses scheduler_op* instead of capy::execution_context::handler*, making the I/O layer self-contained.
1 parent 89a5964 commit 4d43adf

10 files changed

Lines changed: 261 additions & 37 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,7 @@ function(boost_corosio_setup_properties target)
141141
target_compile_features(${target} PUBLIC cxx_std_20)
142142
target_include_directories(${target} PUBLIC "${PROJECT_SOURCE_DIR}/include")
143143
target_include_directories(${target} PRIVATE
144-
"${PROJECT_SOURCE_DIR}/src/corosio"
145-
"${PROJECT_SOURCE_DIR}/src/corosio/src")
144+
"${PROJECT_SOURCE_DIR}/src/corosio")
146145
target_link_libraries(${target}
147146
PUBLIC
148147
${BOOST_COROSIO_DEPENDENCIES}

include/boost/corosio/detail/scheduler.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@
1212

1313
#include <boost/corosio/detail/config.hpp>
1414
#include <boost/capy/ex/any_coro.hpp>
15-
#include <boost/capy/ex/execution_context.hpp>
1615

1716
#include <cstddef>
1817

1918
namespace boost {
2019
namespace corosio {
2120
namespace detail {
2221

22+
class scheduler_op;
23+
2324
struct scheduler
2425
{
2526
virtual ~scheduler() = default;
2627
virtual void post(capy::any_coro) const = 0;
27-
virtual void post(capy::execution_context::handler*) const = 0;
28+
virtual void post(scheduler_op*) const = 0;
2829
virtual void on_work_started() noexcept = 0;
2930
virtual void on_work_finished() noexcept = 0;
3031
virtual bool running_in_this_thread() const noexcept = 0;

src/corosio/src/detail/posix_op.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
#include <boost/capy/concept/affine_awaitable.hpp>
1717
#include <boost/capy/ex/any_coro.hpp>
1818
#include <boost/capy/error.hpp>
19-
#include <boost/capy/ex/execution_context.hpp>
2019
#include <boost/system/error_code.hpp>
2120

21+
#include "detail/scheduler_op.hpp"
22+
2223
#include <unistd.h>
2324
#include <errno.h>
2425

@@ -42,7 +43,7 @@ namespace detail {
4243
It stores the coroutine handle, dispatcher, and result
4344
pointers needed to complete an async operation.
4445
*/
45-
struct posix_op : capy::execution_context::handler
46+
struct posix_op : scheduler_op
4647
{
4748
struct canceller
4849
{
@@ -136,7 +137,7 @@ struct posix_op : capy::execution_context::handler
136137
};
137138

138139
inline posix_op*
139-
get_posix_op(capy::execution_context::handler* h) noexcept
140+
get_posix_op(scheduler_op* h) noexcept
140141
{
141142
return static_cast<posix_op*>(h->data());
142143
}

src/corosio/src/detail/posix_scheduler.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ posix_scheduler::
135135
post(capy::any_coro h) const
136136
{
137137
struct post_handler final
138-
: capy::execution_context::handler
138+
: scheduler_op
139139
{
140140
capy::any_coro h_;
141141

@@ -172,7 +172,7 @@ post(capy::any_coro h) const
172172

173173
void
174174
posix_scheduler::
175-
post(capy::execution_context::handler* h) const
175+
post(scheduler_op* h) const
176176
{
177177
outstanding_work_.fetch_add(1, std::memory_order_relaxed);
178178

@@ -411,7 +411,7 @@ do_one(long timeout_us)
411411
return 0;
412412

413413
// First check if there are handlers in the queue
414-
capy::execution_context::handler* h = nullptr;
414+
scheduler_op* h = nullptr;
415415
{
416416
std::lock_guard lock(mutex_);
417417
h = completed_ops_.pop();

src/corosio/src/detail/posix_scheduler.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
#include <boost/corosio/detail/config.hpp>
1414
#include <boost/corosio/detail/scheduler.hpp>
1515
#include <boost/capy/ex/execution_context.hpp>
16-
#include <boost/capy/core/intrusive_queue.hpp>
16+
17+
#include "src/detail/scheduler_op.hpp"
1718

1819
#include <atomic>
1920
#include <chrono>
@@ -25,8 +26,6 @@ namespace boost {
2526
namespace corosio {
2627
namespace detail {
2728

28-
using op_queue = capy::intrusive_queue<capy::execution_context::handler>;
29-
3029
// Forward declaration
3130
struct posix_op;
3231

@@ -68,7 +67,7 @@ class posix_scheduler
6867

6968
void shutdown() override;
7069
void post(capy::any_coro h) const override;
71-
void post(capy::execution_context::handler* h) const override;
70+
void post(scheduler_op* h) const override;
7271
void on_work_started() noexcept override;
7372
void on_work_finished() noexcept override;
7473
bool running_in_this_thread() const noexcept override;
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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/corosio
8+
//
9+
10+
#ifndef BOOST_COROSIO_DETAIL_SCHEDULER_OP_HPP
11+
#define BOOST_COROSIO_DETAIL_SCHEDULER_OP_HPP
12+
13+
#include <boost/corosio/detail/config.hpp>
14+
#include <boost/capy/core/intrusive_queue.hpp>
15+
16+
namespace boost {
17+
namespace corosio {
18+
namespace detail {
19+
20+
/** Abstract base class for completion handlers.
21+
22+
Handlers are continuations that execute after an asynchronous
23+
operation completes. They can be queued for deferred invocation,
24+
allowing callbacks and coroutine resumptions to be posted to an
25+
executor.
26+
27+
Handlers should execute quickly - typically just initiating
28+
another I/O operation or suspending on a foreign task. Heavy
29+
computation should be avoided in handlers to prevent blocking
30+
the event loop.
31+
32+
Handlers may be heap-allocated or may be data members of an
33+
enclosing object. The allocation strategy is determined by the
34+
creator of the handler.
35+
36+
@par Ownership Contract
37+
38+
Callers must invoke exactly ONE of `operator()` or `destroy()`,
39+
never both:
40+
41+
@li `operator()` - Invokes the handler. The handler is
42+
responsible for its own cleanup (typically `delete this`
43+
for heap-allocated handlers). The caller must not call
44+
`destroy()` after invoking this.
45+
46+
@li `destroy()` - Destroys an uninvoked handler. This is
47+
called when a queued handler must be discarded without
48+
execution, such as during shutdown or exception cleanup.
49+
For heap-allocated handlers, this typically calls
50+
`delete this`.
51+
52+
@par Exception Safety
53+
54+
Implementations of `operator()` must perform cleanup before
55+
any operation that might throw. This ensures that if the handler
56+
throws, the exception propagates cleanly to the caller of
57+
`run()` without leaking resources. Typical pattern:
58+
59+
@code
60+
void operator()() override
61+
{
62+
auto h = h_;
63+
delete this; // cleanup FIRST
64+
h.resume(); // then resume (may throw)
65+
}
66+
@endcode
67+
68+
This "delete-before-invoke" pattern also enables memory
69+
recycling - the handler's memory can be reused immediately
70+
by subsequent allocations.
71+
72+
@note Callers must never delete handlers directly with `delete`;
73+
use `operator()` for normal invocation or `destroy()` for cleanup.
74+
75+
@note Heap-allocated handlers are typically allocated with
76+
custom allocators to minimize allocation overhead in
77+
high-frequency async operations.
78+
79+
@note Some handlers (such as those owned by containers like
80+
`std::unique_ptr` or embedded as data members) are not meant to
81+
be destroyed and should implement both functions as no-ops
82+
(for `operator()`, invoke the continuation but don't delete).
83+
84+
@see scheduler_op_queue
85+
*/
86+
class scheduler_op : public capy::intrusive_queue<scheduler_op>::node
87+
{
88+
public:
89+
virtual void operator()() = 0;
90+
virtual void destroy() = 0;
91+
92+
/** Returns the user-defined data pointer.
93+
94+
Derived classes may set this to store auxiliary data
95+
such as a pointer to the most-derived object.
96+
97+
@par Postconditions
98+
@li Initially returns `nullptr` for newly constructed handlers.
99+
@li Returns the current value of `data_` if modified by a derived class.
100+
101+
@return The user-defined data pointer, or `nullptr` if not set.
102+
*/
103+
void* data() const noexcept
104+
{
105+
return data_;
106+
}
107+
108+
protected:
109+
~scheduler_op() = default;
110+
111+
void* data_ = nullptr;
112+
};
113+
114+
//------------------------------------------------------------------------------
115+
116+
using op_queue = capy::intrusive_queue<scheduler_op>;
117+
118+
//------------------------------------------------------------------------------
119+
120+
/** An intrusive FIFO queue of scheduler_ops.
121+
122+
This queue stores scheduler_ops using an intrusive linked list,
123+
avoiding additional allocations for queue nodes. Scheduler_ops
124+
are popped in the order they were pushed (first-in, first-out).
125+
126+
The destructor calls `destroy()` on any remaining scheduler_ops.
127+
128+
@note This is not thread-safe. External synchronization is
129+
required for concurrent access.
130+
131+
@see scheduler_op
132+
*/
133+
class scheduler_op_queue
134+
{
135+
op_queue q_;
136+
137+
public:
138+
/** Default constructor.
139+
140+
Creates an empty queue.
141+
142+
@post `empty() == true`
143+
*/
144+
scheduler_op_queue() = default;
145+
146+
/** Move constructor.
147+
148+
Takes ownership of all scheduler_ops from `other`,
149+
leaving `other` empty.
150+
151+
@param other The queue to move from.
152+
153+
@post `other.empty() == true`
154+
*/
155+
scheduler_op_queue(scheduler_op_queue&& other) noexcept
156+
: q_(std::move(other.q_))
157+
{
158+
}
159+
160+
scheduler_op_queue(scheduler_op_queue const&) = delete;
161+
scheduler_op_queue& operator=(scheduler_op_queue const&) = delete;
162+
scheduler_op_queue& operator=(scheduler_op_queue&&) = delete;
163+
164+
/** Destructor.
165+
166+
Calls `destroy()` on any remaining scheduler_ops in the queue.
167+
*/
168+
~scheduler_op_queue()
169+
{
170+
while(auto* h = q_.pop())
171+
h->destroy();
172+
}
173+
174+
/** Return true if the queue is empty.
175+
176+
@return `true` if the queue contains no scheduler_ops.
177+
*/
178+
bool
179+
empty() const noexcept
180+
{
181+
return q_.empty();
182+
}
183+
184+
/** Add a scheduler_op to the back of the queue.
185+
186+
@param h Pointer to the scheduler_op to add.
187+
188+
@pre `h` is not null and not already in a queue.
189+
*/
190+
void
191+
push(scheduler_op* h) noexcept
192+
{
193+
q_.push(h);
194+
}
195+
196+
/** Splice all scheduler_ops from another queue to the back.
197+
198+
All scheduler_ops from `other` are moved to the back of this
199+
queue. After this call, `other` is empty.
200+
201+
@param other The queue to splice from.
202+
203+
@post `other.empty() == true`
204+
*/
205+
void
206+
push(scheduler_op_queue& other) noexcept
207+
{
208+
q_.splice(other.q_);
209+
}
210+
211+
/** Remove and return the front scheduler_op.
212+
213+
@return Pointer to the front scheduler_op, or `nullptr`
214+
if the queue is empty.
215+
*/
216+
scheduler_op*
217+
pop() noexcept
218+
{
219+
return q_.pop();
220+
}
221+
};
222+
223+
} // namespace detail
224+
} // namespace corosio
225+
} // namespace boost
226+
227+
#endif

0 commit comments

Comments
 (0)