agy-superpowers 5.2.1 → 5.2.3
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/README.md +47 -150
- package/package.json +1 -1
- package/template/agent/patches/skills-patches.md +23 -0
- package/template/agent/rules/scratch-scripts.md +37 -0
- package/template/agent/rules/superpowers.md +6 -50
- package/template/agent/skills/brainstorming/SKILL.md +4 -3
- package/template/agent/skills/brainstorming/visual-companion.md +2 -3
- package/template/agent/skills/finishing-a-development-branch/SKILL.md +11 -16
- package/template/agent/skills/subagent-driven-development/SKILL.md +16 -0
- package/template/agent/skills/subagent-driven-development/implementer-prompt.md +4 -3
- package/template/agent/skills/using-git-worktrees/SKILL.md +3 -2
- package/template/agent/skills/using-superpowers/SKILL.md +8 -6
- package/template/agent/skills/using-superpowers/references/copilot-tools.md +52 -0
- package/template/agent/skills/writing-plans/SKILL.md +5 -3
- package/template/agent/skills/writing-skills/SKILL.md +1 -1
- package/template/agent/superpowers-version.json +2 -2
- package/template/agent/tmp/agent-config-backup.yml +9 -0
- package/template/agent/skills/ai-integrated-product/SKILL.md +0 -57
- package/template/agent/skills/analytics-setup/SKILL.md +0 -51
- package/template/agent/skills/api-design/SKILL.md +0 -193
- package/template/agent/skills/app-store-optimizer/SKILL.md +0 -127
- package/template/agent/skills/auth-and-identity/SKILL.md +0 -167
- package/template/agent/skills/backend-developer/SKILL.md +0 -148
- package/template/agent/skills/bootstrapper-finance/SKILL.md +0 -55
- package/template/agent/skills/chrome-extension-developer/SKILL.md +0 -53
- package/template/agent/skills/community-manager/SKILL.md +0 -115
- package/template/agent/skills/content-marketer/SKILL.md +0 -111
- package/template/agent/skills/conversion-optimizer/SKILL.md +0 -142
- package/template/agent/skills/cto-architect/SKILL.md +0 -133
- package/template/agent/skills/customer-success-manager/SKILL.md +0 -126
- package/template/agent/skills/data-analyst/SKILL.md +0 -147
- package/template/agent/skills/devops-engineer/SKILL.md +0 -117
- package/template/agent/skills/email-infrastructure/SKILL.md +0 -164
- package/template/agent/skills/game-design/SKILL.md +0 -194
- package/template/agent/skills/game-developer/SKILL.md +0 -175
- package/template/agent/skills/growth-hacker/SKILL.md +0 -122
- package/template/agent/skills/idea-validator/SKILL.md +0 -55
- package/template/agent/skills/indie-legal/SKILL.md +0 -53
- package/template/agent/skills/influencer-marketer/SKILL.md +0 -141
- package/template/agent/skills/landing-page-builder/SKILL.md +0 -59
- package/template/agent/skills/launch-strategist/SKILL.md +0 -62
- package/template/agent/skills/market-researcher/SKILL.md +0 -53
- package/template/agent/skills/micro-saas-builder/SKILL.md +0 -56
- package/template/agent/skills/monetization-strategist/SKILL.md +0 -119
- package/template/agent/skills/paid-acquisition-specialist/SKILL.md +0 -119
- package/template/agent/skills/pricing-psychologist/SKILL.md +0 -58
- package/template/agent/skills/real-time-features/SKILL.md +0 -194
- package/template/agent/skills/retention-specialist/SKILL.md +0 -123
- package/template/agent/skills/rust-developer/SKILL.md +0 -281
- package/template/agent/skills/rust-developer/references/rust-rules/_sections.md +0 -231
- package/template/agent/skills/rust-developer/references/rust-rules/anti-clone-excessive.md +0 -124
- package/template/agent/skills/rust-developer/references/rust-rules/anti-collect-intermediate.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/anti-empty-catch.md +0 -132
- package/template/agent/skills/rust-developer/references/rust-rules/anti-expect-lazy.md +0 -95
- package/template/agent/skills/rust-developer/references/rust-rules/anti-format-hot-path.md +0 -141
- package/template/agent/skills/rust-developer/references/rust-rules/anti-index-over-iter.md +0 -125
- package/template/agent/skills/rust-developer/references/rust-rules/anti-lock-across-await.md +0 -127
- package/template/agent/skills/rust-developer/references/rust-rules/anti-over-abstraction.md +0 -120
- package/template/agent/skills/rust-developer/references/rust-rules/anti-panic-expected.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/anti-premature-optimize.md +0 -156
- package/template/agent/skills/rust-developer/references/rust-rules/anti-string-for-str.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/anti-stringly-typed.md +0 -167
- package/template/agent/skills/rust-developer/references/rust-rules/anti-type-erasure.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/anti-unwrap-abuse.md +0 -143
- package/template/agent/skills/rust-developer/references/rust-rules/anti-vec-for-slice.md +0 -121
- package/template/agent/skills/rust-developer/references/rust-rules/api-builder-must-use.md +0 -143
- package/template/agent/skills/rust-developer/references/rust-rules/api-builder-pattern.md +0 -187
- package/template/agent/skills/rust-developer/references/rust-rules/api-common-traits.md +0 -165
- package/template/agent/skills/rust-developer/references/rust-rules/api-default-impl.md +0 -177
- package/template/agent/skills/rust-developer/references/rust-rules/api-extension-trait.md +0 -163
- package/template/agent/skills/rust-developer/references/rust-rules/api-from-not-into.md +0 -146
- package/template/agent/skills/rust-developer/references/rust-rules/api-impl-asref.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/api-impl-into.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/api-must-use.md +0 -125
- package/template/agent/skills/rust-developer/references/rust-rules/api-newtype-safety.md +0 -162
- package/template/agent/skills/rust-developer/references/rust-rules/api-non-exhaustive.md +0 -177
- package/template/agent/skills/rust-developer/references/rust-rules/api-parse-dont-validate.md +0 -184
- package/template/agent/skills/rust-developer/references/rust-rules/api-sealed-trait.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/api-serde-optional.md +0 -182
- package/template/agent/skills/rust-developer/references/rust-rules/api-typestate.md +0 -199
- package/template/agent/skills/rust-developer/references/rust-rules/async-bounded-channel.md +0 -175
- package/template/agent/skills/rust-developer/references/rust-rules/async-broadcast-pubsub.md +0 -185
- package/template/agent/skills/rust-developer/references/rust-rules/async-cancellation-token.md +0 -203
- package/template/agent/skills/rust-developer/references/rust-rules/async-clone-before-await.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/async-join-parallel.md +0 -158
- package/template/agent/skills/rust-developer/references/rust-rules/async-joinset-structured.md +0 -195
- package/template/agent/skills/rust-developer/references/rust-rules/async-mpsc-queue.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/async-no-lock-await.md +0 -156
- package/template/agent/skills/rust-developer/references/rust-rules/async-oneshot-response.md +0 -191
- package/template/agent/skills/rust-developer/references/rust-rules/async-select-racing.md +0 -198
- package/template/agent/skills/rust-developer/references/rust-rules/async-spawn-blocking.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/async-tokio-fs.md +0 -167
- package/template/agent/skills/rust-developer/references/rust-rules/async-tokio-runtime.md +0 -169
- package/template/agent/skills/rust-developer/references/rust-rules/async-try-join.md +0 -172
- package/template/agent/skills/rust-developer/references/rust-rules/async-watch-latest.md +0 -189
- package/template/agent/skills/rust-developer/references/rust-rules/doc-all-public.md +0 -113
- package/template/agent/skills/rust-developer/references/rust-rules/doc-cargo-metadata.md +0 -147
- package/template/agent/skills/rust-developer/references/rust-rules/doc-errors-section.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/doc-examples-section.md +0 -161
- package/template/agent/skills/rust-developer/references/rust-rules/doc-hidden-setup.md +0 -149
- package/template/agent/skills/rust-developer/references/rust-rules/doc-intra-links.md +0 -138
- package/template/agent/skills/rust-developer/references/rust-rules/doc-link-types.md +0 -169
- package/template/agent/skills/rust-developer/references/rust-rules/doc-module-inner.md +0 -116
- package/template/agent/skills/rust-developer/references/rust-rules/doc-panics-section.md +0 -128
- package/template/agent/skills/rust-developer/references/rust-rules/doc-question-mark.md +0 -136
- package/template/agent/skills/rust-developer/references/rust-rules/doc-safety-section.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/err-anyhow-app.md +0 -179
- package/template/agent/skills/rust-developer/references/rust-rules/err-context-chain.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/err-custom-type.md +0 -152
- package/template/agent/skills/rust-developer/references/rust-rules/err-doc-errors.md +0 -145
- package/template/agent/skills/rust-developer/references/rust-rules/err-expect-bugs-only.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/err-from-impl.md +0 -152
- package/template/agent/skills/rust-developer/references/rust-rules/err-lowercase-msg.md +0 -124
- package/template/agent/skills/rust-developer/references/rust-rules/err-no-unwrap-prod.md +0 -115
- package/template/agent/skills/rust-developer/references/rust-rules/err-question-mark.md +0 -151
- package/template/agent/skills/rust-developer/references/rust-rules/err-result-over-panic.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/err-source-chain.md +0 -155
- package/template/agent/skills/rust-developer/references/rust-rules/err-thiserror-lib.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/lint-cargo-metadata.md +0 -138
- package/template/agent/skills/rust-developer/references/rust-rules/lint-deny-correctness.md +0 -107
- package/template/agent/skills/rust-developer/references/rust-rules/lint-missing-docs.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/lint-pedantic-selective.md +0 -118
- package/template/agent/skills/rust-developer/references/rust-rules/lint-rustfmt-check.md +0 -157
- package/template/agent/skills/rust-developer/references/rust-rules/lint-unsafe-doc.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-complexity.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-perf.md +0 -136
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-style.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/lint-warn-suspicious.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/lint-workspace-lints.md +0 -172
- package/template/agent/skills/rust-developer/references/rust-rules/mem-arena-allocator.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/mem-arrayvec.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/mem-assert-type-size.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/mem-avoid-format.md +0 -147
- package/template/agent/skills/rust-developer/references/rust-rules/mem-box-large-variant.md +0 -158
- package/template/agent/skills/rust-developer/references/rust-rules/mem-boxed-slice.md +0 -139
- package/template/agent/skills/rust-developer/references/rust-rules/mem-clone-from.md +0 -147
- package/template/agent/skills/rust-developer/references/rust-rules/mem-compact-string.md +0 -149
- package/template/agent/skills/rust-developer/references/rust-rules/mem-reuse-collections.md +0 -174
- package/template/agent/skills/rust-developer/references/rust-rules/mem-smaller-integers.md +0 -159
- package/template/agent/skills/rust-developer/references/rust-rules/mem-smallvec.md +0 -138
- package/template/agent/skills/rust-developer/references/rust-rules/mem-thinvec.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/mem-with-capacity.md +0 -156
- package/template/agent/skills/rust-developer/references/rust-rules/mem-write-over-format.md +0 -172
- package/template/agent/skills/rust-developer/references/rust-rules/mem-zero-copy.md +0 -164
- package/template/agent/skills/rust-developer/references/rust-rules/name-acronym-word.md +0 -99
- package/template/agent/skills/rust-developer/references/rust-rules/name-as-free.md +0 -104
- package/template/agent/skills/rust-developer/references/rust-rules/name-consts-screaming.md +0 -94
- package/template/agent/skills/rust-developer/references/rust-rules/name-crate-no-rs.md +0 -78
- package/template/agent/skills/rust-developer/references/rust-rules/name-funcs-snake.md +0 -76
- package/template/agent/skills/rust-developer/references/rust-rules/name-into-ownership.md +0 -123
- package/template/agent/skills/rust-developer/references/rust-rules/name-is-has-bool.md +0 -127
- package/template/agent/skills/rust-developer/references/rust-rules/name-iter-convention.md +0 -129
- package/template/agent/skills/rust-developer/references/rust-rules/name-iter-method.md +0 -131
- package/template/agent/skills/rust-developer/references/rust-rules/name-iter-type-match.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/name-lifetime-short.md +0 -86
- package/template/agent/skills/rust-developer/references/rust-rules/name-no-get-prefix.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/name-to-expensive.md +0 -118
- package/template/agent/skills/rust-developer/references/rust-rules/name-type-param-single.md +0 -92
- package/template/agent/skills/rust-developer/references/rust-rules/name-types-camel.md +0 -65
- package/template/agent/skills/rust-developer/references/rust-rules/name-variants-camel.md +0 -101
- package/template/agent/skills/rust-developer/references/rust-rules/opt-bounds-check.md +0 -161
- package/template/agent/skills/rust-developer/references/rust-rules/opt-cache-friendly.md +0 -187
- package/template/agent/skills/rust-developer/references/rust-rules/opt-codegen-units.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/opt-cold-unlikely.md +0 -152
- package/template/agent/skills/rust-developer/references/rust-rules/opt-inline-always-rare.md +0 -141
- package/template/agent/skills/rust-developer/references/rust-rules/opt-inline-never-cold.md +0 -181
- package/template/agent/skills/rust-developer/references/rust-rules/opt-inline-small.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/opt-likely-hint.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/opt-lto-release.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/opt-pgo-profile.md +0 -167
- package/template/agent/skills/rust-developer/references/rust-rules/opt-simd-portable.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/opt-target-cpu.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/own-arc-shared.md +0 -141
- package/template/agent/skills/rust-developer/references/rust-rules/own-borrow-over-clone.md +0 -95
- package/template/agent/skills/rust-developer/references/rust-rules/own-clone-explicit.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/own-copy-small.md +0 -124
- package/template/agent/skills/rust-developer/references/rust-rules/own-cow-conditional.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/own-lifetime-elision.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/own-move-large.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/own-mutex-interior.md +0 -105
- package/template/agent/skills/rust-developer/references/rust-rules/own-rc-single-thread.md +0 -65
- package/template/agent/skills/rust-developer/references/rust-rules/own-refcell-interior.md +0 -97
- package/template/agent/skills/rust-developer/references/rust-rules/own-rwlock-readers.md +0 -122
- package/template/agent/skills/rust-developer/references/rust-rules/own-slice-over-vec.md +0 -119
- package/template/agent/skills/rust-developer/references/rust-rules/perf-black-box-bench.md +0 -153
- package/template/agent/skills/rust-developer/references/rust-rules/perf-chain-avoid.md +0 -136
- package/template/agent/skills/rust-developer/references/rust-rules/perf-collect-into.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/perf-collect-once.md +0 -120
- package/template/agent/skills/rust-developer/references/rust-rules/perf-drain-reuse.md +0 -137
- package/template/agent/skills/rust-developer/references/rust-rules/perf-entry-api.md +0 -134
- package/template/agent/skills/rust-developer/references/rust-rules/perf-extend-batch.md +0 -150
- package/template/agent/skills/rust-developer/references/rust-rules/perf-iter-lazy.md +0 -123
- package/template/agent/skills/rust-developer/references/rust-rules/perf-iter-over-index.md +0 -113
- package/template/agent/skills/rust-developer/references/rust-rules/perf-profile-first.md +0 -175
- package/template/agent/skills/rust-developer/references/rust-rules/perf-release-profile.md +0 -149
- package/template/agent/skills/rust-developer/references/rust-rules/proj-bin-dir.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/proj-flat-small.md +0 -133
- package/template/agent/skills/rust-developer/references/rust-rules/proj-lib-main-split.md +0 -148
- package/template/agent/skills/rust-developer/references/rust-rules/proj-mod-by-feature.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/proj-mod-rs-dir.md +0 -120
- package/template/agent/skills/rust-developer/references/rust-rules/proj-prelude-module.md +0 -155
- package/template/agent/skills/rust-developer/references/rust-rules/proj-pub-crate-internal.md +0 -139
- package/template/agent/skills/rust-developer/references/rust-rules/proj-pub-super-parent.md +0 -135
- package/template/agent/skills/rust-developer/references/rust-rules/proj-pub-use-reexport.md +0 -162
- package/template/agent/skills/rust-developer/references/rust-rules/proj-workspace-deps.md +0 -186
- package/template/agent/skills/rust-developer/references/rust-rules/proj-workspace-large.md +0 -162
- package/template/agent/skills/rust-developer/references/rust-rules/test-arrange-act-assert.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/test-cfg-test-module.md +0 -151
- package/template/agent/skills/rust-developer/references/rust-rules/test-criterion-bench.md +0 -171
- package/template/agent/skills/rust-developer/references/rust-rules/test-descriptive-names.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/test-doctest-examples.md +0 -168
- package/template/agent/skills/rust-developer/references/rust-rules/test-fixture-raii.md +0 -151
- package/template/agent/skills/rust-developer/references/rust-rules/test-integration-dir.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/test-mock-traits.md +0 -189
- package/template/agent/skills/rust-developer/references/rust-rules/test-mockall-mocking.md +0 -226
- package/template/agent/skills/rust-developer/references/rust-rules/test-proptest-properties.md +0 -161
- package/template/agent/skills/rust-developer/references/rust-rules/test-should-panic.md +0 -130
- package/template/agent/skills/rust-developer/references/rust-rules/test-tokio-async.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/test-use-super.md +0 -127
- package/template/agent/skills/rust-developer/references/rust-rules/type-enum-states.md +0 -154
- package/template/agent/skills/rust-developer/references/rust-rules/type-generic-bounds.md +0 -142
- package/template/agent/skills/rust-developer/references/rust-rules/type-never-diverge.md +0 -146
- package/template/agent/skills/rust-developer/references/rust-rules/type-newtype-ids.md +0 -160
- package/template/agent/skills/rust-developer/references/rust-rules/type-newtype-validated.md +0 -159
- package/template/agent/skills/rust-developer/references/rust-rules/type-no-stringly.md +0 -144
- package/template/agent/skills/rust-developer/references/rust-rules/type-option-nullable.md +0 -137
- package/template/agent/skills/rust-developer/references/rust-rules/type-phantom-marker.md +0 -188
- package/template/agent/skills/rust-developer/references/rust-rules/type-repr-transparent.md +0 -143
- package/template/agent/skills/rust-developer/references/rust-rules/type-result-fallible.md +0 -131
- package/template/agent/skills/saas-architect/SKILL.md +0 -139
- package/template/agent/skills/security-engineer/SKILL.md +0 -133
- package/template/agent/skills/seo-specialist/SKILL.md +0 -130
- package/template/agent/skills/solo-founder-ops/SKILL.md +0 -56
package/template/agent/skills/rust-developer/references/rust-rules/async-joinset-structured.md
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# async-joinset-structured
|
|
2
|
-
|
|
3
|
-
> Use `JoinSet` for managing dynamic collections of spawned tasks
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
When spawning a variable number of tasks, collecting `JoinHandle`s in a `Vec` and using `join_all` works but lacks flexibility. `JoinSet` provides a better abstraction: add/remove tasks dynamically, get results as they complete, and abort all on drop. It's the idiomatic way to manage task collections.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
// Manual handle management
|
|
13
|
-
let mut handles: Vec<JoinHandle<Result<Data>>> = Vec::new();
|
|
14
|
-
|
|
15
|
-
for url in urls {
|
|
16
|
-
handles.push(tokio::spawn(fetch(url)));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Wait for all, in order (not as they complete)
|
|
20
|
-
let results = futures::future::join_all(handles).await;
|
|
21
|
-
|
|
22
|
-
// No easy way to cancel all, handle errors progressively, or add more tasks
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Good
|
|
26
|
-
|
|
27
|
-
```rust
|
|
28
|
-
use tokio::task::JoinSet;
|
|
29
|
-
|
|
30
|
-
let mut set = JoinSet::new();
|
|
31
|
-
|
|
32
|
-
for url in urls {
|
|
33
|
-
set.spawn(fetch(url.clone()));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Process results as they complete
|
|
37
|
-
while let Some(result) = set.join_next().await {
|
|
38
|
-
match result {
|
|
39
|
-
Ok(Ok(data)) => process(data),
|
|
40
|
-
Ok(Err(e)) => log::error!("Task failed: {}", e),
|
|
41
|
-
Err(e) => log::error!("Task panicked: {}", e),
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// All tasks done, set is empty
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Dynamic Task Addition
|
|
49
|
-
|
|
50
|
-
```rust
|
|
51
|
-
use tokio::task::JoinSet;
|
|
52
|
-
|
|
53
|
-
async fn worker_pool(mut rx: mpsc::Receiver<Task>) {
|
|
54
|
-
let mut set = JoinSet::new();
|
|
55
|
-
let max_concurrent = 10;
|
|
56
|
-
|
|
57
|
-
loop {
|
|
58
|
-
tokio::select! {
|
|
59
|
-
// Accept new tasks if under limit
|
|
60
|
-
Some(task) = rx.recv(), if set.len() < max_concurrent => {
|
|
61
|
-
set.spawn(process_task(task));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Process completed tasks
|
|
65
|
-
Some(result) = set.join_next() => {
|
|
66
|
-
handle_result(result);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Exit when no tasks and channel closed
|
|
70
|
-
else => break,
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Abort on Drop
|
|
77
|
-
|
|
78
|
-
```rust
|
|
79
|
-
use tokio::task::JoinSet;
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
let mut set = JoinSet::new();
|
|
83
|
-
set.spawn(long_running_task());
|
|
84
|
-
set.spawn(another_task());
|
|
85
|
-
|
|
86
|
-
// Early exit
|
|
87
|
-
return;
|
|
88
|
-
} // JoinSet dropped here - all tasks are aborted!
|
|
89
|
-
|
|
90
|
-
// Explicit abort
|
|
91
|
-
let mut set = JoinSet::new();
|
|
92
|
-
set.spawn(task());
|
|
93
|
-
set.abort_all(); // Cancel all tasks
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Error Handling Pattern
|
|
97
|
-
|
|
98
|
-
```rust
|
|
99
|
-
use tokio::task::JoinSet;
|
|
100
|
-
|
|
101
|
-
async fn fetch_all(urls: &[String]) -> Vec<Result<Data, Error>> {
|
|
102
|
-
let mut set = JoinSet::new();
|
|
103
|
-
let mut results = Vec::new();
|
|
104
|
-
|
|
105
|
-
for url in urls {
|
|
106
|
-
set.spawn(fetch(url.clone()));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
while let Some(join_result) = set.join_next().await {
|
|
110
|
-
let result = match join_result {
|
|
111
|
-
Ok(task_result) => task_result,
|
|
112
|
-
Err(join_error) => {
|
|
113
|
-
if join_error.is_panic() {
|
|
114
|
-
Err(Error::TaskPanicked)
|
|
115
|
-
} else {
|
|
116
|
-
Err(Error::TaskCancelled)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
results.push(result);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
results
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## With Cancellation
|
|
128
|
-
|
|
129
|
-
```rust
|
|
130
|
-
use tokio::task::JoinSet;
|
|
131
|
-
use tokio_util::sync::CancellationToken;
|
|
132
|
-
|
|
133
|
-
async fn run_workers(shutdown: CancellationToken) {
|
|
134
|
-
let mut set = JoinSet::new();
|
|
135
|
-
|
|
136
|
-
for i in 0..4 {
|
|
137
|
-
let token = shutdown.child_token();
|
|
138
|
-
set.spawn(async move {
|
|
139
|
-
loop {
|
|
140
|
-
tokio::select! {
|
|
141
|
-
_ = token.cancelled() => break,
|
|
142
|
-
_ = do_work(i) => {}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Wait for shutdown
|
|
149
|
-
shutdown.cancelled().await;
|
|
150
|
-
|
|
151
|
-
// Abort remaining tasks
|
|
152
|
-
set.abort_all();
|
|
153
|
-
|
|
154
|
-
// Wait for all to finish (drain aborted tasks)
|
|
155
|
-
while set.join_next().await.is_some() {}
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Spawning with Context
|
|
160
|
-
|
|
161
|
-
```rust
|
|
162
|
-
use tokio::task::JoinSet;
|
|
163
|
-
|
|
164
|
-
let mut set: JoinSet<(usize, Result<Data, Error>)> = JoinSet::new();
|
|
165
|
-
|
|
166
|
-
for (index, url) in urls.iter().enumerate() {
|
|
167
|
-
let url = url.clone();
|
|
168
|
-
set.spawn(async move {
|
|
169
|
-
(index, fetch(&url).await)
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Results include their index
|
|
174
|
-
while let Some(result) = set.join_next().await {
|
|
175
|
-
if let Ok((index, data)) = result {
|
|
176
|
-
results[index] = Some(data);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## JoinSet vs join_all
|
|
182
|
-
|
|
183
|
-
| Feature | JoinSet | join_all |
|
|
184
|
-
|---------|---------|----------|
|
|
185
|
-
| Add tasks dynamically | Yes | No |
|
|
186
|
-
| Results as-completed | Yes | No (all at once) |
|
|
187
|
-
| Abort all on drop | Yes | No |
|
|
188
|
-
| Cancel individual | Yes | No |
|
|
189
|
-
| Memory efficient | Yes | Pre-allocates |
|
|
190
|
-
|
|
191
|
-
## See Also
|
|
192
|
-
|
|
193
|
-
- [async-join-parallel](./async-join-parallel.md) - Static concurrent futures
|
|
194
|
-
- [async-cancellation-token](./async-cancellation-token.md) - Cancellation patterns
|
|
195
|
-
- [async-try-join](./async-try-join.md) - Error handling in joins
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
# async-mpsc-queue
|
|
2
|
-
|
|
3
|
-
> Use `mpsc` channels for async message queues between tasks
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
`tokio::sync::mpsc` (multi-producer, single-consumer) is the workhorse channel for async Rust. It provides async send/receive, backpressure via bounded capacity, and efficient cloning of senders. It's the default choice for task-to-task communication.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
use std::sync::mpsc; // Wrong! Blocks the async runtime
|
|
13
|
-
|
|
14
|
-
let (tx, rx) = std::sync::mpsc::channel();
|
|
15
|
-
|
|
16
|
-
tokio::spawn(async move {
|
|
17
|
-
tx.send("hello").unwrap(); // Might block
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
tokio::spawn(async move {
|
|
21
|
-
let msg = rx.recv().unwrap(); // BLOCKS the executor thread!
|
|
22
|
-
});
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Good
|
|
26
|
-
|
|
27
|
-
```rust
|
|
28
|
-
use tokio::sync::mpsc;
|
|
29
|
-
|
|
30
|
-
let (tx, mut rx) = mpsc::channel::<String>(100);
|
|
31
|
-
|
|
32
|
-
tokio::spawn(async move {
|
|
33
|
-
tx.send("hello".to_string()).await.unwrap();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
tokio::spawn(async move {
|
|
37
|
-
while let Some(msg) = rx.recv().await {
|
|
38
|
-
println!("Received: {}", msg);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Sender Cloning
|
|
44
|
-
|
|
45
|
-
```rust
|
|
46
|
-
use tokio::sync::mpsc;
|
|
47
|
-
|
|
48
|
-
let (tx, mut rx) = mpsc::channel::<Event>(100);
|
|
49
|
-
|
|
50
|
-
// Multiple producers
|
|
51
|
-
for i in 0..10 {
|
|
52
|
-
let tx = tx.clone(); // Cheap clone
|
|
53
|
-
tokio::spawn(async move {
|
|
54
|
-
tx.send(Event { source: i }).await.unwrap();
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Drop original sender so channel closes when all clones dropped
|
|
59
|
-
drop(tx);
|
|
60
|
-
|
|
61
|
-
// Consumer
|
|
62
|
-
while let Some(event) = rx.recv().await {
|
|
63
|
-
process(event);
|
|
64
|
-
}
|
|
65
|
-
// Loop exits when all senders dropped
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Message Handler Pattern
|
|
69
|
-
|
|
70
|
-
```rust
|
|
71
|
-
use tokio::sync::mpsc;
|
|
72
|
-
|
|
73
|
-
enum Command {
|
|
74
|
-
Get { key: String, reply: oneshot::Sender<Option<Value>> },
|
|
75
|
-
Set { key: String, value: Value },
|
|
76
|
-
Delete { key: String },
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async fn run_store(mut commands: mpsc::Receiver<Command>) {
|
|
80
|
-
let mut store = HashMap::new();
|
|
81
|
-
|
|
82
|
-
while let Some(cmd) = commands.recv().await {
|
|
83
|
-
match cmd {
|
|
84
|
-
Command::Get { key, reply } => {
|
|
85
|
-
let _ = reply.send(store.get(&key).cloned());
|
|
86
|
-
}
|
|
87
|
-
Command::Set { key, value } => {
|
|
88
|
-
store.insert(key, value);
|
|
89
|
-
}
|
|
90
|
-
Command::Delete { key } => {
|
|
91
|
-
store.remove(&key);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Usage
|
|
98
|
-
async fn client(tx: mpsc::Sender<Command>) -> Option<Value> {
|
|
99
|
-
let (reply_tx, reply_rx) = oneshot::channel();
|
|
100
|
-
|
|
101
|
-
tx.send(Command::Get {
|
|
102
|
-
key: "foo".to_string(),
|
|
103
|
-
reply: reply_tx
|
|
104
|
-
}).await.unwrap();
|
|
105
|
-
|
|
106
|
-
reply_rx.await.unwrap()
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Graceful Shutdown
|
|
111
|
-
|
|
112
|
-
```rust
|
|
113
|
-
async fn worker(mut rx: mpsc::Receiver<Task>, shutdown: CancellationToken) {
|
|
114
|
-
loop {
|
|
115
|
-
tokio::select! {
|
|
116
|
-
_ = shutdown.cancelled() => {
|
|
117
|
-
// Drain remaining messages
|
|
118
|
-
while let Ok(task) = rx.try_recv() {
|
|
119
|
-
process(task).await;
|
|
120
|
-
}
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
Some(task) = rx.recv() => {
|
|
124
|
-
process(task).await;
|
|
125
|
-
}
|
|
126
|
-
else => break, // Channel closed
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## WeakSender for Optional Producers
|
|
133
|
-
|
|
134
|
-
```rust
|
|
135
|
-
use tokio::sync::mpsc;
|
|
136
|
-
|
|
137
|
-
let (tx, mut rx) = mpsc::channel::<Message>(100);
|
|
138
|
-
let weak = tx.downgrade(); // Doesn't keep channel alive
|
|
139
|
-
|
|
140
|
-
tokio::spawn(async move {
|
|
141
|
-
// Strong sender - keeps channel alive
|
|
142
|
-
tx.send("from strong".into()).await.unwrap();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
tokio::spawn(async move {
|
|
146
|
-
// Weak sender - may fail if strong senders dropped
|
|
147
|
-
if let Some(tx) = weak.upgrade() {
|
|
148
|
-
tx.send("from weak".into()).await.unwrap();
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Permit Pattern
|
|
154
|
-
|
|
155
|
-
```rust
|
|
156
|
-
// Reserve slot before preparing message
|
|
157
|
-
let permit = tx.reserve().await?;
|
|
158
|
-
|
|
159
|
-
// Now we have guaranteed capacity
|
|
160
|
-
let message = expensive_to_create_message();
|
|
161
|
-
permit.send(message); // Never fails
|
|
162
|
-
|
|
163
|
-
// Useful when message creation is expensive
|
|
164
|
-
// and you don't want to create it if channel is full
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## See Also
|
|
168
|
-
|
|
169
|
-
- [async-bounded-channel](./async-bounded-channel.md) - Why bounded channels
|
|
170
|
-
- [async-oneshot-response](./async-oneshot-response.md) - Request-response with oneshot
|
|
171
|
-
- [async-broadcast-pubsub](./async-broadcast-pubsub.md) - Multiple consumers
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
# async-no-lock-await
|
|
2
|
-
|
|
3
|
-
> Never hold `Mutex`/`RwLock` across `.await`
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
Holding a lock across an `.await` point can cause deadlocks and severely hurt performance. The task may be suspended while holding the lock, blocking all other tasks waiting for it - potentially indefinitely.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
use tokio::sync::Mutex;
|
|
13
|
-
|
|
14
|
-
async fn bad_update(state: &Mutex<State>) {
|
|
15
|
-
let mut guard = state.lock().await;
|
|
16
|
-
|
|
17
|
-
// BAD: Lock held across await!
|
|
18
|
-
let data = fetch_from_network().await;
|
|
19
|
-
|
|
20
|
-
guard.value = data;
|
|
21
|
-
} // Lock finally released
|
|
22
|
-
|
|
23
|
-
// This can deadlock or starve other tasks
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Good
|
|
27
|
-
|
|
28
|
-
```rust
|
|
29
|
-
use tokio::sync::Mutex;
|
|
30
|
-
|
|
31
|
-
async fn good_update(state: &Mutex<State>) {
|
|
32
|
-
// Fetch data BEFORE taking the lock
|
|
33
|
-
let data = fetch_from_network().await;
|
|
34
|
-
|
|
35
|
-
// Lock only for the quick update
|
|
36
|
-
let mut guard = state.lock().await;
|
|
37
|
-
guard.value = data;
|
|
38
|
-
} // Lock released immediately
|
|
39
|
-
|
|
40
|
-
// Alternative: Clone data out, process, then update
|
|
41
|
-
async fn good_update_v2(state: &Mutex<State>) {
|
|
42
|
-
// Extract what we need
|
|
43
|
-
let id = {
|
|
44
|
-
let guard = state.lock().await;
|
|
45
|
-
guard.id.clone()
|
|
46
|
-
}; // Lock released!
|
|
47
|
-
|
|
48
|
-
// Do async work without lock
|
|
49
|
-
let data = fetch_by_id(id).await;
|
|
50
|
-
|
|
51
|
-
// Quick update
|
|
52
|
-
state.lock().await.value = data;
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## The Problem Visualized
|
|
57
|
-
|
|
58
|
-
```rust
|
|
59
|
-
// Task A:
|
|
60
|
-
let guard = mutex.lock().await; // Acquires lock
|
|
61
|
-
expensive_io().await; // Suspended, still holding lock!
|
|
62
|
-
// ... many milliseconds pass ...
|
|
63
|
-
drop(guard); // Finally releases
|
|
64
|
-
|
|
65
|
-
// Task B, C, D:
|
|
66
|
-
let guard = mutex.lock().await; // All blocked waiting for A!
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Patterns for Extraction
|
|
70
|
-
|
|
71
|
-
```rust
|
|
72
|
-
use tokio::sync::Mutex;
|
|
73
|
-
|
|
74
|
-
// Pattern 1: Clone out, process, update
|
|
75
|
-
async fn pattern_clone(state: &Mutex<State>) {
|
|
76
|
-
let config = state.lock().await.config.clone();
|
|
77
|
-
let result = process_with_io(&config).await;
|
|
78
|
-
state.lock().await.result = result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Pattern 2: Compute closure, apply
|
|
82
|
-
async fn pattern_closure(state: &Mutex<State>) {
|
|
83
|
-
let update = compute_update().await;
|
|
84
|
-
|
|
85
|
-
state.lock().await.apply(update);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Pattern 3: Message passing
|
|
89
|
-
async fn pattern_message(
|
|
90
|
-
state: &Mutex<State>,
|
|
91
|
-
tx: mpsc::Sender<Update>,
|
|
92
|
-
) {
|
|
93
|
-
let update = compute_update().await;
|
|
94
|
-
tx.send(update).await.unwrap();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Separate task handles updates
|
|
98
|
-
async fn state_manager(
|
|
99
|
-
state: Arc<Mutex<State>>,
|
|
100
|
-
mut rx: mpsc::Receiver<Update>,
|
|
101
|
-
) {
|
|
102
|
-
while let Some(update) = rx.recv().await {
|
|
103
|
-
state.lock().await.apply(update);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Using RwLock
|
|
109
|
-
|
|
110
|
-
```rust
|
|
111
|
-
use tokio::sync::RwLock;
|
|
112
|
-
|
|
113
|
-
async fn read_heavy(state: &RwLock<State>) {
|
|
114
|
-
// Multiple readers OK, but still don't hold across await
|
|
115
|
-
let value = {
|
|
116
|
-
let guard = state.read().await;
|
|
117
|
-
guard.value.clone()
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Process without lock
|
|
121
|
-
let result = process(value).await;
|
|
122
|
-
|
|
123
|
-
// Write lock for update
|
|
124
|
-
state.write().await.result = result;
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## std::sync::Mutex vs tokio::sync::Mutex
|
|
129
|
-
|
|
130
|
-
```rust
|
|
131
|
-
// std::sync::Mutex: Blocks the entire thread
|
|
132
|
-
// - Use for quick, CPU-only operations
|
|
133
|
-
// - NEVER use in async code with await inside
|
|
134
|
-
|
|
135
|
-
// tokio::sync::Mutex: Async-aware, yields to runtime
|
|
136
|
-
// - Use in async code
|
|
137
|
-
// - Still don't hold across await points!
|
|
138
|
-
|
|
139
|
-
// std::sync::Mutex in async (quick operation, OK):
|
|
140
|
-
async fn quick_update(state: &std::sync::Mutex<State>) {
|
|
141
|
-
state.lock().unwrap().counter += 1; // No await, OK
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// tokio::sync::Mutex (must use if lock scope has await):
|
|
145
|
-
async fn must_await_inside(state: &tokio::sync::Mutex<State>) {
|
|
146
|
-
let mut guard = state.lock().await;
|
|
147
|
-
// Only if you REALLY need the lock during async op
|
|
148
|
-
// (usually you don't - redesign instead)
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## See Also
|
|
153
|
-
|
|
154
|
-
- [async-spawn-blocking](async-spawn-blocking.md) - Use spawn_blocking for CPU work
|
|
155
|
-
- [async-clone-before-await](async-clone-before-await.md) - Clone data before await
|
|
156
|
-
- [anti-lock-across-await](anti-lock-across-await.md) - Anti-pattern reference
|
package/template/agent/skills/rust-developer/references/rust-rules/async-oneshot-response.md
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# async-oneshot-response
|
|
2
|
-
|
|
3
|
-
> Use `oneshot` channel for request-response patterns
|
|
4
|
-
|
|
5
|
-
## Why It Matters
|
|
6
|
-
|
|
7
|
-
When one task needs to send a request and wait for exactly one response, `oneshot` is the perfect fit. It's a single-use channel optimized for this pattern—no buffering, no clone overhead. Combined with `mpsc`, it enables clean actor-style message passing.
|
|
8
|
-
|
|
9
|
-
## Bad
|
|
10
|
-
|
|
11
|
-
```rust
|
|
12
|
-
// Using mpsc for single response - wasteful
|
|
13
|
-
let (tx, mut rx) = mpsc::channel::<Response>(1);
|
|
14
|
-
send_request().await;
|
|
15
|
-
let response = rx.recv().await.unwrap();
|
|
16
|
-
// Channel persists, could accidentally receive more
|
|
17
|
-
|
|
18
|
-
// Using shared state - complex
|
|
19
|
-
let result = Arc::new(Mutex::new(None));
|
|
20
|
-
send_request(result.clone()).await;
|
|
21
|
-
while result.lock().await.is_none() {
|
|
22
|
-
tokio::time::sleep(Duration::from_millis(10)).await; // Polling!
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Good
|
|
27
|
-
|
|
28
|
-
```rust
|
|
29
|
-
use tokio::sync::oneshot;
|
|
30
|
-
|
|
31
|
-
let (tx, rx) = oneshot::channel::<Response>();
|
|
32
|
-
|
|
33
|
-
// Send request with reply channel
|
|
34
|
-
send_request(Request { data, reply: tx }).await;
|
|
35
|
-
|
|
36
|
-
// Wait for response
|
|
37
|
-
let response = rx.await?;
|
|
38
|
-
|
|
39
|
-
// Channel is consumed - can't accidentally reuse
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Request-Response Pattern
|
|
43
|
-
|
|
44
|
-
```rust
|
|
45
|
-
use tokio::sync::{mpsc, oneshot};
|
|
46
|
-
|
|
47
|
-
enum Request {
|
|
48
|
-
Get {
|
|
49
|
-
key: String,
|
|
50
|
-
reply: oneshot::Sender<Option<Value>>,
|
|
51
|
-
},
|
|
52
|
-
Set {
|
|
53
|
-
key: String,
|
|
54
|
-
value: Value,
|
|
55
|
-
reply: oneshot::Sender<bool>,
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Service handler
|
|
60
|
-
async fn service(mut rx: mpsc::Receiver<Request>) {
|
|
61
|
-
let mut store = HashMap::new();
|
|
62
|
-
|
|
63
|
-
while let Some(req) = rx.recv().await {
|
|
64
|
-
match req {
|
|
65
|
-
Request::Get { key, reply } => {
|
|
66
|
-
let value = store.get(&key).cloned();
|
|
67
|
-
let _ = reply.send(value); // Ignore if receiver dropped
|
|
68
|
-
}
|
|
69
|
-
Request::Set { key, value, reply } => {
|
|
70
|
-
store.insert(key, value);
|
|
71
|
-
let _ = reply.send(true);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Client
|
|
78
|
-
async fn get_value(tx: &mpsc::Sender<Request>, key: &str) -> Option<Value> {
|
|
79
|
-
let (reply_tx, reply_rx) = oneshot::channel();
|
|
80
|
-
|
|
81
|
-
tx.send(Request::Get {
|
|
82
|
-
key: key.to_string(),
|
|
83
|
-
reply: reply_tx,
|
|
84
|
-
}).await.ok()?;
|
|
85
|
-
|
|
86
|
-
reply_rx.await.ok()?
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## With Timeout
|
|
91
|
-
|
|
92
|
-
```rust
|
|
93
|
-
use tokio::time::{timeout, Duration};
|
|
94
|
-
|
|
95
|
-
async fn request_with_timeout(
|
|
96
|
-
tx: &mpsc::Sender<Request>,
|
|
97
|
-
key: &str,
|
|
98
|
-
) -> Result<Value, Error> {
|
|
99
|
-
let (reply_tx, reply_rx) = oneshot::channel();
|
|
100
|
-
|
|
101
|
-
tx.send(Request::Get {
|
|
102
|
-
key: key.to_string(),
|
|
103
|
-
reply: reply_tx,
|
|
104
|
-
}).await.map_err(|_| Error::ServiceDown)?;
|
|
105
|
-
|
|
106
|
-
timeout(Duration::from_secs(5), reply_rx)
|
|
107
|
-
.await
|
|
108
|
-
.map_err(|_| Error::Timeout)?
|
|
109
|
-
.map_err(|_| Error::ServiceDown)?
|
|
110
|
-
.ok_or(Error::NotFound)
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Error Handling
|
|
115
|
-
|
|
116
|
-
```rust
|
|
117
|
-
use tokio::sync::oneshot;
|
|
118
|
-
|
|
119
|
-
let (tx, rx) = oneshot::channel::<String>();
|
|
120
|
-
|
|
121
|
-
// Sender dropped without sending
|
|
122
|
-
drop(tx);
|
|
123
|
-
match rx.await {
|
|
124
|
-
Ok(value) => println!("Got: {}", value),
|
|
125
|
-
Err(oneshot::error::RecvError { .. }) => {
|
|
126
|
-
println!("Sender dropped");
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Receiver dropped before send
|
|
131
|
-
let (tx, rx) = oneshot::channel::<String>();
|
|
132
|
-
drop(rx);
|
|
133
|
-
match tx.send("hello".to_string()) {
|
|
134
|
-
Ok(()) => println!("Sent"),
|
|
135
|
-
Err(value) => println!("Receiver dropped, value: {}", value),
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Closed Detection
|
|
140
|
-
|
|
141
|
-
```rust
|
|
142
|
-
// Check if receiver is still waiting
|
|
143
|
-
let (tx, rx) = oneshot::channel::<i32>();
|
|
144
|
-
|
|
145
|
-
// In producer
|
|
146
|
-
if tx.is_closed() {
|
|
147
|
-
println!("Receiver already gone, skip expensive computation");
|
|
148
|
-
} else {
|
|
149
|
-
let result = expensive_computation();
|
|
150
|
-
tx.send(result).ok();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Async wait for close
|
|
154
|
-
let tx_clone = tx.clone(); // Note: can't actually clone, just showing concept
|
|
155
|
-
tokio::select! {
|
|
156
|
-
_ = tx.closed() => println!("Receiver dropped"),
|
|
157
|
-
result = compute() => { tx.send(result).ok(); }
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Response Type Wrapper
|
|
162
|
-
|
|
163
|
-
```rust
|
|
164
|
-
// Standardize request-response pattern
|
|
165
|
-
struct RpcRequest<Req, Res> {
|
|
166
|
-
request: Req,
|
|
167
|
-
reply: oneshot::Sender<Res>,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
impl<Req, Res> RpcRequest<Req, Res> {
|
|
171
|
-
fn new(request: Req) -> (Self, oneshot::Receiver<Res>) {
|
|
172
|
-
let (tx, rx) = oneshot::channel();
|
|
173
|
-
(RpcRequest { request, reply: tx }, rx)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
fn respond(self, response: Res) {
|
|
177
|
-
let _ = self.reply.send(response);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Usage
|
|
182
|
-
let (req, rx) = RpcRequest::new(GetUser { id: 42 });
|
|
183
|
-
tx.send(req).await?;
|
|
184
|
-
let user = rx.await?;
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## See Also
|
|
188
|
-
|
|
189
|
-
- [async-mpsc-queue](./async-mpsc-queue.md) - Pair with oneshot for request-response
|
|
190
|
-
- [async-bounded-channel](./async-bounded-channel.md) - Channel sizing
|
|
191
|
-
- [async-select-racing](./async-select-racing.md) - Timeout patterns
|