opencode-skills-collection 4.0.35 → 4.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +13 -1
- package/bundled-skills/agent-evaluation-reporting/SKILL.md +136 -0
- package/bundled-skills/agent-harness-fault-injection/SKILL.md +250 -0
- package/bundled-skills/anti-ui-slop/SKILL.md +115 -0
- package/bundled-skills/audit-agent-run-evidence/SKILL.md +165 -0
- package/bundled-skills/boost-asio-pro/SKILL.md +172 -0
- package/bundled-skills/boost-asio-pro/references/build.md +88 -0
- package/bundled-skills/boost-asio-pro/references/classic-boost.md +33 -0
- package/bundled-skills/boost-asio-pro/references/coroutines.md +415 -0
- package/bundled-skills/boost-asio-pro/references/pre-cpp20.md +164 -0
- package/bundled-skills/boost-asio-pro/references/ssl.md +38 -0
- package/bundled-skills/cross-platform-contract-propagation-audit/SKILL.md +159 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +1 -1
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/aas-core.md +9 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +3 -3
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/dsh-deepread/SKILL.md +200 -0
- package/bundled-skills/liuguang-banlan-ui/SKILL.md +135 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/favicon.svg +4 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/index.html +32 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/launcher.css +25 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/index.html +179 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/theme-config.js +105 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/theme.css +107 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/index.html +179 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/theme-config.js +105 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/theme.css +107 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/base.css +423 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/spectral-field.js +450 -0
- package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/workbench.js +255 -0
- package/bundled-skills/liuguang-banlan-ui/references/style-contract.md +53 -0
- package/bundled-skills/liuguang-banlan-ui/references/verification.md +70 -0
- package/bundled-skills/liuguang-banlan-ui/scripts/measure_preview.py +144 -0
- package/bundled-skills/liuguang-banlan-ui/scripts/requirements.txt +2 -0
- package/bundled-skills/liuguang-banlan-ui/scripts/scaffold_template.py +43 -0
- package/bundled-skills/liuguang-banlan-ui/scripts/validate_manifest.py +173 -0
- package/bundled-skills/multi-source-search/SKILL.md +139 -0
- package/bundled-skills/multi-source-search/references/report-schema.md +47 -0
- package/bundled-skills/multi-source-search/scripts/validate_report.py +221 -0
- package/bundled-skills/review-multi-agent-orchestration/SKILL.md +201 -0
- package/bundled-skills/ui-slop-score/SKILL.md +80 -0
- package/bundled-skills/using-lwc/LICENSE +201 -0
- package/bundled-skills/using-lwc/README.md +926 -0
- package/bundled-skills/using-lwc/README.zh-CN.md +836 -0
- package/bundled-skills/using-lwc/SKILL.md +156 -0
- package/bundled-skills/using-lwc/agents/openai.yaml +4 -0
- package/bundled-skills/using-lwc/assets/global-purpose.md +17 -0
- package/bundled-skills/using-lwc/assets/global-schema.md +28 -0
- package/bundled-skills/using-lwc/references/active-memory.md +81 -0
- package/bundled-skills/using-lwc/references/agent-onboarding.md +84 -0
- package/bundled-skills/using-lwc/references/code-graph.md +60 -0
- package/bundled-skills/using-lwc/references/core-memory.md +51 -0
- package/bundled-skills/using-lwc/references/document-conversion.md +53 -0
- package/bundled-skills/using-lwc/references/document-graph.md +58 -0
- package/bundled-skills/using-lwc/references/llm-wiki.md +75 -0
- package/bundled-skills/using-lwc/references/memory-policy.md +551 -0
- package/bundled-skills/using-lwc/references/operations-manual.md +503 -0
- package/bundled-skills/using-lwc/references/recovery-maintenance.md +53 -0
- package/bundled-skills/using-lwc/references/strong-context.md +54 -0
- package/bundled-skills/using-lwc/references/trigger-playbook.md +53 -0
- package/bundled-skills/using-lwc/references/word-graph.md +45 -0
- package/bundled-skills/using-lwc/scripts/bootstrap.sh +359 -0
- package/bundled-skills/using-lwc/scripts/install-lwc.sh +172 -0
- package/bundled-skills/youtube-summarizer/SKILL.md +21 -7
- package/bundled-skills/youtube-summarizer/scripts/extract-transcript.py +45 -12
- package/package.json +3 -2
- package/skills_index.json +268 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# C++20 Coroutine Style (Boost ≥ 1.77)
|
|
2
|
+
|
|
3
|
+
The preferred style when the toolchain allows it. Read `SKILL.md` first — the rules there (write queue, buffer lifetime, version floors) apply here and are not repeated.
|
|
4
|
+
|
|
5
|
+
## Core Architecture
|
|
6
|
+
|
|
7
|
+
Boost.Asio uses the **Proactor pattern**: async operations run in the background, completion handlers are invoked with results.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Program → I/O Object → Execution Context → OS → (completion) → Handler
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Execution contexts:** `io_context` (single/multi-thread event loop), `thread_pool`, `system_context`
|
|
14
|
+
|
|
15
|
+
**I/O objects:** `tcp::socket`, `tcp::acceptor`, `udp::socket`, `steady_timer`, `ssl::stream<>`
|
|
16
|
+
|
|
17
|
+
**Completion tokens:** Control how async results are delivered — `use_awaitable`, `deferred` (default), `detached`, callbacks, futures.
|
|
18
|
+
|
|
19
|
+
## C++20 Coroutines (Preferred Style)
|
|
20
|
+
|
|
21
|
+
```cpp
|
|
22
|
+
#include <boost/asio.hpp>
|
|
23
|
+
#include <boost/asio/co_spawn.hpp>
|
|
24
|
+
#include <boost/asio/use_awaitable.hpp>
|
|
25
|
+
|
|
26
|
+
namespace asio = boost::asio;
|
|
27
|
+
using tcp = asio::ip::tcp;
|
|
28
|
+
|
|
29
|
+
asio::awaitable<void> echo_session(tcp::socket socket) {
|
|
30
|
+
try {
|
|
31
|
+
char data[1024];
|
|
32
|
+
for (;;) {
|
|
33
|
+
std::size_t n = co_await socket.async_read_some(asio::buffer(data));
|
|
34
|
+
co_await async_write(socket, asio::buffer(data, n));
|
|
35
|
+
}
|
|
36
|
+
} catch (std::exception&) {
|
|
37
|
+
// Connection closed or error — coroutine ends
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
asio::awaitable<void> listener(tcp::acceptor acceptor) {
|
|
42
|
+
for (;;) {
|
|
43
|
+
auto socket = co_await acceptor.async_accept();
|
|
44
|
+
co_spawn(acceptor.get_executor(), echo_session(std::move(socket)), asio::detached);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
int main() {
|
|
49
|
+
asio::io_context io(1); // concurrency_hint=1 for single-threaded
|
|
50
|
+
tcp::acceptor acceptor(io, {tcp::v4(), 8080});
|
|
51
|
+
co_spawn(io, listener(std::move(acceptor)), asio::detached);
|
|
52
|
+
io.run();
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Key rules:**
|
|
57
|
+
- `co_spawn(executor, coroutine, completion_token)` launches a coroutine
|
|
58
|
+
- Without explicit token, async ops use `deferred` (returns awaitable object for `co_await`)
|
|
59
|
+
- Errors become `system_error` exceptions by default inside coroutines
|
|
60
|
+
- Use `asio::detached` when you don't need the coroutine's result
|
|
61
|
+
|
|
62
|
+
## Error Handling in Coroutines
|
|
63
|
+
|
|
64
|
+
**Default:** Errors throw `boost::system::system_error`.
|
|
65
|
+
|
|
66
|
+
**Explicit error handling with `as_tuple`:**
|
|
67
|
+
```cpp
|
|
68
|
+
auto [ec, n] = co_await socket.async_read_some(
|
|
69
|
+
asio::buffer(data), asio::as_tuple(asio::use_awaitable));
|
|
70
|
+
if (ec) { /* handle error, no exception */ }
|
|
71
|
+
```
|
|
72
|
+
**Wrap, don't use bare `as_tuple`.** Always write `as_tuple(use_awaitable)`. Bare `asio::as_tuple` resolves against the operation's *default* completion token (often `deferred`), which compiles in some contexts but fails in others — wrapping an explicit base token is unambiguous everywhere.
|
|
73
|
+
|
|
74
|
+
**With `redirect_error`:**
|
|
75
|
+
```cpp
|
|
76
|
+
boost::system::error_code ec;
|
|
77
|
+
std::size_t n = co_await socket.async_read_some(
|
|
78
|
+
asio::buffer(data), asio::redirect_error(asio::use_awaitable, ec));
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Strands (Thread Safety)
|
|
82
|
+
|
|
83
|
+
**Rule: All async operations on a shared object MUST execute on the same strand.**
|
|
84
|
+
|
|
85
|
+
```cpp
|
|
86
|
+
// Per-connection strand
|
|
87
|
+
asio::strand<asio::io_context::executor_type> strand(io.get_executor());
|
|
88
|
+
co_spawn(strand, session(std::move(socket)), asio::detached);
|
|
89
|
+
|
|
90
|
+
// Bind handler to strand
|
|
91
|
+
socket.async_read_some(asio::buffer(data),
|
|
92
|
+
asio::bind_executor(strand, [](error_code ec, size_t n) { /*...*/ }));
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Implicit strands (no explicit strand needed):**
|
|
96
|
+
- Single-threaded `io_context::run()` — all handlers are sequential
|
|
97
|
+
- Single chain of async ops on one connection (half-duplex)
|
|
98
|
+
|
|
99
|
+
**Explicit strand required when:**
|
|
100
|
+
- Multiple threads call `io_context::run()`
|
|
101
|
+
- Full-duplex read+write on same socket
|
|
102
|
+
- Shared state accessed from multiple async chains
|
|
103
|
+
|
|
104
|
+
## Full-Duplex: Strand + Write Queue
|
|
105
|
+
|
|
106
|
+
**A strand serializes handler *execution*, NOT whole composed operations.** Two `async_write`s started "concurrently" on the same strand still overlap and **interleave bytes on the wire** — the strand only orders the intermediate handlers, not the byte stream. For full-duplex (a read loop plus pushes/replies writing at the same time on one socket), a strand alone is **not** enough: you must serialize outbound writes yourself with a queue.
|
|
107
|
+
|
|
108
|
+
```cpp
|
|
109
|
+
// Give each accepted socket its OWN strand, then run every chain (read loop,
|
|
110
|
+
// pushes, replies) on that strand. Passing an executor to async_accept means you
|
|
111
|
+
// must ALSO pass an explicit completion token — the default-deferred shortcut on
|
|
112
|
+
// the zero-arg form no longer applies.
|
|
113
|
+
auto socket = co_await acceptor.async_accept(asio::make_strand(io), asio::use_awaitable);
|
|
114
|
+
std::make_shared<connection>(std::move(socket))->start();
|
|
115
|
+
|
|
116
|
+
class connection : public std::enable_shared_from_this<connection> {
|
|
117
|
+
tcp::socket socket_; // bound to its own strand
|
|
118
|
+
std::deque<std::string> outbox_;
|
|
119
|
+
bool writing_ = false;
|
|
120
|
+
public:
|
|
121
|
+
explicit connection(tcp::socket s) : socket_(std::move(s)) {}
|
|
122
|
+
|
|
123
|
+
void start() {
|
|
124
|
+
// Each chain captures `self` so the connection outlives all its coroutines.
|
|
125
|
+
co_spawn(socket_.get_executor(),
|
|
126
|
+
[self = shared_from_this()] { return self->read_loop(); }, asio::detached);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Call ONLY from the connection's strand (e.g. from its own coroutines).
|
|
130
|
+
// From another thread/strand: asio::dispatch(socket_.get_executor(), ...).
|
|
131
|
+
void send(std::string frame) {
|
|
132
|
+
outbox_.push_back(std::move(frame));
|
|
133
|
+
if (!writing_)
|
|
134
|
+
co_spawn(socket_.get_executor(),
|
|
135
|
+
[self = shared_from_this()] { return self->write_loop(); }, asio::detached);
|
|
136
|
+
}
|
|
137
|
+
private:
|
|
138
|
+
asio::awaitable<void> write_loop() {
|
|
139
|
+
writing_ = true;
|
|
140
|
+
while (!outbox_.empty()) {
|
|
141
|
+
co_await async_write(socket_, asio::buffer(outbox_.front()));
|
|
142
|
+
outbox_.pop_front(); // pop only AFTER the write completes
|
|
143
|
+
}
|
|
144
|
+
writing_ = false;
|
|
145
|
+
}
|
|
146
|
+
asio::awaitable<void> read_loop(); // reads frames, calls send() for replies
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Why each rule matters:**
|
|
151
|
+
- One strand per connection → read loop and write loop never run their handlers concurrently.
|
|
152
|
+
- Write queue + `writing_` flag → at most one `async_write` in flight, so frames never interleave.
|
|
153
|
+
- `enable_shared_from_this` + capturing `self` in every `co_spawn` → the connection survives until all of its read/write/timer chains finish.
|
|
154
|
+
- The accepted socket from `async_accept(make_strand(...))` is `basic_stream_socket<tcp, strand<...>>`, **not** `tcp::socket`. Take it **by value** (`connection(tcp::socket s)`, store `tcp::socket socket_`) — the strand executor type-erases into `any_io_executor` on the move. Passing that accepted socket to a `tcp::socket&` (by reference) instead will **fail to compile** — use `auto` or accept by value.
|
|
155
|
+
|
|
156
|
+
**Strand from inside a coroutine** (when `io` isn't a captured local): get the executor from the coroutine and make a strand off it — no `io_context&` needed:
|
|
157
|
+
```cpp
|
|
158
|
+
auto ex = co_await asio::this_coro::executor;
|
|
159
|
+
auto socket = co_await acceptor.async_accept(asio::make_strand(ex), asio::use_awaitable);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Run the read loop and idle watch together** — two `awaitable<void>` branches; don't inspect the result, the first to finish unwinds the other:
|
|
163
|
+
```cpp
|
|
164
|
+
using namespace asio::experimental::awaitable_operators;
|
|
165
|
+
co_await (read_loop() || idle_watch(socket_, timer_)); // either returning tears down the connection
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Stopping a detached side-coroutine** (e.g. a per-symbol ticker that must end on unsubscribe/close): a detached `co_spawn` won't stop itself. Either (a) have its loop re-check a flag each iteration and `co_return` when gone:
|
|
169
|
+
```cpp
|
|
170
|
+
while (subscriptions_.contains(symbol) && socket_.is_open()) {
|
|
171
|
+
timer.expires_after(250ms);
|
|
172
|
+
co_await timer.async_wait(asio::as_tuple(asio::use_awaitable));
|
|
173
|
+
if (/* still subscribed */) send(make_tick(symbol));
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
or (b) spawn it with a `cancellation_signal` and `emit()` cancellation on unsubscribe. The flag approach is simpler for per-subscription tickers.
|
|
177
|
+
|
|
178
|
+
## Timers and Timeouts
|
|
179
|
+
|
|
180
|
+
```cpp
|
|
181
|
+
asio::awaitable<void> with_timeout(tcp::socket& socket) {
|
|
182
|
+
asio::steady_timer timer(co_await asio::this_coro::executor);
|
|
183
|
+
timer.expires_after(std::chrono::seconds(30));
|
|
184
|
+
|
|
185
|
+
// Race: read vs timeout (requires awaitable_operators)
|
|
186
|
+
using namespace asio::experimental::awaitable_operators;
|
|
187
|
+
|
|
188
|
+
auto result = co_await (
|
|
189
|
+
socket.async_read_some(asio::buffer(data), asio::use_awaitable)
|
|
190
|
+
|| timer.async_wait(asio::use_awaitable)
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (result.index() == 0) { /* read completed */ }
|
|
194
|
+
else { /* timeout — cancel the socket */ socket.close(); }
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Re-armable idle timeout** (reset on every received frame — the common server pattern):
|
|
199
|
+
```cpp
|
|
200
|
+
// Run as a long-lived parallel branch. Calling expires_after() again cancels the
|
|
201
|
+
// pending wait, resolving the in-flight async_wait with operation_aborted — that
|
|
202
|
+
// is the signal to keep waiting, NOT an error. Genuine expiry resolves with no error.
|
|
203
|
+
asio::awaitable<void> idle_watch(tcp::socket& sock, asio::steady_timer& timer) {
|
|
204
|
+
for (;;) {
|
|
205
|
+
auto [ec] = co_await timer.async_wait(asio::as_tuple(asio::use_awaitable));
|
|
206
|
+
if (ec == asio::error::operation_aborted) continue; // re-armed → keep waiting
|
|
207
|
+
if (ec) co_return; // timer error
|
|
208
|
+
sock.close(); // real timeout fired
|
|
209
|
+
co_return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// On every frame received from the peer: timer.expires_after(30s);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Parallel operations (`&&` and `||`):**
|
|
216
|
+
```cpp
|
|
217
|
+
#include <boost/asio/experimental/awaitable_operators.hpp>
|
|
218
|
+
using namespace asio::experimental::awaitable_operators;
|
|
219
|
+
|
|
220
|
+
// Wait for both (AND) — cancels other on failure
|
|
221
|
+
auto [read_n, write_n] = co_await (
|
|
222
|
+
async_read(sock, in_buf, use_awaitable) &&
|
|
223
|
+
async_write(sock, out_buf, use_awaitable)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Wait for first (OR) — cancels other on success
|
|
227
|
+
auto result = co_await (
|
|
228
|
+
async_read(sock, buf, use_awaitable) ||
|
|
229
|
+
timer.async_wait(use_awaitable)
|
|
230
|
+
);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Note:** `||` and `&&` operators require explicit `use_awaitable` token, and the `awaitable_operators.hpp` header (Boost ≥ 1.77 — see the version floors in SKILL.md).
|
|
234
|
+
|
|
235
|
+
**Void branches:** when a branch returns `void` (e.g. two `awaitable<void>` chains), that arm contributes `std::monostate` to the result variant. If *both* branches are void the result is `variant<monostate, monostate>` — don't inspect `.index()`; just `co_await` the expression and let whichever finishes first unwind the other.
|
|
236
|
+
|
|
237
|
+
## Cancellation
|
|
238
|
+
|
|
239
|
+
```cpp
|
|
240
|
+
asio::awaitable<void> cancellable_work() {
|
|
241
|
+
// Check cancellation state
|
|
242
|
+
auto cs = co_await asio::this_coro::cancellation_state;
|
|
243
|
+
if (cs.cancelled() != asio::cancellation_type::none) {
|
|
244
|
+
co_return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Enable cancellation types
|
|
248
|
+
co_await asio::this_coro::reset_cancellation_state(
|
|
249
|
+
asio::enable_total_cancellation());
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## TCP Server Pattern
|
|
254
|
+
|
|
255
|
+
```cpp
|
|
256
|
+
asio::awaitable<void> server(asio::io_context& io, unsigned short port) {
|
|
257
|
+
tcp::acceptor acceptor(io, {tcp::v4(), port});
|
|
258
|
+
acceptor.set_option(tcp::acceptor::reuse_address(true));
|
|
259
|
+
|
|
260
|
+
for (;;) {
|
|
261
|
+
auto socket = co_await acceptor.async_accept();
|
|
262
|
+
co_spawn(
|
|
263
|
+
io.get_executor(), // or a strand for multi-threaded
|
|
264
|
+
handle_client(std::move(socket)),
|
|
265
|
+
[](std::exception_ptr ep) {
|
|
266
|
+
if (ep) std::rethrow_exception(ep);
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Buffers
|
|
274
|
+
|
|
275
|
+
| Type | Use |
|
|
276
|
+
|------|-----|
|
|
277
|
+
| `asio::buffer(data, size)` | Wrap existing memory (no ownership) |
|
|
278
|
+
| `asio::dynamic_buffer(vec)` | Growable buffer over `vector`/`string` |
|
|
279
|
+
| `asio::streambuf` | Legacy stream buffer |
|
|
280
|
+
| `asio::const_buffer` | Read-only view |
|
|
281
|
+
| `asio::mutable_buffer` | Writable view |
|
|
282
|
+
|
|
283
|
+
**Critical:** `asio::buffer()` does NOT own memory. The underlying storage must outlive the async operation.
|
|
284
|
+
|
|
285
|
+
## Resolver (DNS)
|
|
286
|
+
|
|
287
|
+
```cpp
|
|
288
|
+
asio::awaitable<void> connect_to(asio::io_context& io,
|
|
289
|
+
std::string host, std::string port) {
|
|
290
|
+
tcp::resolver resolver(io);
|
|
291
|
+
auto endpoints = co_await resolver.async_resolve(host, port);
|
|
292
|
+
|
|
293
|
+
tcp::socket socket(io);
|
|
294
|
+
co_await asio::async_connect(socket, endpoints);
|
|
295
|
+
// socket is now connected
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Multi-Threaded io_context
|
|
300
|
+
|
|
301
|
+
```cpp
|
|
302
|
+
asio::io_context io;
|
|
303
|
+
std::vector<std::thread> threads;
|
|
304
|
+
|
|
305
|
+
for (int i = 0; i < std::thread::hardware_concurrency(); ++i) {
|
|
306
|
+
threads.emplace_back([&io] { io.run(); });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// All handlers MUST be strand-protected when sharing state
|
|
310
|
+
for (auto& t : threads) t.join();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Composed Async Operations (Custom)
|
|
314
|
+
|
|
315
|
+
```cpp
|
|
316
|
+
template <typename CompletionToken>
|
|
317
|
+
auto async_echo(tcp::socket& socket, CompletionToken&& token) {
|
|
318
|
+
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
|
|
319
|
+
asio::co_composed<void(boost::system::error_code)>(
|
|
320
|
+
[](auto state, tcp::socket& socket) -> void {
|
|
321
|
+
state.throw_if_cancelled(true);
|
|
322
|
+
state.reset_cancellation_state(asio::enable_terminal_cancellation());
|
|
323
|
+
try {
|
|
324
|
+
char data[1024];
|
|
325
|
+
for (;;) {
|
|
326
|
+
std::size_t n = co_await socket.async_read_some(asio::buffer(data));
|
|
327
|
+
co_await async_write(socket, asio::buffer(data, n));
|
|
328
|
+
}
|
|
329
|
+
} catch (const boost::system::system_error& e) {
|
|
330
|
+
co_return {e.code()};
|
|
331
|
+
}
|
|
332
|
+
}, socket),
|
|
333
|
+
token, std::ref(socket));
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Line-Based Protocols
|
|
338
|
+
|
|
339
|
+
For newline-delimited protocols, prefer `async_read_until` over manual `async_read_some` + buffer parsing:
|
|
340
|
+
|
|
341
|
+
```cpp
|
|
342
|
+
asio::awaitable<void> line_echo(tcp::socket socket) {
|
|
343
|
+
asio::streambuf buf;
|
|
344
|
+
for (;;) {
|
|
345
|
+
std::size_t n = co_await asio::async_read_until(socket, buf, '\n');
|
|
346
|
+
std::string line(asio::buffers_begin(buf.data()),
|
|
347
|
+
asio::buffers_begin(buf.data()) + n);
|
|
348
|
+
buf.consume(n);
|
|
349
|
+
co_await async_write(socket, asio::buffer(line));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Or with `dynamic_buffer` over a `std::string`:
|
|
355
|
+
```cpp
|
|
356
|
+
std::string buf;
|
|
357
|
+
std::size_t n = co_await asio::async_read_until(socket, asio::dynamic_buffer(buf), '\n');
|
|
358
|
+
std::string line = buf.substr(0, n);
|
|
359
|
+
buf.erase(0, n);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Length-Prefixed Binary Framing
|
|
363
|
+
|
|
364
|
+
For binary protocols, read the fixed-size header fully, then the body fully — two sequential **composed** reads (`async_read` fills the whole buffer, handling short reads). Do NOT use `async_read_some` for framing.
|
|
365
|
+
|
|
366
|
+
```cpp
|
|
367
|
+
// Frame: [4-byte big-endian length N][N-byte body]
|
|
368
|
+
asio::awaitable<std::string> read_frame(tcp::socket& sock) {
|
|
369
|
+
constexpr uint32_t max_frame_size = 16 * 1024 * 1024;
|
|
370
|
+
uint32_t len_be = 0;
|
|
371
|
+
co_await async_read(sock, asio::buffer(&len_be, sizeof len_be)); // exactly 4 bytes
|
|
372
|
+
uint32_t n = ntohl(len_be); // <arpa/inet.h>; or hand-roll endian swap
|
|
373
|
+
if (n > max_frame_size) {
|
|
374
|
+
throw std::length_error("frame exceeds 16 MiB limit"); // <stdexcept>
|
|
375
|
+
}
|
|
376
|
+
std::string body(n, '\0');
|
|
377
|
+
co_await async_read(sock, asio::buffer(body)); // exactly n bytes
|
|
378
|
+
co_return body;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
asio::awaitable<void> write_frame(tcp::socket& sock, std::string_view body) {
|
|
382
|
+
uint32_t len_be = htonl(static_cast<uint32_t>(body.size()));
|
|
383
|
+
std::array<asio::const_buffer, 2> bufs{
|
|
384
|
+
asio::buffer(&len_be, sizeof len_be), asio::buffer(body)};
|
|
385
|
+
co_await async_write(sock, bufs); // gather-write header + body atomically
|
|
386
|
+
// len_be and body must outlive the write — they do here (co_await suspends in-frame).
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Graceful Shutdown (signal_set)
|
|
391
|
+
|
|
392
|
+
```cpp
|
|
393
|
+
asio::signal_set signals(io, SIGINT, SIGTERM);
|
|
394
|
+
signals.async_wait([&](const boost::system::error_code&, int /*signo*/) {
|
|
395
|
+
acceptor.close(); // stop accepting; let in-flight sessions drain, then io.run() returns
|
|
396
|
+
// or, for an immediate stop: io.stop();
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
For coroutine-style shutdown, `co_await signals.async_wait()` in a dedicated coroutine instead of a callback.
|
|
401
|
+
|
|
402
|
+
## Quick Reference
|
|
403
|
+
|
|
404
|
+
| Operation | Function |
|
|
405
|
+
|-----------|----------|
|
|
406
|
+
| Launch coroutine | `co_spawn(executor, coro, token)` |
|
|
407
|
+
| Accept connection | `co_await acceptor.async_accept()` |
|
|
408
|
+
| Read some bytes | `co_await socket.async_read_some(buffer)` |
|
|
409
|
+
| Read exact/until | `co_await async_read(stream, buf)` / `async_read_until(stream, buf, delim)` |
|
|
410
|
+
| Write all | `co_await async_write(stream, buffer)` |
|
|
411
|
+
| Connect | `co_await async_connect(socket, endpoints)` |
|
|
412
|
+
| Resolve DNS | `co_await resolver.async_resolve(host, port)` |
|
|
413
|
+
| Wait timer | `co_await timer.async_wait()` |
|
|
414
|
+
| TLS handshake | `co_await stream.async_handshake(type)` |
|
|
415
|
+
| Get executor | `co_await asio::this_coro::executor` |
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Pre-C++20 Styles (C++11–17, Boost ≥ 1.74)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
If you can't use C++20 `co_await`, the **same Asio library** (modern Boost or standalone) still works — only the *async style* changes. Compile with C++11 or later. Two pre-C++20 styles:
|
|
5
|
+
|
|
6
|
+
1. **Completion handlers (callbacks)** — header-only, C++11, no extra dependencies. The recommended baseline.
|
|
7
|
+
2. **Stackful coroutines** (`asio::spawn` + `yield_context`) — synchronous-looking like `co_await`, but built on Boost.Coroutine/Boost.Context, so it **must be linked** (not header-only) — see build note below.
|
|
8
|
+
|
|
9
|
+
**Unchanged from the coroutine style** (these are library, not language, features): `io_context`, `make_strand`, `bind_executor`, `steady_timer`, `ssl::stream`, `signal_set`, `async_read`/`async_write`/`async_read_until`, buffers, `resolver`. Use them exactly as shown in [coroutines.md](coroutines.md).
|
|
10
|
+
|
|
11
|
+
**Not available pre-C++20:** `co_await`/`awaitable<T>`, `co_spawn`, `use_awaitable`, the `||`/`&&` `awaitable_operators`, `as_tuple`, and `co_composed`. The table below gives the equivalent.
|
|
12
|
+
|
|
13
|
+
### C++20 → pre-C++20 mapping
|
|
14
|
+
|
|
15
|
+
| C++20 coroutine | Pre-C++20 equivalent |
|
|
16
|
+
|-----------------|----------------------|
|
|
17
|
+
| `co_await op(use_awaitable)` | callback: `op(handler)` · stackful: `op(yield)` |
|
|
18
|
+
| `awaitable<T>` function | member `do_x()` callback chain · or `spawn(strand, fn)` |
|
|
19
|
+
| `co_spawn(ex, coro, tok)` | start the callback chain · or `asio::spawn(ex, fn, tok)` |
|
|
20
|
+
| `as_tuple(use_awaitable)` → `[ec,n]` | callback's `(ec, n)` params · stackful: `op(yield[ec])` |
|
|
21
|
+
| `a() \|\| b()` (first-wins race) | a **watchdog timer** that closes the socket; the other op fails with `operation_aborted` |
|
|
22
|
+
| `a() && b()` (wait both) | launch both, count completions in a shared `shared_ptr<int>` |
|
|
23
|
+
| `co_composed<>` custom op | `asio::async_compose<>` (C++11) |
|
|
24
|
+
| `co_await this_coro::executor` | `socket_.get_executor()` / a passed-in executor |
|
|
25
|
+
|
|
26
|
+
### Callback style: full-duplex + write queue
|
|
27
|
+
|
|
28
|
+
The full-duplex write-queue rule is identical — a strand alone doesn't stop interleaved writes — just expressed with chained handlers. Capture `self = shared_from_this()` in **every** handler to keep the connection alive.
|
|
29
|
+
|
|
30
|
+
```cpp
|
|
31
|
+
class connection : public std::enable_shared_from_this<connection> {
|
|
32
|
+
tcp::socket socket_;
|
|
33
|
+
asio::strand<asio::any_io_executor> strand_; // tcp::socket's executor is any_io_executor
|
|
34
|
+
std::deque<std::string> outbox_;
|
|
35
|
+
bool writing_ = false;
|
|
36
|
+
char buf_[1024];
|
|
37
|
+
public:
|
|
38
|
+
explicit connection(tcp::socket s)
|
|
39
|
+
: socket_(std::move(s)), strand_(asio::make_strand(socket_.get_executor())) {}
|
|
40
|
+
void start() { do_read(); }
|
|
41
|
+
|
|
42
|
+
void send(std::string frame) { // call on the strand only
|
|
43
|
+
outbox_.push_back(std::move(frame));
|
|
44
|
+
if (!writing_) do_write();
|
|
45
|
+
}
|
|
46
|
+
private:
|
|
47
|
+
void do_read() {
|
|
48
|
+
auto self = shared_from_this();
|
|
49
|
+
socket_.async_read_some(asio::buffer(buf_),
|
|
50
|
+
asio::bind_executor(strand_, // serialize handler execution
|
|
51
|
+
[this, self](boost::system::error_code ec, std::size_t n) {
|
|
52
|
+
if (ec) return; // self drops here → socket closes
|
|
53
|
+
/* parse buf_[0..n]; call send() for replies */
|
|
54
|
+
do_read();
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
void do_write() { // at most one async_write in flight
|
|
58
|
+
writing_ = true;
|
|
59
|
+
auto self = shared_from_this();
|
|
60
|
+
asio::async_write(socket_, asio::buffer(outbox_.front()),
|
|
61
|
+
asio::bind_executor(strand_,
|
|
62
|
+
[this, self](boost::system::error_code ec, std::size_t) {
|
|
63
|
+
if (ec) { writing_ = false; return; }
|
|
64
|
+
outbox_.pop_front();
|
|
65
|
+
if (!outbox_.empty()) do_write();
|
|
66
|
+
else writing_ = false;
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Stackful style: spawn + yield_context
|
|
73
|
+
|
|
74
|
+
`yield` is a completion token: `op(socket, ..., yield)` suspends until done and returns the result; errors **throw** by default, or use `yield[ec]` for an `error_code`. Run each chain on a per-connection strand.
|
|
75
|
+
|
|
76
|
+
```cpp
|
|
77
|
+
asio::spawn(strand, // executor or strand
|
|
78
|
+
[self](asio::yield_context yield) { // capture self for lifetime
|
|
79
|
+
try {
|
|
80
|
+
char data[1024];
|
|
81
|
+
for (;;) {
|
|
82
|
+
std::size_t n = self->socket_.async_read_some(asio::buffer(data), yield);
|
|
83
|
+
asio::async_write(self->socket_, asio::buffer(data, n), yield);
|
|
84
|
+
}
|
|
85
|
+
} catch (const std::exception&) { self->socket_.close(); }
|
|
86
|
+
},
|
|
87
|
+
asio::detached); // completion token (3rd arg)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Timeout without `||` (watchdog timer)
|
|
91
|
+
|
|
92
|
+
Replace the `read || timer` race with a separate watchdog: reset the timer on each read; a second chain waits on it and closes the socket on expiry, which makes the read fail with `operation_aborted`.
|
|
93
|
+
|
|
94
|
+
```cpp
|
|
95
|
+
// callback watchdog
|
|
96
|
+
void arm_timeout() {
|
|
97
|
+
timer_.expires_after(std::chrono::seconds(30));
|
|
98
|
+
auto self = shared_from_this();
|
|
99
|
+
timer_.async_wait(asio::bind_executor(strand_,
|
|
100
|
+
[this, self](boost::system::error_code ec) {
|
|
101
|
+
if (!ec) socket_.close(); // fired → drop; reset cancels with ec
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
// call arm_timeout() again on every frame received to re-arm
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Callback multi-step reads + recurring side-tasks
|
|
108
|
+
|
|
109
|
+
**Lifetime shift:** coroutine *stack locals* become *member variables* in callback style — a header/body buffer must outlive each async op or it dangles. Chain a composed read of the length, then the body:
|
|
110
|
+
|
|
111
|
+
```cpp
|
|
112
|
+
// members, NOT locals — they must survive until the handler runs
|
|
113
|
+
uint32_t len_be_;
|
|
114
|
+
std::string body_;
|
|
115
|
+
|
|
116
|
+
void read_frame() {
|
|
117
|
+
auto self = shared_from_this();
|
|
118
|
+
asio::async_read(socket_, asio::buffer(&len_be_, sizeof len_be_),
|
|
119
|
+
asio::bind_executor(strand_, [this, self](boost::system::error_code ec, std::size_t) {
|
|
120
|
+
if (ec) return;
|
|
121
|
+
body_.assign(ntohl(len_be_), '\0');
|
|
122
|
+
asio::async_read(socket_, asio::buffer(body_), // read exactly N bytes
|
|
123
|
+
asio::bind_executor(strand_, [this, self](boost::system::error_code ec2, std::size_t) {
|
|
124
|
+
if (ec2) return;
|
|
125
|
+
handle_frame(body_); // dispatch on type byte
|
|
126
|
+
read_frame(); // next frame
|
|
127
|
+
}));
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Recurring side-task** (e.g. push every 250ms) running concurrently with the read loop — there is no detached coroutine to stop, so use a self-rescheduling timer and `cancel()` it to stop:
|
|
133
|
+
|
|
134
|
+
```cpp
|
|
135
|
+
void schedule_tick(std::string symbol, std::shared_ptr<asio::steady_timer> t) {
|
|
136
|
+
t->expires_after(std::chrono::milliseconds(250));
|
|
137
|
+
auto self = shared_from_this();
|
|
138
|
+
t->async_wait(asio::bind_executor(strand_,
|
|
139
|
+
[this, self, symbol, t](boost::system::error_code ec) {
|
|
140
|
+
if (ec) return; // cancelled on unsubscribe/close → stops
|
|
141
|
+
send(make_tick(symbol)); // enqueue on the write queue
|
|
142
|
+
schedule_tick(symbol, t); // reschedule itself
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
// start: keep one timer per subscription alive (e.g. in a map); stop: erase + t->cancel()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Build difference (stackful spawn only)
|
|
149
|
+
|
|
150
|
+
Callbacks need no change beyond the standard (drop `-fcoroutines`; it's only for C++20 `co_await`):
|
|
151
|
+
```cmake
|
|
152
|
+
set(CMAKE_CXX_STANDARD 11) # or 14 / 17
|
|
153
|
+
set(CMAKE_CXX_EXTENSIONS OFF) # else CMake emits -std=gnu++NN, not literal -std=c++NN
|
|
154
|
+
target_link_libraries(app PRIVATE Boost::headers Threads::Threads)
|
|
155
|
+
target_compile_definitions(app PRIVATE BOOST_ERROR_CODE_HEADER_ONLY)
|
|
156
|
+
```
|
|
157
|
+
Stackful `spawn` additionally requires Boost.Coroutine (which uses Boost.Context) — **not header-only**:
|
|
158
|
+
```cmake
|
|
159
|
+
find_package(Boost REQUIRED COMPONENTS coroutine)
|
|
160
|
+
target_link_libraries(app PRIVATE Boost::coroutine) # pulls in Boost.Context
|
|
161
|
+
```
|
|
162
|
+
> Standalone Asio's `spawn` also depends on Boost.Coroutine/Context — it drags Boost into an otherwise Boost-free build. If you want zero Boost, use the **callback** style.
|
|
163
|
+
>
|
|
164
|
+
> The 3-arg `spawn(ex, fn, token)` form needs **Boost ≥ 1.80** (older Boost has only `spawn(ex, fn)`). On old distros like Debian bookworm (Boost 1.74), the **callback** style compiles cleanly while stackful `spawn` does not — verified.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SSL/TLS
|
|
2
|
+
|
|
3
|
+
Applies to every style — `ssl::stream<>` is a library feature, not a language one.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```cpp
|
|
7
|
+
#include <boost/asio.hpp>
|
|
8
|
+
#include <boost/asio/ssl.hpp>
|
|
9
|
+
|
|
10
|
+
namespace asio = boost::asio;
|
|
11
|
+
namespace ssl = asio::ssl;
|
|
12
|
+
using tcp = asio::ip::tcp;
|
|
13
|
+
|
|
14
|
+
asio::awaitable<void> tls_client(asio::io_context& io) {
|
|
15
|
+
ssl::context ctx(ssl::context::tlsv13_client);
|
|
16
|
+
ctx.set_default_verify_paths();
|
|
17
|
+
|
|
18
|
+
ssl::stream<tcp::socket> stream(io, ctx);
|
|
19
|
+
|
|
20
|
+
// Connect underlying TCP socket
|
|
21
|
+
auto& sock = stream.lowest_layer();
|
|
22
|
+
co_await sock.async_connect(endpoint);
|
|
23
|
+
|
|
24
|
+
// Set SNI hostname (required for most servers)
|
|
25
|
+
SSL_set_tlsext_host_name(stream.native_handle(), "example.com");
|
|
26
|
+
stream.set_verify_mode(ssl::verify_peer);
|
|
27
|
+
stream.set_verify_callback(ssl::host_name_verification("example.com"));
|
|
28
|
+
|
|
29
|
+
// TLS handshake
|
|
30
|
+
co_await stream.async_handshake(ssl::stream_base::client);
|
|
31
|
+
|
|
32
|
+
// Read/write as normal stream
|
|
33
|
+
co_await async_write(stream, asio::buffer(request));
|
|
34
|
+
co_await async_read_until(stream, response_buf, "\r\n");
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Critical:** SSL streams require strand-based synchronization for all async operations — no concurrent reads/writes without a strand.
|