-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for 'abort'ing shuttle::future::JoinHandle's #150
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -343,6 +343,12 @@ impl Task { | |
self.detached = true; | ||
} | ||
|
||
pub(crate) fn abort(&mut self) { | ||
self.finish(); | ||
let mut continuation = self.continuation.borrow_mut(); | ||
continuation.wipe(); | ||
Comment on lines
+348
to
+349
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe worth a comment about why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||
} | ||
|
||
pub(crate) fn waker(&self) -> Waker { | ||
self.waker.clone() | ||
} | ||
|
@@ -373,8 +379,10 @@ impl Task { | |
self.park_state.blocked_in_park = false; | ||
} | ||
|
||
// TODO: Investigate whether we should move `wipe` here. (I think the correct scheme is to have it | ||
// toggleable by the instantiator of the `Task` — those modelling async `JoinHandle`s should | ||
// clean eagerly, thos modelling sync `JoinHandle`s should not.) | ||
pub(crate) fn finish(&mut self) { | ||
assert!(self.state != TaskState::Finished); | ||
self.state = TaskState::Finished; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -221,6 +221,78 @@ fn async_counter() { | |
}); | ||
} | ||
|
||
// We need a way to hold the `MutexGuard`, which is `!Send`, across an `await`. | ||
struct WrappedMutexGuard<'a> { | ||
#[allow(unused)] | ||
guard: shuttle::sync::MutexGuard<'a, ()>, | ||
} | ||
|
||
unsafe impl<'a> Send for WrappedMutexGuard<'a> {} | ||
|
||
async fn acquire_and_loop(mutex: Arc<Mutex<()>>) { | ||
let _g = WrappedMutexGuard { | ||
guard: mutex.lock().unwrap(), | ||
}; | ||
loop { | ||
future::yield_now().await; | ||
} | ||
} | ||
|
||
// The idea is to acquire a mutex, abort the JoinHandle, then acquire the Mutex. | ||
// This should succeed, because `JoinHandle::abort()` should free the Mutex. | ||
#[test] | ||
fn abort_frees_mutex() { | ||
check_random( | ||
|| { | ||
let mutex = Arc::new(Mutex::new(())); | ||
let jh = future::spawn(acquire_and_loop(mutex.clone())); | ||
|
||
jh.abort(); // this unblocks | ||
|
||
let _g = mutex.lock(); | ||
}, | ||
1000, | ||
); | ||
} | ||
|
||
// The idea is to acquire a mutex, drop the JoinHandle, then acquire the Mutex. | ||
// This should fail, because `drop`ping the JoinHandle just detaches it, meaning | ||
// it keeps holding the Mutex. | ||
#[test] | ||
#[should_panic(expected = "exceeded max_steps bound")] | ||
fn drop_join_handle_deadlocks() { | ||
check_random( | ||
|| { | ||
let mutex = Arc::new(Mutex::new(())); | ||
let jh = future::spawn(acquire_and_loop(mutex.clone())); | ||
|
||
drop(jh); | ||
|
||
let _g = mutex.lock(); | ||
}, | ||
1000, | ||
); | ||
} | ||
|
||
// The idea is to acquire a mutex, forget the JoinHandle, then acquire the Mutex. | ||
// This should fail, because `forget`ting the JoinHandle doesn't cause it to release | ||
// the Mutex. | ||
#[test] | ||
#[should_panic(expected = "exceeded max_steps bound")] | ||
fn forget_join_handle_deadlocks() { | ||
check_random( | ||
|| { | ||
let mutex = Arc::new(Mutex::new(())); | ||
let jh = future::spawn(acquire_and_loop(mutex.clone())); | ||
|
||
std::mem::forget(jh); | ||
|
||
let _g = mutex.lock(); | ||
}, | ||
1000, | ||
); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like this doesn't work, because it's possible for the abort to happen while #[test]
fn abort_while_blocked() {
check_dfs(
|| {
let lock = Arc::new(Mutex::new(0i32));
let lock2 = lock.clone();
let t1 = thread::spawn(move || {
let _l = lock2.lock().unwrap();
});
let t2 = future::spawn(async move {
let _l = lock.lock().unwrap();
});
t2.abort();
t1.join().unwrap();
},
None,
);
} The way it fails right now is a bit funny — when Not sure what the right thing to do here is. Maybe we need to check lazily whether a task has been aborted, only once a poll of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, what about removing the Actually, wait, is our implementation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Their fairness is platform-dependent IIRC. Removing the assert might be OK, though we should add an explicit aborted flag as a sanity check that the only way a task gets unblocked is if it was aborted. But tokio's JoinHandle semantics are that the task cancels at the next let t1 = future::spawn(async move {
let l1 = lock1.lock().unwrap();
*l1 += 1;
let l2 = lock2.lock().unwrap();
*l2 += 1;
});
t1.abort();
assert_eq!(*l1.lock().unwrap(), *l2.lock().unwrap()); In real Rust this assertion can't fail because the task body is atomic with respect to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah, the immediate |
||
#[test] | ||
fn async_counter_random() { | ||
check_random(async_counter, 5000) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think we also need to do something about the Future impl for JoinHandle, otherwise this test deadlocks:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes indeed, I'll have a look.