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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
//! This module provides an interface to use [Future]s on top of the CRT's event loops and event
//! loop groups.
use std::fmt::Debug;
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use futures::channel::oneshot;
use futures::future::BoxFuture;
use futures::task::ArcWake;
use futures::{FutureExt, TryFutureExt};
use thiserror::Error;
use crate::common::allocator::Allocator;
use crate::common::task_scheduler::{Task, TaskScheduler, TaskStatus};
/// Handle to a spawned future. Can be converted into a [Future] that completes when the task finishes.
#[derive(Debug)]
pub struct FutureJoinHandle<T: Send + 'static> {
inner: Arc<Mutex<Option<FutureTaskInner<T>>>>,
receiver: oneshot::Receiver<Result<T, JoinError>>,
}
impl<T> FutureJoinHandle<T>
where
T: Send + 'static,
{
/// Convert this handle into a future that completes when the spawned future does.
pub fn into_future(self) -> impl Future<Output = Result<T, JoinError>> {
self.receiver
.unwrap_or_else(|oneshot::Canceled| Err(JoinError::Canceled))
}
/// Wait for a result, blocking the current thread.
pub fn wait(self) -> Result<T, JoinError> {
futures::executor::block_on(self.into_future())
}
/// Cancel this Future. This is best-effort: the Future can continue to run in the background
/// after this until the next time that it gets woken up (probably by some CRT callback deep
/// down, but it could be anything that calls wake).
///
/// However, this _does_ synchronously drop the [Future] provided to [EventLoopGroup::spawn_future].
/// This frees any resources associated with that Future before cancel returns.
pub fn cancel(self) {
let mut locked = self.inner.lock().unwrap();
// Cancel the task by dropping the [FutureTaskInner] held by the mutex. The next time the
// task is woken up, it will look as though the future has already completed and won't
// be able to make any progress.
if let Some(inner) = locked.take() {
std::mem::drop(inner);
}
}
}
/// Internal bookkeeping about a not-yet-completed future.
struct FutureTaskInner<T: Send + 'static> {
/// The [Future] from the client.
future: BoxFuture<'static, T>,
/// A channel to write the result to when the future completes.
result_channel: oneshot::Sender<Result<T, JoinError>>,
}
/// Manual [Debug] implementation since [BoxFuture] doesn't implement Debug.
impl<T: Debug + Send + 'static> Debug for FutureTaskInner<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FutureTaskInner")
.field("future", &(&self.future as *const BoxFuture<'static, T>))
.field("result_channel", &self.result_channel)
.finish()
}
}
/// Implements [ArcWake] for Futures spawned on an event loop group.
struct FutureTaskWaker<S: TaskScheduler, T: Send + 'static> {
/// Inner information about the task if it hasn't completed yet. Held behind a mutex (since wake
/// can be called from different threads). If the future is not None, then it is still busy
/// and we should call poll again when woken. If it is None, the future has already
/// finished executing.
inner: Arc<Mutex<Option<FutureTaskInner<T>>>>,
/// The [TaskScheduler] that knows how to arrange for [Task]s to be run by the CRT.
scheduler: S,
}
impl<S: TaskScheduler, T: Send + 'static> FutureTaskWaker<S, T> {
/// Finish this task with an error. Does not call [Future::poll], and prevents any future
/// wake-ups from calling poll either.
fn finish_with_error(arc_self: &Arc<Self>, error: JoinError) {
let mut locked = arc_self.inner.lock().unwrap();
if let Some(inner) = locked.take() {
// Drop the future before replying, so the client can rely on resources from future
// being freed before continuing execution.
std::mem::drop(inner.future);
let _ = inner.result_channel.send(Err(error));
}
}
/// Poll the future associated with this FutureTask. If the future has already completed,
/// this does nothing. If it hasn't, then it calls Future::poll. If the future is ready, write
/// the result back to the synchronous channel. Otherwise wait for someone to poll again.
fn poll(arc_self: &Arc<Self>) {
// Lock to read the future and call poll on it. Note this will block other tasks if wake was
// called multiple times.
let mut locked = arc_self.inner.lock().unwrap();
// Only do anything if there is a future to poll (i.e., it hasn't completed yet).
if let Some(mut inner) = locked.take() {
// Otherwise poll the client-provided future.
let waker = futures::task::waker_ref(arc_self);
let context = &mut Context::from_waker(&waker);
match Future::poll(inner.future.as_mut(), context) {
Poll::Ready(value) => {
// Drop the future before replying on the channel. This guarantees that the
// client can rely on values owned by the Future / closure will be dropped
// once the channel has a result on it.
std::mem::drop(inner.future);
let _ = inner.result_channel.send(Ok(value));
}
Poll::Pending => {
// The future isn't done, so put inner back into the [FutureTask] so that it
// will still be there the next time this is polled.
*locked = Some(inner);
}
}
}
}
}
impl<S: TaskScheduler, T: Send + 'static> ArcWake for FutureTaskWaker<S, T> {
/// Wakes the FutureTask by creating a new [Task] to call poll, and scheduling it on the event
/// loop group associated with the task.
fn wake_by_ref(arc_self: &Arc<Self>) {
let task_arc_self = arc_self.clone();
// Create a [Task] that calls poll. If the CRT tells us that the task is canceled, finishes
// the future with an error.
let task = Task::init(
&Allocator::default(),
move |status| match status {
TaskStatus::RunReady => FutureTaskWaker::poll(&task_arc_self),
TaskStatus::Canceled => FutureTaskWaker::finish_with_error(&task_arc_self, JoinError::Canceled),
},
"FutureTaskWaker_wake_by_ref",
);
// Schedule the task. If it fails, finish with the error.
match arc_self.scheduler.schedule_task_now(task) {
Ok(()) => {}
Err(err) => FutureTaskWaker::finish_with_error(arc_self, err.into()),
}
}
}
/// Trait for things that can spawn futures. For now this is just an extension to the [TaskScheduler] trait.
pub trait FutureSpawner: crate::private::Sealed {
/// Spawn the given [Future] to run asynchronously. This [TaskScheduler] is responsible for
/// determining how to run [Task]s in the CRT. This returns a [FutureJoinHandle] that can be
/// used to cancel, block on, or await the result.
///
/// - If the scheduler is an [EventLoopGroup], then every time the Future is polled, the CRT
/// will determine the best [EventLoop] to run on.
///
/// - If the scheduler is an [EventLoop], the Future will be pinned to the core that [EventLoop]
/// runs on.
///
/// - If the scheduler is [BlockingTaskScheduler], then the thread that calls wake will block on
/// [Future::poll]. (This is undesirable except in tests, and could cause deadlocks or other
/// issues when combined with other CRT functionality.)
fn spawn_future<T>(&self, future: impl Future<Output = T> + Send + 'static) -> FutureJoinHandle<T>
where
T: Send + 'static;
}
impl<S: TaskScheduler + Clone> FutureSpawner for S {
fn spawn_future<T>(&self, future: impl Future<Output = T> + Send + 'static) -> FutureJoinHandle<T>
where
T: Send + 'static,
{
let future = future.boxed();
let (tx, rx) = oneshot::channel();
let task_inner = Arc::new(Mutex::new(Some(FutureTaskInner {
future,
result_channel: tx,
})));
let waker = futures::task::waker(Arc::new(FutureTaskWaker {
inner: task_inner.clone(),
scheduler: self.clone(),
}));
// Inject a wake to kick-start the Future's execution. (This internally uses the TaskScheduler
// to call poll, so this won't block unless the scheduler does.)
waker.wake_by_ref();
FutureJoinHandle {
inner: task_inner,
receiver: rx,
}
}
}
/// Future completion failures
#[derive(Error, Debug)]
pub enum JoinError {
/// The task was cancelled
#[error("The task was cancelled")]
Canceled,
/// Internal error from the AWS Common Runtime
#[error("Internal CRT error: {0}")]
InternalError(#[from] crate::common::error::Error),
}
#[cfg(test)]
mod test {
use futures::executor::block_on;
use futures::future::join_all;
use std::sync::atomic::{AtomicBool, AtomicU64};
use std::time::Duration;
use super::*;
use crate::common::allocator::Allocator;
use crate::io::event_loop::{EventLoopGroup, EventLoopTimer};
use std::sync::atomic::Ordering;
/// Test that running a small future on an event loop works correctly.
#[test]
fn test_simple_future() {
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
let handle = el_group.spawn_future(async {
println!("Hello from the future");
});
handle.wait().unwrap();
}
/// Test that spawns a lot of futures and waits for them all to finish, parameterized by the FutureSpawner.
fn test_join_all_futures(scheduler: &impl FutureSpawner) {
const NUM_FUTURES: u64 = 50_000;
let counter = Arc::new(AtomicU64::new(0));
let mut future_handles = vec![];
for _ in 0..NUM_FUTURES {
let counter = counter.clone();
future_handles.push(scheduler.spawn_future(async move {
counter.fetch_add(1, Ordering::SeqCst);
}))
}
let results = block_on(join_all(future_handles.into_iter().map(FutureJoinHandle::into_future)));
assert_eq!(
Arc::strong_count(&counter),
1,
"all references to the counter except ours should be dropped"
);
// Check that all Futures completed successfully.
let results: Result<(), JoinError> = results.into_iter().collect();
results.expect("one or more futures failed");
assert_eq!(counter.load(Ordering::SeqCst), NUM_FUTURES);
}
/// test_join_all_futures using a pinned EventLoop.
#[test]
fn test_join_all_futures_event_loop() {
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
let event_loop = el_group.get_next_loop().unwrap();
test_join_all_futures(&event_loop);
}
/// test_join_all_futures using an EventLoopGroup.
#[test]
fn test_join_all_futures_event_loop_group() {
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
test_join_all_futures(&el_group);
}
/// Test that cancelling a future works.
#[test]
fn test_cancel_future() {
let allocator = Allocator::default();
let el_group = EventLoopGroup::new_default(&allocator, None, || {}).unwrap();
// Create a long timer to delay the future for some time.
let timer = EventLoopTimer::new(&el_group.get_next_loop().unwrap(), Duration::from_secs(20));
// Set up a flag that will set to true when the timer is finished.
let flag = Arc::new(AtomicBool::new(false));
// Spawn a future that waits for the timer to be done then stores true to the flag.
let future_handle = {
let flag = flag.clone();
el_group.spawn_future(async move {
timer.await.expect("failed while awaiting timer");
flag.store(true, Ordering::SeqCst);
})
};
assert_eq!(
Arc::strong_count(&flag),
2,
"there should be 2 references to flag: ours and the Future's"
);
// Sleep this thread some amount of time (enough for the timer to start running after the first poll).
std::thread::sleep(Duration::from_secs(1));
// Cancel the future
future_handle.cancel();
assert_eq!(
Arc::strong_count(&flag),
1,
"The Future should be dropped at this point"
);
assert!(
!flag.load(Ordering::SeqCst),
"flag should still be false after cancellation"
);
}
}