Skip to content

Commit c24322f

Browse files
committed
use ParkThread in LocalPool
- split all state but park into separate struct - various "poll" methods need to call park(Poll) as it might change the readiness of futures (expired timers, IO ready, ...) or spawn new futures (although ParkThread::park(Poll) won't)
1 parent 927e6e4 commit c24322f

1 file changed

Lines changed: 145 additions & 135 deletions

File tree

futures-executor/src/local_pool.rs

Lines changed: 145 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,85 @@
11
use crate::enter;
2+
use crate::park::{Park, ParkDuration};
3+
use crate::park_thread::ParkThread;
24
use futures_core::future::Future;
35
use futures_core::stream::Stream;
46
use futures_core::task::{Context, Poll};
5-
use futures_task::{waker_ref, ArcWake};
67
use futures_task::{FutureObj, LocalFutureObj, LocalSpawn, Spawn, SpawnError};
78
use futures_util::stream::FuturesUnordered;
89
use futures_util::stream::StreamExt;
910
use std::cell::RefCell;
1011
use std::ops::{Deref, DerefMut};
1112
use std::pin::pin;
1213
use std::rc::{Rc, Weak};
13-
use std::sync::{
14-
atomic::{AtomicBool, Ordering},
15-
Arc,
16-
};
17-
use std::thread::{self, Thread};
1814
use std::vec::Vec;
1915

16+
// state outside park
17+
#[derive(Debug)]
18+
struct State {
19+
pool: FuturesUnordered<LocalFutureObj<'static, ()>>,
20+
incoming: Rc<Incoming>,
21+
}
22+
23+
impl State {
24+
/// Poll `self.pool`, re-filling it with any newly-spawned tasks.
25+
/// Repeat until either the pool is empty, or it returns `Pending`.
26+
///
27+
/// Returns `Ready` if the pool was empty, and `Pending` otherwise.
28+
///
29+
/// NOTE: the pool may call `wake`, so `Pending` doesn't necessarily
30+
/// mean that the pool can't make progress.
31+
fn poll_pool(&mut self, cx: &mut Context<'_>) -> Poll<()> {
32+
loop {
33+
self.drain_incoming();
34+
35+
let pool_ret = self.pool.poll_next_unpin(cx);
36+
37+
// We queued up some new tasks; add them and poll again.
38+
if !self.incoming.borrow().is_empty() {
39+
continue;
40+
}
41+
42+
match pool_ret {
43+
Poll::Ready(Some(())) => continue,
44+
Poll::Ready(None) => return Poll::Ready(()),
45+
Poll::Pending => return Poll::Pending,
46+
}
47+
}
48+
}
49+
50+
/// Empty the incoming queue of newly-spawned tasks.
51+
fn drain_incoming(&mut self) {
52+
let mut incoming = self.incoming.borrow_mut();
53+
for task in incoming.drain(..) {
54+
self.pool.push(task)
55+
}
56+
}
57+
58+
/// Runs all tasks and returns after completing one future or until no more progress
59+
/// can be made. Returns `Ready` if one future was completed, `Pending` otherwise.
60+
fn poll_pool_one(&mut self, cx: &mut Context<'_>) -> Poll<()> {
61+
loop {
62+
self.drain_incoming();
63+
64+
match self.pool.poll_next_unpin(cx) {
65+
// Success!
66+
Poll::Ready(Some(())) => return Poll::Ready(()),
67+
// The pool was empty.
68+
Poll::Ready(None) => return Poll::Pending,
69+
Poll::Pending => (),
70+
}
71+
72+
if !self.incoming.borrow().is_empty() {
73+
// New tasks were spawned; try again.
74+
continue;
75+
} else {
76+
// No more progress for now (but caller should check cx)
77+
return Poll::Pending;
78+
}
79+
}
80+
}
81+
}
82+
2083
/// A single-threaded task pool for polling futures to completion.
2184
///
2285
/// This executor allows you to multiplex any number of tasks onto a single
@@ -29,9 +92,9 @@ use std::vec::Vec;
2992
/// single-threaded, it supports a special form of task spawning for non-`Send`
3093
/// futures, via [`spawn_local_obj`](futures_task::LocalSpawn::spawn_local_obj).
3194
#[derive(Debug)]
32-
pub struct LocalPool {
33-
pool: FuturesUnordered<LocalFutureObj<'static, ()>>,
34-
incoming: Rc<Incoming>,
95+
pub struct LocalPool<P = ParkThread> {
96+
park: P,
97+
state: State,
3598
}
3699

37100
/// A handle to a [`LocalPool`] that implements [`Spawn`](futures_task::Spawn).
@@ -42,80 +105,56 @@ pub struct LocalSpawner {
42105

43106
type Incoming = RefCell<Vec<LocalFutureObj<'static, ()>>>;
44107

45-
pub(crate) struct ThreadNotify {
46-
/// The (single) executor thread.
47-
thread: Thread,
48-
/// A flag to ensure a wakeup (i.e. `unpark()`) is not "forgotten"
49-
/// before the next `park()`, which may otherwise happen if the code
50-
/// being executed as part of the future(s) being polled makes use of
51-
/// park / unpark calls of its own, i.e. we cannot assume that no other
52-
/// code uses park / unpark on the executing `thread`.
53-
unparked: AtomicBool,
54-
}
55-
56-
std::thread_local! {
57-
static CURRENT_THREAD_NOTIFY: Arc<ThreadNotify> = Arc::new(ThreadNotify {
58-
thread: thread::current(),
59-
unparked: AtomicBool::new(false),
60-
});
61-
}
62-
63-
impl ArcWake for ThreadNotify {
64-
fn wake_by_ref(arc_self: &Arc<Self>) {
65-
// Make sure the wakeup is remembered until the next `park()`.
66-
let unparked = arc_self.unparked.swap(true, Ordering::Release);
67-
if !unparked {
68-
// If the thread has not been unparked yet, it must be done
69-
// now. If it was actually parked, it will run again,
70-
// otherwise the token made available by `unpark`
71-
// may be consumed before reaching `park()`, but `unparked`
72-
// ensures it is not forgotten.
73-
arc_self.thread.unpark();
74-
}
75-
}
76-
}
77-
78-
// Set up and run a basic single-threaded spawner loop, invoking `f` on each
79-
// turn.
80-
fn run_executor<T, F: FnMut(&mut Context<'_>) -> Poll<T>>(mut f: F) -> T {
81-
let _enter = enter().expect(
108+
fn run_executor<P: Park, T, F: FnMut(&mut Context<'_>) -> Poll<T>>(park: &mut P, mut f: F) -> T {
109+
let mut enter = enter().expect(
82110
"cannot execute `LocalPool` executor from within \
83-
another executor",
111+
another executor",
84112
);
85113

86-
CURRENT_THREAD_NOTIFY.with(|thread_notify| {
87-
let waker = waker_ref(thread_notify);
114+
loop {
115+
let waker = park.waker();
88116
let mut cx = Context::from_waker(&waker);
89-
loop {
90-
if let Poll::Ready(t) = f(&mut cx) {
91-
return t;
92-
}
93117

94-
// Wait for a wakeup.
95-
while !thread_notify.unparked.swap(false, Ordering::Acquire) {
96-
// No wakeup occurred. It may occur now, right before parking,
97-
// but in that case the token made available by `unpark()`
98-
// is guaranteed to still be available and `park()` is a no-op.
99-
thread::park();
100-
}
118+
if let Poll::Ready(t) = f(&mut cx) {
119+
return t;
101120
}
102-
})
103-
}
104-
105-
/// Check for a wakeup, but don't consume it.
106-
fn woken() -> bool {
107-
CURRENT_THREAD_NOTIFY.with(|thread_notify| thread_notify.unparked.load(Ordering::Acquire))
121+
park.park(&mut enter, ParkDuration::Block);
122+
}
108123
}
109124

110125
impl LocalPool {
111126
/// Create a new, empty pool of tasks.
127+
// Not generic over `P: Default` - would break API (default type not working)
112128
pub fn new() -> Self {
113-
Self { pool: FuturesUnordered::new(), incoming: Default::default() }
129+
Self::new_with_park(ParkThread::default())
130+
}
131+
}
132+
133+
impl<P> LocalPool<P>
134+
where
135+
P: Park,
136+
{
137+
/// Create a new, empty pool of tasks.
138+
pub fn new_with_park(park: P) -> Self {
139+
LocalPool {
140+
park,
141+
state: State { pool: FuturesUnordered::new(), incoming: Default::default() },
142+
}
143+
}
144+
145+
/// Returns a reference to the underlying Park instance.
146+
pub fn get_park(&self) -> &P {
147+
&self.park
148+
}
149+
150+
/// Returns a mutable reference to the underlying Park instance.
151+
pub fn get_park_mut(&mut self) -> &mut P {
152+
&mut self.park
114153
}
115154

116155
/// Get a clonable handle to the pool as a [`Spawn`].
117156
pub fn spawner(&self) -> LocalSpawner {
118-
LocalSpawner { incoming: Rc::downgrade(&self.incoming) }
157+
LocalSpawner { incoming: Rc::downgrade(&self.state.incoming) }
119158
}
120159

121160
/// Run all tasks in the pool to completion.
@@ -134,7 +173,7 @@ impl LocalPool {
134173
/// The function will block the calling thread until *all* tasks in the pool
135174
/// are complete, including any spawned while running existing tasks.
136175
pub fn run(&mut self) {
137-
run_executor(|cx| self.poll_pool(cx))
176+
self.run_executor(|state, cx| state.poll_pool(cx))
138177
}
139178

140179
/// Runs all the tasks in the pool until the given future completes.
@@ -157,7 +196,7 @@ impl LocalPool {
157196
pub fn run_until<F: Future>(&mut self, future: F) -> F::Output {
158197
let mut future = pin!(future);
159198

160-
run_executor(|cx| {
199+
self.run_executor(|state, cx| {
161200
{
162201
// if our main task is done, so are we
163202
let result = future.as_mut().poll(cx);
@@ -166,7 +205,7 @@ impl LocalPool {
166205
}
167206
}
168207

169-
let _ = self.poll_pool(cx);
208+
let _ = state.poll_pool(cx);
170209
Poll::Pending
171210
})
172211
}
@@ -200,29 +239,7 @@ impl LocalPool {
200239
/// further use of one of the pool's run or poll methods.
201240
/// Though only one task will be completed, progress may be made on multiple tasks.
202241
pub fn try_run_one(&mut self) -> bool {
203-
run_executor(|cx| {
204-
loop {
205-
self.drain_incoming();
206-
207-
match self.pool.poll_next_unpin(cx) {
208-
// Success!
209-
Poll::Ready(Some(())) => return Poll::Ready(true),
210-
// The pool was empty.
211-
Poll::Ready(None) => return Poll::Ready(false),
212-
Poll::Pending => (),
213-
}
214-
215-
if !self.incoming.borrow().is_empty() {
216-
// New tasks were spawned; try again.
217-
continue;
218-
} else if woken() {
219-
// The pool yielded to us, but there's more progress to be made.
220-
return Poll::Pending;
221-
} else {
222-
return Poll::Ready(false);
223-
}
224-
}
225-
})
242+
self.poll_executor(|state, cx| state.poll_pool_one(cx)).is_ready()
226243
}
227244

228245
/// Runs all tasks in the pool and returns if no more progress can be made
@@ -251,58 +268,51 @@ impl LocalPool {
251268
/// of the pool's run or poll methods. While the function is running, all tasks
252269
/// in the pool will try to make progress.
253270
pub fn run_until_stalled(&mut self) {
254-
run_executor(|cx| match self.poll_pool(cx) {
255-
// The pool is empty.
256-
Poll::Ready(()) => Poll::Ready(()),
257-
Poll::Pending => {
258-
if woken() {
259-
Poll::Pending
260-
} else {
261-
// We're stalled for now.
262-
Poll::Ready(())
263-
}
264-
}
265-
});
271+
// Ready: all tasks finished, Pending: would block
272+
let _ = self.poll_executor(|state, cx| state.poll_pool(cx));
266273
}
267274

268-
/// Poll `self.pool`, re-filling it with any newly-spawned tasks.
269-
/// Repeat until either the pool is empty, or it returns `Pending`.
270-
///
271-
/// Returns `Ready` if the pool was empty, and `Pending` otherwise.
272-
///
273-
/// NOTE: the pool may call `wake`, so `Pending` doesn't necessarily
274-
/// mean that the pool can't make progress.
275-
fn poll_pool(&mut self, cx: &mut Context<'_>) -> Poll<()> {
276-
loop {
277-
self.drain_incoming();
275+
// Set up and run a basic single-threaded spawner loop, polling `f` on each
276+
// turn until it completes or [`P::park`] fails.
277+
fn run_executor<T, F: FnMut(&mut State, &mut Context<'_>) -> Poll<T>>(
278+
&mut self,
279+
mut f: F,
280+
) -> T {
281+
let park = &mut self.park;
282+
let state = &mut self.state;
283+
run_executor(park, |cx| f(state, cx))
284+
}
278285

279-
let pool_ret = self.pool.poll_next_unpin(cx);
286+
// Set up and run a basic single-threaded spawner loop, polling `f` on each
287+
// turn until it completes or no progress can't be made anymore.
288+
fn poll_executor<T, F: FnMut(&mut State, &mut Context<'_>) -> Poll<T>>(
289+
&mut self,
290+
mut f: F,
291+
) -> Poll<T> {
292+
let _enter = enter().expect(
293+
"cannot execute `LocalPool` executor from within \
294+
another executor",
295+
);
280296

281-
// We queued up some new tasks; add them and poll again.
282-
if !self.incoming.borrow().is_empty() {
283-
continue;
284-
}
297+
loop {
298+
let waker = self.park.waker();
299+
let mut cx = Context::from_waker(&waker);
285300

286-
match pool_ret {
287-
Poll::Ready(Some(())) => continue,
288-
Poll::Ready(None) => return Poll::Ready(()),
289-
Poll::Pending => return Poll::Pending,
301+
if let Poll::Ready(t) = f(&mut self.state, &mut cx) {
302+
return Poll::Ready(t);
303+
}
304+
if !self.park.poll() {
305+
// can't make more progress
306+
return Poll::Pending;
290307
}
291-
}
292-
}
293-
294-
/// Empty the incoming queue of newly-spawned tasks.
295-
fn drain_incoming(&mut self) {
296-
let mut incoming = self.incoming.borrow_mut();
297-
for task in incoming.drain(..) {
298-
self.pool.push(task)
299308
}
300309
}
301310
}
302311

312+
// Not generic over `P: Default` - would break API (default type not working)
303313
impl Default for LocalPool {
304314
fn default() -> Self {
305-
Self::new()
315+
Self::new_with_park(ParkThread::default())
306316
}
307317
}
308318

@@ -313,7 +323,7 @@ impl Default for LocalPool {
313323
/// Use a [`LocalPool`] if you need finer-grained control over spawned tasks.
314324
pub fn block_on<F: Future>(f: F) -> F::Output {
315325
let mut f = pin!(f);
316-
run_executor(|cx| f.as_mut().poll(cx))
326+
run_executor(&mut ParkThread::new(), |cx| f.as_mut().poll(cx))
317327
}
318328

319329
/// Turn a stream into a blocking iterator.

0 commit comments

Comments
 (0)