1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! Opting a fan-out into real parallelism by delegating task spawning to the consumer.
use Future;
use Pin;
/// Work handed to a [`TaskSpawner`] to run to completion.
pub type SpawnedTask = ;
/// A handle that resolves once a spawned task has finished, however it finished. A task
/// that panicked or was aborted must still resolve its handle rather than hanging.
pub type SpawnHandle = ;
/// Runs fan-out branches on a real executor instead of on the caller's task.
///
/// orka depends on no async runtime and never spawns, so by default a
/// [`FanOut`](crate::FanOut) polls its branches cooperatively: they interleave at await
/// points but share one task, and a branch that blocks the thread stalls its siblings.
/// Supplying a spawner lifts that: each branch becomes a task on your runtime, so branches
/// run on different threads and CPU-bound work no longer serialises.
///
/// For tokio, enable orka's `tokio` feature and use the shipped [`TokioSpawner`]:
///
/// ```ignore
/// let results = FanOut::new(pipeline)
/// .spawner(Arc::new(TokioSpawner))
/// .max_concurrent(8)
/// .run(items)
/// .await;
/// ```
///
/// For anything else the implementation is short enough to paste:
///
/// ```ignore
/// struct MySpawner;
///
/// impl TaskSpawner for MySpawner {
/// fn spawn(&self, task: SpawnedTask) -> SpawnHandle {
/// let handle = my_runtime::spawn(task);
/// // Resolve on panic or abort too; orka reports that branch as lost rather than hanging.
/// Box::pin(async move { let _ = handle.await; })
/// }
/// }
/// ```
///
/// Two behaviours change when a spawner is in play, both worth knowing:
///
/// - **A panicking branch is contained.** Cooperatively, a branch that panics unwinds the
/// whole fan-out and the caller with it. Spawned, the runtime catches it, the handle
/// resolves without a result, and orka reports that branch as failed with
/// [`OrkaError::FanOutBranchLost`](crate::OrkaError::FanOutBranchLost). The other
/// branches are unaffected.
/// - **Dropping the fan-out no longer cancels in-flight work.** Cooperative branches are
/// owned by the fan-out future and stop when it is dropped; spawned ones belong to the
/// runtime and keep running detached.
///
/// Branch *starting* is still governed by [`max_concurrent`](crate::FanOut::max_concurrent)
/// and by fail-fast, because a branch only spawns when the fan-out first polls it.
/// A [`TaskSpawner`] backed by `tokio::spawn`. Requires orka's `tokio` feature.
///
/// The feature pulls in tokio with only its `rt` feature, which is all `tokio::spawn`
/// needs. orka itself still depends on no runtime; this is opt-in.
///
/// Like any use of `tokio::spawn`, this must be called from within a tokio runtime, which
/// a fan-out being awaited inside one always is.
;