Repository navigation
Expand file tree
/
Copy pathio_operation.hpp
More file actions
427 lines (362 loc) · 16.1 KB
/
Copy pathio_operation.hpp
File metadata and controls
427 lines (362 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the file
LICENSE in the distribution; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
// Private operation I/O contract of the v3 native engine (architecture
// §3.4, DR-V3-004, TASK-099). Private io_operation objects describe
// accept, receive (read), send (write), timer, wake, and cancellation
// completions with storage alive through completion. One connection has
// one io_connection_owner; a backend completion wins exactly one
// terminal claim on the op state and hands the record to that owner,
// which serializes application on the connection's executor -- backend
// delivery order never defines protocol order.
//
// Lifetime contract: every object here travels by shared_ptr. Backends
// and owners never touch raw op addresses, so destroying an operation
// handle after submission is memory-safe: a pending awaiter is
// uninstalled by the awaiter destructor, and a posted resumption is
// witness-guarded. The owner and the io_backend passed to submit() must
// outlive every handle that references them.
//
// Vocabulary is http::outcome_code only (REQ-037); misuse (double
// submit, double await, stop armed without a backend) is reported as
// std::logic_error or a typed invalid_state outcome, never as an OS
// error. No OS-socket vocabulary appears in this layer; real socket
// backends arrive with TASK-100.
#if !defined(HTTPSERVER_COMPILATION)
#error "io_operation.hpp is internal; only reachable when compiling libhttpserver."
#endif
#ifndef SRC_HTTPSERVER_DETAIL_IO_OPERATION_HPP_
#define SRC_HTTPSERVER_DETAIL_IO_OPERATION_HPP_
#include <atomic>
#include <chrono>
#include <coroutine>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <span>
#include <stdexcept>
#include <stop_token>
#include <utility>
#include <variant>
#include <httpserver/concurrency/executor.hpp>
#include <httpserver/concurrency/task.hpp>
#include <httpserver/http/outcome.hpp>
#include <httpserver/net/address.hpp>
#include <httpserver/detail/io_datagram.hpp>
namespace httpserver {
namespace detail {
class io_connection_owner;
class op_state;
class op_handle;
class op_awaiter;
// Raw completion kinds, followed by controls intercepted by the TLS adapter.
enum class io_op_kind : std::uint8_t {
accept,
read,
write,
timer,
wake,
cancel,
tls_handshake,
tls_shutdown,
udp_receive,
udp_send,
};
constexpr bool is_tls_control(io_op_kind kind) noexcept {
return kind == io_op_kind::tls_handshake || kind == io_op_kind::tls_shutdown;
}
constexpr bool is_udp(io_op_kind kind) noexcept {
return kind == io_op_kind::udp_receive || kind == io_op_kind::udp_send;
}
// One terminal result. The vocabulary is http::outcome_code only.
struct io_result {
http::outcome_code code = http::outcome_code::ok;
std::size_t transferred = 0; // read/write: bytes; other kinds: 0
std::uint64_t accepted_id = 0; // accept only: fabricated connection id
// Accept only (TASK-119): the accepted transport's peer snapshot,
// captured at accept; unspec when the platform reported no
// address or the completion is another kind.
net::peer_address peer;
std::shared_ptr<const io_datagram> datagram;
};
// Per-kind payload carried inside op_state. Stream spans borrow caller memory
// until the terminal result; UDP payloads own their packet storage.
// TLS consumes read/write deadlines; raw backends use timer_operation.
struct udp_payload {
std::shared_ptr<io_datagram> packet;
bool valid = true;
std::size_t storage_bytes = 0; // immutable admission charge through terminal claim
};
struct accept_payload { };
struct read_payload {
std::span<std::byte> buffer;
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::time_point::max();
};
struct write_payload {
std::span<const std::byte> bytes;
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::time_point::max();
};
struct timer_payload {
std::chrono::steady_clock::time_point deadline;
};
struct wake_payload { };
struct cancel_payload {
std::shared_ptr<op_state> target;
};
// IWYU note: kept on one line; a wrapped namespace-scope continuation trips cpplint.
using op_payload = std::variant<accept_payload, read_payload, write_payload, timer_payload, wake_payload, cancel_payload, udp_payload>;
// Shared, lifetime-safe terminal state of one operation. Exactly-once
// termination is enforced by claim_terminal(): duplicate completions,
// late cancels, and close-after-complete all lose the CAS and become
// no-ops. This is the single enforcement point of the whole layer.
class op_state final : public std::enable_shared_from_this<op_state> {
public:
op_state(io_op_kind kind, io_connection_owner* owner,
std::uint64_t connection, op_payload payload);
op_state(const op_state&) = delete;
op_state& operator=(const op_state&) = delete;
io_op_kind kind() const noexcept { return kind_; }
io_connection_owner* owner() const noexcept { return owner_; }
std::uint64_t connection() const noexcept { return connection_; }
const op_payload& payload() const noexcept { return payload_; }
// Submission order as metadata, bound by the backend at submit.
// Monotonic per backend; never defines delivery order.
std::uint64_t sequence() const noexcept {
return sequence_.load(std::memory_order_acquire);
}
void set_sequence(std::uint64_t sequence) noexcept {
sequence_.store(sequence, std::memory_order_release);
}
// One-shot submission/await/consumption flags (CAS-backed so they
// survive handle moves). False means the flag was already taken.
bool mark_submitted() noexcept {
return try_flag(submitted_);
}
bool mark_awaited() noexcept {
return try_flag(awaited_);
}
// The single enforcement point: first terminal claimant wins.
bool claim_terminal() noexcept;
bool is_terminal() const noexcept {
return terminal_.load(std::memory_order_acquire);
}
// Waiter plumbing (used by op_awaiter). arm_waiter installs the
// suspended frame under the state mutex; false means a completion
// already applied (the caller completes via symmetric transfer).
bool arm_waiter(std::coroutine_handle<> frame,
const std::shared_ptr<frame_witness>& witness,
executor* resume_ex);
void disarm_waiter() noexcept;
// Owner path: stores the decided result, marks applied, and resumes
// the armed waiter (posted to its executor unless already current;
// witness-guarded so a destroyed frame turns the job into a no-op).
void apply(io_result result);
bool applied() const noexcept {
return applied_.load(std::memory_order_acquire);
}
// Precondition: applied().
io_result stored_result() const;
private:
static bool try_flag(std::atomic<bool>& flag) noexcept;
const io_op_kind kind_;
io_connection_owner* const owner_;
const std::uint64_t connection_;
op_payload payload_;
std::atomic<bool> submitted_{false};
std::atomic<bool> awaited_{false};
std::atomic<bool> terminal_{false};
std::atomic<bool> applied_{false};
std::atomic<std::uint64_t> sequence_{0};
mutable std::mutex mu_; // guards the waiter slot below
io_result result_{};
std::coroutine_handle<> frame_{};
std::shared_ptr<frame_witness> witness_;
executor* resume_ex_ = nullptr;
};
// Abstract backend seam (TASK-100's poll/WSAPoll drivers implement it;
// TASK-124/126's readiness adapter subclasses it without rework).
// Deliberately minimal: readiness/interest APIs are later-task surface.
class io_backend {
public:
virtual ~io_backend() = default;
// Registers a freshly submitted operation. The backend binds the
// op's sequence and takes shared ownership of the state.
virtual void submit(op_state& op) = 0;
// Attempts to cancel @p target. Returns the outcome the canceling
// side reports: ok when the target was delivered cancelled,
// invalid_state when it had already reached a terminal result some
// other way, connection_closed when the backend is closed.
virtual http::outcome_code request_cancel(op_state& target) = 0;
protected:
io_backend() = default;
private:
io_backend(const io_backend&) = delete;
io_backend& operator=(const io_backend&) = delete;
};
// Awaiter produced by op_handle::operator co_await. Frame-resident: its
// destructor disarms the waiter, so destroying the awaiting task after
// submission races a concurrent completion safely (the applied flag
// keeps delivery exactly once and the frame witness keeps any in-flight
// posted resumption a no-op).
class op_awaiter final {
public:
explicit op_awaiter(std::shared_ptr<op_state> state) noexcept
: state_(std::move(state)) { }
// Awaiter pre-bound to the awaiting task frame (see
// op_handle::bind_frame). The op's waiter then arms with the
// frame's witness and executor.
op_awaiter(std::shared_ptr<op_state> state,
task_frame_base* frame) noexcept;
op_awaiter(const op_awaiter&) = delete;
op_awaiter& operator=(const op_awaiter&) = delete;
op_awaiter(op_awaiter&&) = delete;
op_awaiter& operator=(op_awaiter&&) = delete;
op_awaiter& bind_frame(task_frame_base* frame) noexcept;
// A terminal-and-applied operation completes inline on the awaiting
// frame's own thread. A claimed-but-not-yet-applied operation
// suspends, so per-connection effect order == resumption order.
bool await_ready() const noexcept {
return state_->applied();
}
std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting);
// Precondition: applied().
io_result await_resume() const {
return state_->stored_result();
}
~op_awaiter();
private:
std::shared_ptr<op_state> state_;
std::shared_ptr<frame_witness> witness_;
executor* resume_ex_ = nullptr;
};
// Common machinery of the six typed operation handles: non-copyable,
// movable, one owner (connection) fixed at construction, payload owned
// until the terminal result. submit() is one-shot; operator co_await is
// single-consumer; both violations are std::logic_error.
class op_handle {
public:
// Installed by the task promise's await_transform when the handle
// itself is the co_await operand (`co_await std::move(op)`): the
// produced awaiter then arms the waiter with this frame's witness
// and executor, so a late posted resumption of a finished frame is
// a guarded no-op instead of a raw resume.
op_handle& bind_frame(task_frame_base* frame) noexcept;
// One-shot: binds the backend and hands the op state over. A second
// submit is a std::logic_error (task's single-consumer discipline).
void submit(io_backend& backend);
// Arms cancellation: registers a std::stop_callback that calls
// backend.request_cancel(*state). The terminal claim CAS resolves
// the race with a concurrent backend completion; std::stop_callback's
// blocking destructor gives the same destroy-during-callback safety
// cancellation.hpp relies on. Arming after the op reached a terminal
// result is a no-op. Requires a prior submit().
void arm_stop(std::stop_token token);
// Single-consumer await; a second await is a std::logic_error.
// Unqualified on purpose: the task promise's await_transform binds
// the frame and returns the handle as an lvalue, so the awaiter
// extraction must accept it (the one-shot mark still enforces the
// single consumer at runtime).
op_awaiter operator co_await();
io_op_kind kind() const noexcept { return state_->kind(); }
std::uint64_t connection() const noexcept { return state_->connection(); }
std::uint64_t sequence() const noexcept { return state_->sequence(); }
bool is_terminal() const noexcept { return state_->is_terminal(); }
// White-box seam for the detail unit tests (and the backend
// completion path of tests that drive op_state directly).
const std::shared_ptr<op_state>& state() const noexcept {
return state_;
}
protected:
explicit op_handle(std::shared_ptr<op_state> state) noexcept
: state_(std::move(state)) { }
op_handle(op_handle&&) noexcept = default;
op_handle& operator=(op_handle&&) noexcept = default;
~op_handle() = default;
private:
op_handle(const op_handle&) = delete;
op_handle& operator=(const op_handle&) = delete;
friend class cancel_operation;
std::shared_ptr<op_state> state_;
io_backend* backend_ = nullptr;
task_frame_base* frame_ = nullptr; // awaiting frame, when bound
// Type-erased holder for the heap-allocated std::stop_callback
// (stop_callback itself is neither copyable nor movable).
std::shared_ptr<void> stop_registration_;
};
// The six typed, owned operations (DR-V3-004).
class accept_operation final : public op_handle {
public:
accept_operation(io_connection_owner& owner, std::uint64_t connection);
};
class read_operation final : public op_handle {
public:
read_operation(io_connection_owner& owner, std::uint64_t connection,
std::span<std::byte> buffer,
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::time_point::max());
std::span<std::byte> buffer() const noexcept {
return std::get<read_payload>(state()->payload()).buffer;
}
};
class write_operation final : public op_handle {
public:
write_operation(io_connection_owner& owner, std::uint64_t connection,
std::span<const std::byte> bytes,
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::time_point::max());
std::span<const std::byte> bytes() const noexcept {
return std::get<write_payload>(state()->payload()).bytes;
}
};
// UDP storage is owned, unlike the stream spans. Invalid sizes allocate no
// packet bytes and are completed with limit_exceeded at backend admission.
class udp_receive_operation final : public op_handle {
public:
udp_receive_operation(io_connection_owner& owner, std::uint64_t socket,
std::size_t capacity = k_max_datagram_bytes);
private:
static op_payload make_payload(std::size_t capacity);
};
class udp_send_operation final : public op_handle {
public:
udp_send_operation(io_connection_owner& owner, std::uint64_t socket,
std::span<const std::byte> bytes, datagram_endpoint peer);
private:
static op_payload make_payload(std::span<const std::byte> bytes, datagram_endpoint peer);
};
class timer_operation final : public op_handle {
public:
timer_operation(io_connection_owner& owner, std::uint64_t connection,
std::chrono::steady_clock::time_point deadline);
std::chrono::steady_clock::time_point deadline() const noexcept {
return std::get<timer_payload>(state()->payload()).deadline;
}
};
class wake_operation final : public op_handle {
public:
wake_operation(io_connection_owner& owner, std::uint64_t connection);
};
class cancel_operation final : public op_handle {
public:
cancel_operation(io_connection_owner& owner, std::uint64_t connection,
const op_handle& target);
std::shared_ptr<op_state> target() const noexcept {
return std::get<cancel_payload>(state()->payload()).target;
}
};
} // namespace detail
} // namespace httpserver
#endif // SRC_HTTPSERVER_DETAIL_IO_OPERATION_HPP_