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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! Worker pool and related traits and primitives.
//!
//! [WorkerPools](WorkerPool) are used to schedule and execute units of "work" with varying
//! priorities. Each unit of [Work] is a specific task that will be executed from start to end. Note
//! that these tasks are not async and therefore can't be context switched away from in the middle
//! of execution. If you wish to execute async tasks, see the [Executor](crate::util::executor)
//! abstraction which builds on top of worker pools.
//!
//! A basic usage of [WorkerPool] looks like the following:
//! ```
//! use gtether::worker::{WorkQueue, WorkerPool};
//! # use smol::future;
//! use std::sync::Arc;
//!
//! # future::block_on(async move {
//! // 1. create the worker pool
//! let workers = WorkerPool::builder().start();
//!
//! // 2. configure the worker pool with a work source
//! let queue = Arc::new(WorkQueue::new());
//! workers.insert_source((), queue.clone());
//!
//! // 3. execute a task
//! let task = queue.execute(|| 1 + 1);
//! let output = task.await.unwrap();
//! assert_eq!(output, 2);
//! # });
//! ```
//!
//! # Configuration
//!
//! By default, [worker pools](WorkerPool) use an amount of workers equal to
//! [`std::thread::available_parallelism()`]. If a different worker count is desired, it can be
//! configured with [`WorkerPoolBuilder::worker_count()`]:
//!
//! ```no_run
//! use gtether::worker::WorkerPool;
//!
//! // Use a singular worker
//! let workers = WorkerPool::<isize>::builder()
//! .worker_count(1.try_into().unwrap())
//! .start();
//!
//! // Use 4 workers
//! let workers = WorkerPool::<isize>::builder()
//! .worker_count(4.try_into().unwrap())
//! .start();
//! ```
//!
//! Worker threads are named "worker-<idx>" by default, where "<idx>" starts at `0`. To use a
//! different naming scheme, use [`WorkerPoolBuilder::prefix()`]:
//!
//! ```no_run
//! use gtether::worker::WorkerPool;
//!
//! // Name workers "custom-work-thread-<idx>"
//! let workers = WorkerPool::<isize>::builder()
//! .prefix("custom-work-thread")
//! .start();
//! ```
//!
//! # Submitting Work
//!
//! Work cannot be submitted directly to [worker pools](WorkerPool). Instead, worker pools poll for
//! work from [work sources](WorkSource), which can be inserted into worker pools with a given
//! priority.
//!
//! For general use cases, a basic [work queue](WorkQueue) that implements [WorkSource] is provided.
//!
//! ## Execution Order
//!
//! Work sources are polled in order of the priority they are inserted with, with higher priorities
//! being polled first. If multiple sources share the same priority, they are grouped in a bucket
//! that is polled in round-robin order. Note that the last source that was polled in a bucket is
//! kept track of per-thread, so that the next poll of that bucket continues where the last left
//! off. This ensures that every source in the same bucket is given a fair chance to poll for work.
use Deref;
use ;
use Waker;
pub use ;
pub use ;
/// A single unit of work to be submitted to a [WorkerPool].
///
/// Note that work items must have an output type of `()` to be executed by a [WorkerPool]. If your
/// work item has some other type of output, you can convert it via [`WorkTask::spawn()`] - most
/// [WorkSource] implementations will do this for you.
/// Errors that can occur when looking for [Work] items from a [WorkSource].
pub type FindWorkResult = ;
/// A source of [Work] items that can be inserted into a [WorkerPool].
///
/// A basic queue implementation is provided via [WorkQueue], so this trait only needs to be
/// implemented for more advanced and/or bespoke implementations.
///
/// WorkSource implementations are provided for [Arc] and [Weak] containers that wrap other
/// WorkSource implementations.
///
/// # Implementation
///
/// Work sources are expected to provide [Work] items from a shared reference, and to store worker
/// wakers when workers sleep from lack of work. In the event that new work becomes available from
/// a source, that source is expected to wake the last waker that was provided to it.
///
/// Example implementation:
/// ```
/// use gtether::worker::{FindWorkError, FindWorkResult, Work, WorkSource};
/// use std::collections::VecDeque;
/// use std::sync::Mutex;
/// use std::task::Waker;
///
/// struct MyWorkSource {
/// queue: Mutex<VecDeque<Box<dyn Work<Output=()>>>>,
/// waker: Mutex<Option<Waker>>,
/// }
///
/// impl MyWorkSource {
/// fn push_work(&self, work: Box<dyn Work<Output=()>>) {
/// let mut queue = self.queue.lock().unwrap();
/// let mut waker = self.waker.lock().unwrap();
/// queue.push_back(work);
/// // There's new work, so wake the waker if it has been provided
/// waker.take().map(|w| w.wake());
/// }
/// }
///
/// impl WorkSource for MyWorkSource {
/// fn find_work(&self) -> FindWorkResult {
/// let mut queue = self.queue.lock().unwrap();
/// queue.pop_front().ok_or(FindWorkError::NoWork)
/// }
///
/// fn set_worker_waker(&self, waker: &Waker) {
/// *self.waker.lock().unwrap() = Some(waker.clone());
/// }
/// }
/// ```