opencode-skills-collection 4.0.36 → 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 +7 -1
- package/bundled-skills/agent-harness-fault-injection/SKILL.md +250 -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/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/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/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 +175 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Build Configuration
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Boost.Asio (header-only since Boost 1.74+)
|
|
5
|
+
|
|
6
|
+
```cmake
|
|
7
|
+
find_package(Boost REQUIRED)
|
|
8
|
+
find_package(OpenSSL REQUIRED) # if using SSL
|
|
9
|
+
find_package(Threads REQUIRED)
|
|
10
|
+
|
|
11
|
+
target_link_libraries(myapp PRIVATE
|
|
12
|
+
Boost::headers # header-only Asio
|
|
13
|
+
OpenSSL::SSL OpenSSL::Crypto # if using SSL
|
|
14
|
+
Threads::Threads
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
target_compile_features(myapp PRIVATE cxx_std_20)
|
|
18
|
+
|
|
19
|
+
# REQUIRED for GCC coroutine support — build will fail without this
|
|
20
|
+
target_compile_options(myapp PRIVATE
|
|
21
|
+
$<$<CXX_COMPILER_ID:GNU>:-fcoroutines>
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Optional: truly header-only (no Boost.System link needed)
|
|
25
|
+
target_compile_definitions(myapp PRIVATE BOOST_ERROR_CODE_HEADER_ONLY)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Standalone Asio (always header-only)
|
|
29
|
+
|
|
30
|
+
```cmake
|
|
31
|
+
# Standalone Asio has no CMake config — use pkg-config or manual path
|
|
32
|
+
find_package(OpenSSL REQUIRED)
|
|
33
|
+
find_package(Threads REQUIRED)
|
|
34
|
+
|
|
35
|
+
# If installed via brew:
|
|
36
|
+
find_path(ASIO_INCLUDE_DIR asio.hpp HINTS /opt/homebrew/include)
|
|
37
|
+
|
|
38
|
+
target_include_directories(myapp PRIVATE ${ASIO_INCLUDE_DIR})
|
|
39
|
+
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto Threads::Threads)
|
|
40
|
+
target_compile_features(myapp PRIVATE cxx_std_20)
|
|
41
|
+
target_compile_definitions(myapp PRIVATE ASIO_STANDALONE)
|
|
42
|
+
|
|
43
|
+
target_compile_options(myapp PRIVATE
|
|
44
|
+
$<$<CXX_COMPILER_ID:GNU>:-fcoroutines>
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Dual-mode CMake (supports both)
|
|
49
|
+
|
|
50
|
+
```cmake
|
|
51
|
+
option(USE_STANDALONE_ASIO "Use standalone Asio instead of Boost.Asio" OFF)
|
|
52
|
+
|
|
53
|
+
find_package(OpenSSL REQUIRED)
|
|
54
|
+
find_package(Threads REQUIRED)
|
|
55
|
+
|
|
56
|
+
if(USE_STANDALONE_ASIO)
|
|
57
|
+
find_path(ASIO_INCLUDE_DIR asio.hpp HINTS /opt/homebrew/include)
|
|
58
|
+
target_include_directories(myapp PRIVATE ${ASIO_INCLUDE_DIR})
|
|
59
|
+
target_compile_definitions(myapp PRIVATE USE_STANDALONE_ASIO ASIO_STANDALONE)
|
|
60
|
+
else()
|
|
61
|
+
find_package(Boost REQUIRED)
|
|
62
|
+
target_link_libraries(myapp PRIVATE Boost::headers)
|
|
63
|
+
target_compile_definitions(myapp PRIVATE BOOST_ERROR_CODE_HEADER_ONLY)
|
|
64
|
+
endif()
|
|
65
|
+
|
|
66
|
+
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto Threads::Threads)
|
|
67
|
+
target_compile_features(myapp PRIVATE cxx_std_20)
|
|
68
|
+
target_compile_options(myapp PRIVATE $<$<CXX_COMPILER_ID:GNU>:-fcoroutines>)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Header-Only Usage
|
|
72
|
+
|
|
73
|
+
**Boost.Asio:** Asio is header-only by default. The only thing that pulls in a Boost library to link is `boost::system::error_code`'s out-of-line symbols, so for a truly link-free build define **`BOOST_ERROR_CODE_HEADER_ONLY`**. `BOOST_ASIO_HEADER_ONLY` is rarely needed and only relevant if separate compilation was previously enabled; you do **not** normally need both.
|
|
74
|
+
|
|
75
|
+
**Define `BOOST_ERROR_CODE_HEADER_ONLY` in exactly ONE place — prefer CMake** (`target_compile_definitions`, as shown above). Defining it in CMake *and* with a source `#define` triggers `-Wmacro-redefined`. So in source, just include — no `#define`:
|
|
76
|
+
```cpp
|
|
77
|
+
#include <boost/asio.hpp>
|
|
78
|
+
#include <boost/asio/ssl.hpp>
|
|
79
|
+
#include <boost/asio/experimental/awaitable_operators.hpp>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Standalone Asio:**
|
|
83
|
+
```cpp
|
|
84
|
+
#include <asio.hpp>
|
|
85
|
+
#include <asio/ssl.hpp>
|
|
86
|
+
#include <asio/experimental/awaitable_operators.hpp>
|
|
87
|
+
// No macros needed — always header-only
|
|
88
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Classic Boost (pre-1.66, the `io_service` era — verified to 1.62)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
To support Boost older than 1.66 (no `io_context`, no `make_strand`, no `any_io_executor`), drop to the classic API — verified building **back to Boost 1.62** (Debian 9) while still compiling on current Boost via a tiny shim:
|
|
5
|
+
|
|
6
|
+
| Modern (1.66+) | Classic (pre-1.66) |
|
|
7
|
+
|----------------|--------------------|
|
|
8
|
+
| `io_context` | `io_service` |
|
|
9
|
+
| `make_strand(ex)` / `strand<any_io_executor>` | `io_service::strand strand(io)` |
|
|
10
|
+
| `bind_executor(strand, h)` | `strand.wrap(h)` |
|
|
11
|
+
| `timer.expires_after(d)` | `timer.expires_from_now(d)` |
|
|
12
|
+
| move-return `async_accept()` | `async_accept(socket_, handler)` |
|
|
13
|
+
| header-only `error_code` | link **Boost.System** (`find_package(Boost COMPONENTS system)`) |
|
|
14
|
+
|
|
15
|
+
Only the `io_service`/`io_context` name and the `expires_after`/`expires_from_now` call actually differ across 1.62…1.90 — isolate both behind `#if BOOST_VERSION >= 106600`:
|
|
16
|
+
```cpp
|
|
17
|
+
#include <boost/version.hpp>
|
|
18
|
+
#include <boost/asio/steady_timer.hpp> // not pulled in by <boost/asio.hpp> on old Boost
|
|
19
|
+
#if BOOST_VERSION >= 106600
|
|
20
|
+
using io_service_t = boost::asio::io_context;
|
|
21
|
+
#else
|
|
22
|
+
using io_service_t = boost::asio::io_service;
|
|
23
|
+
#endif
|
|
24
|
+
template <class T, class Rep, class Period>
|
|
25
|
+
void timer_expires_in(T& t, std::chrono::duration<Rep,Period> d) {
|
|
26
|
+
#if BOOST_VERSION >= 106600
|
|
27
|
+
t.expires_after(d);
|
|
28
|
+
#else
|
|
29
|
+
t.expires_from_now(d);
|
|
30
|
+
#endif
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
CMake for this range: `cmake_minimum_required(VERSION 3.5)` (Debian 9 ships cmake 3.7), link `Boost::system` only if the component is found (modern Boost is header-only and has no such component), and use the classic out-of-source build (`mkdir build && cd build && cmake ..`) since `-S`/`-B` need cmake ≥ 3.13.
|
|
@@ -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` |
|