Skip to content

Commit e94c7d8

Browse files
committed
Drop the join waker of a task eagerly when the task completes and there is no
join interest
1 parent bb7ca75 commit e94c7d8

File tree

4 files changed

+87
-26
lines changed

4 files changed

+87
-26
lines changed

tokio/src/runtime/task/harness.rs

+36-15
Original file line numberDiff line numberDiff line change
@@ -284,21 +284,34 @@ where
284284
}
285285

286286
pub(super) fn drop_join_handle_slow(self) {
287-
// Try to unset `JOIN_INTEREST`. This must be done as a first step in
287+
// Try to unset `JOIN_INTEREST` and `JOIN_WAKER`. This must be done as a first step in
288288
// case the task concurrently completed.
289-
if self.state().unset_join_interested().is_err() {
290-
// It is our responsibility to drop the output. This is critical as
291-
// the task output may not be `Send` and as such must remain with
292-
// the scheduler or `JoinHandle`. i.e. if the output remains in the
293-
// task structure until the task is deallocated, it may be dropped
294-
// by a Waker on any arbitrary thread.
295-
//
296-
// Panics are delivered to the user via the `JoinHandle`. Given that
297-
// they are dropping the `JoinHandle`, we assume they are not
298-
// interested in the panic and swallow it.
299-
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
300-
self.core().drop_future_or_output();
301-
}));
289+
let snapshot = match self.state().unset_join_interested_and_waker() {
290+
Ok(snapshot) => snapshot,
291+
Err(snapshot) => {
292+
// It is our responsibility to drop the output. This is critical as
293+
// the task output may not be `Send` and as such must remain with
294+
// the scheduler or `JoinHandle`. i.e. if the output remains in the
295+
// task structure until the task is deallocated, it may be dropped
296+
// by a Waker on any arbitrary thread.
297+
//
298+
// Panics are delivered to the user via the `JoinHandle`. Given that
299+
// they are dropping the `JoinHandle`, we assume they are not
300+
// interested in the panic and swallow it.
301+
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
302+
self.core().drop_future_or_output();
303+
}));
304+
snapshot
305+
}
306+
};
307+
308+
if !snapshot.is_join_waker_set() {
309+
// If the JOIN_WAKER bit is not set the join handle has exclusive access to the waker
310+
// at this point following rule 2 in task/mod.rs so we drop the waker at this point
311+
// together with the join handle.
312+
unsafe {
313+
self.trailer().set_waker(None);
314+
}
302315
}
303316

304317
// Drop the `JoinHandle` reference, possibly deallocating the task
@@ -311,7 +324,6 @@ where
311324
fn complete(self) {
312325
// The future has completed and its output has been written to the task
313326
// stage. We transition from running to complete.
314-
315327
let snapshot = self.state().transition_to_complete();
316328

317329
// We catch panics here in case dropping the future or waking the
@@ -343,6 +355,15 @@ where
343355
}));
344356
}
345357

358+
if snapshot.is_join_interested() && snapshot.is_join_waker_set() {
359+
// If JOIN_INTEREST and JOIN_WAKER are still set at this point, the runtime should
360+
// drop the join waker as the join handle is not allowed to modify the waker
361+
// following rule 6 in task/mod.rs
362+
unsafe {
363+
self.trailer().set_waker(None);
364+
}
365+
}
366+
346367
// The task has completed execution and will no longer be scheduled.
347368
let num_release = self.release();
348369

tokio/src/runtime/task/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
//! * `RUNNING` - Tracks whether the task is currently being polled or cancelled.
3838
//! This bit functions as a lock around the task.
3939
//!
40-
//! * `COMPLETE` - Is one once the future has fully completed and has been
40+
//! * `COMPLETE` - Is one once the future has fully completed and the future is
4141
//! dropped. Never unset once set. Never set together with RUNNING.
4242
//!
4343
//! * `NOTIFIED` - Tracks whether a Notified object currently exists.

tokio/src/runtime/task/state.rs

+20-10
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,24 @@ impl State {
190190
///
191191
/// Returns true if the task should be deallocated.
192192
pub(super) fn transition_to_terminal(&self, count: usize) -> bool {
193-
let prev = Snapshot(self.val.fetch_sub(count * REF_ONE, AcqRel));
194-
assert!(
195-
prev.ref_count() >= count,
196-
"current: {}, sub: {}",
197-
prev.ref_count(),
198-
count
199-
);
200-
prev.ref_count() == count
193+
self.fetch_update_action(|mut snapshot| {
194+
assert!(
195+
snapshot.ref_count() >= count,
196+
"current: {}, sub: {}",
197+
snapshot.ref_count(),
198+
count
199+
);
200+
201+
snapshot.0 -= count * REF_ONE;
202+
if snapshot.is_join_interested() {
203+
// If there is still a join handle alive at this point we unset the
204+
// JOIN_WAKER bit so that the join handle gains exclusive access to
205+
// the waker field to actually drop it.
206+
snapshot.unset_join_waker();
207+
}
208+
209+
(snapshot.ref_count() == 0, Some(snapshot))
210+
})
201211
}
202212

203213
/// Transitions the state to `NOTIFIED`.
@@ -371,11 +381,11 @@ impl State {
371381
.map_err(|_| ())
372382
}
373383

374-
/// Tries to unset the `JOIN_INTEREST` flag.
384+
/// Tries to unset the `JOIN_INTEREST` and `JOIN_WAKER` flag.
375385
///
376386
/// Returns `Ok` if the operation happens before the task transitions to a
377387
/// completed state, `Err` otherwise.
378-
pub(super) fn unset_join_interested(&self) -> UpdateResult {
388+
pub(super) fn unset_join_interested_and_waker(&self) -> UpdateResult {
379389
self.fetch_update(|curr| {
380390
assert!(curr.is_join_interested());
381391

tokio/src/runtime/tests/loom_multi_thread.rs

+30
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod yield_now;
1010
/// In order to speed up the C
1111
use crate::runtime::tests::loom_oneshot as oneshot;
1212
use crate::runtime::{self, Runtime};
13+
use crate::sync::mpsc::channel;
1314
use crate::{spawn, task};
1415
use tokio_test::assert_ok;
1516

@@ -459,3 +460,32 @@ impl<T: Future> Future for Track<T> {
459460
})
460461
}
461462
}
463+
464+
#[test]
465+
fn drop_tasks_with_reference_cycle() {
466+
loom::model(|| {
467+
let pool = mk_pool(2);
468+
469+
pool.block_on(async move {
470+
let (tx, mut rx) = channel(1);
471+
472+
let (a_closer, mut wait_for_close_a) = channel::<()>(1);
473+
let (b_closer, mut wait_for_close_b) = channel::<()>(1);
474+
475+
let a = spawn(async move {
476+
let b = rx.recv().await.unwrap();
477+
478+
futures::future::select(std::pin::pin!(b), std::pin::pin!(a_closer.send(()))).await;
479+
});
480+
481+
let b = spawn(async move {
482+
let _ = a.await;
483+
let _ = b_closer.send(()).await;
484+
});
485+
486+
tx.send(b).await.unwrap();
487+
488+
futures::future::join(wait_for_close_a.recv(), wait_for_close_b.recv()).await;
489+
});
490+
});
491+
}

0 commit comments

Comments
 (0)