litclaude-ai 0.2.2
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/CHANGELOG.md +155 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/README_ko-KR.md +374 -0
- package/RELEASE_CHECKLIST.md +165 -0
- package/bin/litclaude-ai.js +643 -0
- package/cover.png +0 -0
- package/docs/agents.md +67 -0
- package/docs/hooks.md +134 -0
- package/docs/lsp.md +40 -0
- package/docs/migration.md +209 -0
- package/docs/workflow-compatibility-audit.md +119 -0
- package/generate_cover.py +123 -0
- package/package.json +48 -0
- package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
- package/plugins/litclaude/.lsp.json +13 -0
- package/plugins/litclaude/.mcp.json +9 -0
- package/plugins/litclaude/agents/boulder-executor.md +12 -0
- package/plugins/litclaude/agents/librarian-researcher.md +15 -0
- package/plugins/litclaude/agents/oracle-verifier.md +16 -0
- package/plugins/litclaude/agents/prometheus-planner.md +13 -0
- package/plugins/litclaude/agents/qa-runner.md +16 -0
- package/plugins/litclaude/agents/quality-reviewer.md +17 -0
- package/plugins/litclaude/bin/litclaude-hook.js +110 -0
- package/plugins/litclaude/bin/litclaude-hud.js +271 -0
- package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
- package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
- package/plugins/litclaude/commands/deep-interview.md +21 -0
- package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
- package/plugins/litclaude/commands/lit-loop.md +40 -0
- package/plugins/litclaude/commands/lit-plan.md +35 -0
- package/plugins/litclaude/commands/litgoal.md +30 -0
- package/plugins/litclaude/commands/review-work.md +35 -0
- package/plugins/litclaude/commands/start-work.md +36 -0
- package/plugins/litclaude/hooks/hooks.json +54 -0
- package/plugins/litclaude/lib/context-pressure.mjs +25 -0
- package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
- package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
- package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
- package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
- package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
- package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
- package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
- package/plugins/litclaude/lib/workflow-check.mjs +83 -0
- package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
- package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
- package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
- package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
- package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
- package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
- package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
- package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
- package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
- package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
- package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
- package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
- package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
- package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
- package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
- package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
- package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
- package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
- package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
- package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
- package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
- package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
- package/plugins/litclaude/skills/programming/SKILL.md +106 -0
- package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
- package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
- package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
- package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
- package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
- package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
- package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
- package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
- package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
- package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
- package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
- package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
- package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
- package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
- package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
- package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
- package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
- package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
- package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
- package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
- package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
- package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
- package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
- package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
- package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
- package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
- package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
- package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
- package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
- package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
- package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
- package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
- package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
- package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
- package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
- package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
- package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
- package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
- package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
- package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
- package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
- package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
- package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
- package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
- package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
- package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
- package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
- package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
- package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
- package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
- package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
- package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
- package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
- package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
- package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
- package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
- package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
- package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
- package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
- package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
- package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
- package/plugins/litclaude/skills/rules/SKILL.md +66 -0
- package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
- package/scripts/audit-plan-checkboxes.mjs +37 -0
- package/scripts/doctor.mjs +41 -0
- package/scripts/inspect-agent-tools.mjs +27 -0
- package/scripts/postinstall.mjs +50 -0
- package/scripts/qa-claude-plugin-smoke.sh +60 -0
- package/scripts/qa-portable-install.sh +136 -0
- package/scripts/validate-plugin.mjs +72 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Async with Tokio
|
|
2
|
+
|
|
3
|
+
Structured concurrency, cancellation, blocking-work isolation, channel selection. The patterns the agent should reach for by default.
|
|
4
|
+
|
|
5
|
+
## Runtime selection
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
// Default for services and CLIs that do real work
|
|
9
|
+
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
|
|
10
|
+
async fn main() -> anyhow::Result<()> { ... }
|
|
11
|
+
|
|
12
|
+
// For tiny CLIs or wasm where you measured single-thread is enough
|
|
13
|
+
#[tokio::main(flavor = "current_thread")]
|
|
14
|
+
async fn main() -> anyhow::Result<()> { ... }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Pick worker count explicitly. The default (`num_cpus`) is fine for servers; for desktop tools you usually want 2-4.
|
|
18
|
+
|
|
19
|
+
## Spawning
|
|
20
|
+
|
|
21
|
+
`tokio::spawn` returns a `JoinHandle<T>`. The future runs to completion even if the handle is dropped (detached). To enforce structured concurrency, use `JoinSet`:
|
|
22
|
+
|
|
23
|
+
```rust
|
|
24
|
+
use tokio::task::JoinSet;
|
|
25
|
+
|
|
26
|
+
let mut set = JoinSet::new();
|
|
27
|
+
for url in urls {
|
|
28
|
+
let client = client.clone();
|
|
29
|
+
set.spawn(async move { fetch(&client, &url).await });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let mut results = Vec::new();
|
|
33
|
+
while let Some(joined) = set.join_next().await {
|
|
34
|
+
match joined {
|
|
35
|
+
Ok(Ok(body)) => results.push(body),
|
|
36
|
+
Ok(Err(error)) => tracing::warn!(%error, "fetch failed"),
|
|
37
|
+
Err(panicked) if panicked.is_panic() => {
|
|
38
|
+
tracing::error!(?panicked, "worker panicked");
|
|
39
|
+
// Choose: re-raise, or continue with degraded result set.
|
|
40
|
+
}
|
|
41
|
+
Err(other) => tracing::error!(?other, "worker join error"),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`JoinSet`:
|
|
47
|
+
- Knows when all spawned tasks finish.
|
|
48
|
+
- Dropping the set aborts every still-running task.
|
|
49
|
+
- Lets you handle failures one by one rather than all-or-nothing.
|
|
50
|
+
|
|
51
|
+
For wait-for-all semantics with one type, `join!`:
|
|
52
|
+
|
|
53
|
+
```rust
|
|
54
|
+
let (a, b, c) = tokio::join!(load_a(), load_b(), load_c());
|
|
55
|
+
let a = a?; let b = b?; let c = c?;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For first-of-many, `select!`:
|
|
59
|
+
|
|
60
|
+
```rust
|
|
61
|
+
tokio::select! {
|
|
62
|
+
biased; // bias to top-to-bottom checking when ordering matters
|
|
63
|
+
_ = shutdown.recv() => {
|
|
64
|
+
tracing::info!("shutdown signal");
|
|
65
|
+
return Ok(());
|
|
66
|
+
}
|
|
67
|
+
request = listener.accept() => {
|
|
68
|
+
handle_request(request?).await?;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Without `biased`, branches are polled in random order each iteration (good for fairness). Use `biased` only when you need deterministic priority (shutdown signal first, etc).
|
|
74
|
+
|
|
75
|
+
## Cancellation
|
|
76
|
+
|
|
77
|
+
A future is cancelled when it is dropped (e.g., the `select!` arm wins another branch). **Always think: if this future is dropped mid-await, what state is left behind?**
|
|
78
|
+
|
|
79
|
+
Cancel-safe futures (you can drop without lasting effect):
|
|
80
|
+
- `recv()` on channels
|
|
81
|
+
- `accept()` on listeners
|
|
82
|
+
- `wait_for` on `watch::Receiver`
|
|
83
|
+
- `read_buf`/`write_all` on streams **only when buffers are owned by the future**, otherwise no
|
|
84
|
+
|
|
85
|
+
Cancel-unsafe futures (dropping mid-way leaves partial state):
|
|
86
|
+
- Manual `read_exact` into an external buffer
|
|
87
|
+
- Custom futures that perform partial side effects before suspending
|
|
88
|
+
|
|
89
|
+
If a function is cancel-unsafe, document it in a rustdoc `# Cancel Safety` section.
|
|
90
|
+
|
|
91
|
+
To explicitly opt out of cancellation, use `tokio_util::sync::CancellationToken`:
|
|
92
|
+
|
|
93
|
+
```rust
|
|
94
|
+
use tokio_util::sync::CancellationToken;
|
|
95
|
+
|
|
96
|
+
let token = CancellationToken::new();
|
|
97
|
+
let child = token.child_token();
|
|
98
|
+
tokio::spawn(async move {
|
|
99
|
+
tokio::select! {
|
|
100
|
+
_ = child.cancelled() => { /* clean up */ }
|
|
101
|
+
result = work() => { /* normal */ }
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// later
|
|
105
|
+
token.cancel();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Pass child tokens down the call tree so the whole tree can be cancelled together.
|
|
109
|
+
|
|
110
|
+
## Timeouts
|
|
111
|
+
|
|
112
|
+
```rust
|
|
113
|
+
use tokio::time::{timeout, Duration};
|
|
114
|
+
|
|
115
|
+
match timeout(Duration::from_secs(5), fetch(url)).await {
|
|
116
|
+
Ok(Ok(body)) => Ok(body),
|
|
117
|
+
Ok(Err(error)) => Err(error.into()),
|
|
118
|
+
Err(_elapsed) => Err(anyhow::anyhow!("timed out fetching {url}")),
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Set timeouts on every external I/O boundary. Defaults of "wait forever" are bugs.
|
|
123
|
+
|
|
124
|
+
## Blocking work
|
|
125
|
+
|
|
126
|
+
NEVER block inside an async task. Symptoms: deadlock, every future stalled, latency cliffs.
|
|
127
|
+
|
|
128
|
+
Heavy CPU or sync I/O → `spawn_blocking`:
|
|
129
|
+
|
|
130
|
+
```rust
|
|
131
|
+
let result = tokio::task::spawn_blocking(|| {
|
|
132
|
+
// CPU-bound: parsing, hashing, image processing
|
|
133
|
+
// Or sync I/O: rusqlite, OS APIs without async wrappers
|
|
134
|
+
expensive_pure_computation()
|
|
135
|
+
}).await?;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Long-running blocking jobs (more than ~1 second of CPU) → use a dedicated thread pool (`rayon`), not tokio's blocking pool which is sized for short bursts.
|
|
139
|
+
|
|
140
|
+
## Channels
|
|
141
|
+
|
|
142
|
+
| Need | Use |
|
|
143
|
+
|---|---|
|
|
144
|
+
| 1-many producers → 1 consumer, async | `tokio::sync::mpsc::channel(cap)` |
|
|
145
|
+
| Same as above, both sync + async | `flume::bounded(cap)` |
|
|
146
|
+
| 1 → many fan-out, latest-value semantics | `tokio::sync::watch::channel(initial)` |
|
|
147
|
+
| 1 → many fan-out, queued | `tokio::sync::broadcast::channel(cap)` |
|
|
148
|
+
| One-shot reply | `tokio::sync::oneshot::channel()` |
|
|
149
|
+
| Backpressure-driven stream of items | `tokio::sync::mpsc::Receiver` + `ReceiverStream` |
|
|
150
|
+
|
|
151
|
+
Mpsc pattern:
|
|
152
|
+
|
|
153
|
+
```rust
|
|
154
|
+
let (tx, mut rx) = tokio::sync::mpsc::channel::<Job>(256);
|
|
155
|
+
|
|
156
|
+
tokio::spawn(async move {
|
|
157
|
+
while let Some(job) = rx.recv().await {
|
|
158
|
+
if let Err(error) = process(job).await {
|
|
159
|
+
tracing::warn!(%error, "job failed");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
tracing::info!("queue closed, shutting down worker");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
tx.send(Job { ... }).await?; // blocks if full, applies backpressure
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Always bound channels. Unbounded channels are a memory leak waiting to happen.
|
|
169
|
+
|
|
170
|
+
## Streams
|
|
171
|
+
|
|
172
|
+
`futures::Stream` is the async analogue of `Iterator`. Use it for paginated fetches, long-poll responses, file lines.
|
|
173
|
+
|
|
174
|
+
```rust
|
|
175
|
+
use futures::stream::{StreamExt, TryStreamExt};
|
|
176
|
+
|
|
177
|
+
let urls: Vec<String> = ...;
|
|
178
|
+
let bodies: Vec<String> = futures::stream::iter(urls)
|
|
179
|
+
.map(|url| async move { fetch(&url).await })
|
|
180
|
+
.buffer_unordered(8) // up to 8 in flight
|
|
181
|
+
.try_collect()
|
|
182
|
+
.await?;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`buffer_unordered(n)` is the throttle. Use it instead of spawning N tasks manually.
|
|
186
|
+
|
|
187
|
+
For producing a stream from a channel:
|
|
188
|
+
|
|
189
|
+
```rust
|
|
190
|
+
use tokio_stream::wrappers::ReceiverStream;
|
|
191
|
+
|
|
192
|
+
let (tx, rx) = tokio::sync::mpsc::channel::<Event>(64);
|
|
193
|
+
let stream = ReceiverStream::new(rx);
|
|
194
|
+
serve_sse(stream).await
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Graceful shutdown
|
|
198
|
+
|
|
199
|
+
```rust
|
|
200
|
+
use tokio::signal;
|
|
201
|
+
|
|
202
|
+
async fn shutdown_signal() {
|
|
203
|
+
let ctrl_c = async { signal::ctrl_c().await.expect("ctrl_c handler") };
|
|
204
|
+
#[cfg(unix)]
|
|
205
|
+
let terminate = async {
|
|
206
|
+
signal::unix::signal(signal::unix::SignalKind::terminate())
|
|
207
|
+
.expect("install signal handler")
|
|
208
|
+
.recv()
|
|
209
|
+
.await;
|
|
210
|
+
};
|
|
211
|
+
#[cfg(not(unix))]
|
|
212
|
+
let terminate = std::future::pending::<()>();
|
|
213
|
+
|
|
214
|
+
tokio::select! {
|
|
215
|
+
_ = ctrl_c => {},
|
|
216
|
+
_ = terminate => {},
|
|
217
|
+
}
|
|
218
|
+
tracing::info!("shutdown signal received");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[tokio::main]
|
|
222
|
+
async fn main() -> anyhow::Result<()> {
|
|
223
|
+
let token = CancellationToken::new();
|
|
224
|
+
let server = tokio::spawn(run_server(token.child_token()));
|
|
225
|
+
shutdown_signal().await;
|
|
226
|
+
token.cancel();
|
|
227
|
+
let _ = tokio::time::timeout(Duration::from_secs(10), server).await;
|
|
228
|
+
Ok(())
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Pattern: catch signal → cancel a token shared with the server → server's `select!` arms see the cancel and exit cleanly → wait with a timeout so a hung worker can't deadlock shutdown.
|
|
233
|
+
|
|
234
|
+
## Concurrency primitives
|
|
235
|
+
|
|
236
|
+
- `tokio::sync::Mutex` — async mutex. Use for state shared between async tasks. **Do not hold across `.await` without thinking** (you'll serialize the whole system).
|
|
237
|
+
- `tokio::sync::RwLock` — async read-write lock. Same caveat.
|
|
238
|
+
- `parking_lot::Mutex` — sync mutex, faster than `std::sync::Mutex`, no poisoning. Use when the lock is held briefly and you do not need to `.await` while holding it.
|
|
239
|
+
- `tokio::sync::Semaphore` — bound concurrent operations. Perfect for "max 10 in-flight HTTP requests" or "max 3 DB writers".
|
|
240
|
+
|
|
241
|
+
```rust
|
|
242
|
+
let sem = Arc::new(tokio::sync::Semaphore::new(10));
|
|
243
|
+
for url in urls {
|
|
244
|
+
let permit = sem.clone().acquire_owned().await?;
|
|
245
|
+
tokio::spawn(async move {
|
|
246
|
+
let _permit = permit; // released on task end
|
|
247
|
+
fetch(&url).await
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Common mistakes
|
|
253
|
+
|
|
254
|
+
1. **Holding a sync mutex across `.await`.** Compiles and runs, deadlocks at scale. Solution: refactor to release before await, or use `tokio::sync::Mutex`.
|
|
255
|
+
2. **Forgetting `?` on `JoinHandle`.** A panicked task returns `Err(JoinError)`; if you `.await` and ignore, panics are silently swallowed.
|
|
256
|
+
3. **`tokio::spawn` instead of `JoinSet`.** Detached tasks survive past their parent, causing leaks. Default to `JoinSet` for structured concurrency.
|
|
257
|
+
4. **Unbounded channels.** Always set a capacity.
|
|
258
|
+
5. **`block_on` inside an async context.** Causes deadlock under `current_thread` runtime, performance cliff under `multi_thread`.
|
|
259
|
+
6. **CPU-heavy work in async fn.** Move to `spawn_blocking` or `rayon`.
|
|
260
|
+
7. **No timeout on external I/O.** Every `await` that touches the network or filesystem needs `tokio::time::timeout` wrapping.
|
|
261
|
+
|
|
262
|
+
## Testing async code
|
|
263
|
+
|
|
264
|
+
```rust
|
|
265
|
+
#[tokio::test]
|
|
266
|
+
async fn fetches_and_parses() {
|
|
267
|
+
let server = wiremock::MockServer::start().await;
|
|
268
|
+
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
|
269
|
+
.respond_with(wiremock::ResponseTemplate::new(200).set_body_string("{\"id\":1}"))
|
|
270
|
+
.mount(&server)
|
|
271
|
+
.await;
|
|
272
|
+
|
|
273
|
+
let result = my_client::fetch(&server.uri()).await.unwrap();
|
|
274
|
+
assert_eq!(result.id, 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
278
|
+
async fn parallel_work() { ... }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
For time-sensitive tests, advance virtual time:
|
|
282
|
+
|
|
283
|
+
```rust
|
|
284
|
+
#[tokio::test(start_paused = true)]
|
|
285
|
+
async fn time_travel() {
|
|
286
|
+
let start = tokio::time::Instant::now();
|
|
287
|
+
tokio::time::sleep(Duration::from_secs(3600)).await;
|
|
288
|
+
assert!(start.elapsed() >= Duration::from_secs(3600));
|
|
289
|
+
// Real wallclock elapsed: ~0ms.
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## When NOT to use async
|
|
294
|
+
|
|
295
|
+
- Single-threaded CPU-heavy code that does no I/O — plain `fn` + `rayon` is simpler and often faster.
|
|
296
|
+
- Trivial scripts that do one HTTP call — `ureq` (sync) is simpler.
|
|
297
|
+
- FFI heavy code where the FFI side is sync.
|
|
298
|
+
|
|
299
|
+
Async pays off when you have many concurrent I/O operations or need cancellation as a first-class primitive.
|