Skip to content

Commit cb51897

Browse files
committed
Fix a schedule() race by postponing preemption enablement to inside the dispatch closure
This resolves a situation where a preemptible function could time out before performing the fun.take() inside said closure. Such a case was erroneous because the dispatch closure itself is passed between stacks as a raw pointer via BOOTSTRAP, but it lives on the original stack until the aforementioned move! This was found because a later resume() sometimes segfaulted within memmove(). After this change, release builds of the resume_completion test appear to be stable with and without global variable interpositioning. This was tested down to a preemption quantum of 5 μs.
1 parent b8741db commit cb51897

File tree

2 files changed

+15
-14
lines changed

2 files changed

+15
-14
lines changed

src/linger.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ struct Task {
9999
pub fn launch<T: Send>(fun: impl FnOnce() -> T + Send, us: u64)
100100
-> Result<Linger<T, impl FnMut(*mut Option<ThdResult<T>>) + Send>> {
101101
use groups::assign_group;
102+
use preemption::enable_preemption;
102103
use std::panic::AssertUnwindSafe;
103104
use std::panic::catch_unwind;
104105

@@ -124,13 +125,22 @@ pub fn launch<T: Send>(fun: impl FnOnce() -> T + Send, us: u64)
124125
// arising from the former case, we first assert that no one has attempted a nested call.
125126
debug_assert!(! is_preemptible(), "launch(): called from preemptible function");
126127

128+
let group = assign_group().expect("launch(): too many active timed functions");
129+
let group_id = *group;
127130
let mut fun = Completion::Function(AssertUnwindSafe (fun));
128131
let fun = Box::new(move |ret: *mut Option<ThdResult<T>>| {
129132
fun = match fun.take() {
130-
Completion::Function(fun) =>
133+
Completion::Function(fun) => {
131134
// We haven't yet moved the closure. This means schedule() is invoking us
132135
// as a *nullary* function, implying ret is undefined and mustn't be used.
133-
Completion::Return(catch_unwind(fun)),
136+
let res = catch_unwind(|| {
137+
stamp();
138+
enable_preemption(group_id.into());
139+
fun()
140+
});
141+
disable_preemption();
142+
Completion::Return(res)
143+
},
134144
Completion::Return(val) => {
135145
debug_assert!(! ret.is_null());
136146
let ret = unsafe {
@@ -145,7 +155,6 @@ pub fn launch<T: Send>(fun: impl FnOnce() -> T + Send, us: u64)
145155
});
146156

147157
let checkpoint = setup_stack(STACK_SIZE_BYTES)?;
148-
let group = assign_group().expect("launch(): too many active timed functions");
149158
let mut linger = Linger::Continuation(Continuation {
150159
functional: fun,
151160
stateful: Task {
@@ -162,7 +171,7 @@ pub fn launch<T: Send>(fun: impl FnOnce() -> T + Send, us: u64)
162171
}
163172

164173
thread_local! {
165-
static BOOTSTRAP: Cell<Option<(NonNull<(dyn FnMut() + Send)>, Group)>> = Cell::default();
174+
static BOOTSTRAP: Cell<Option<NonNull<(dyn FnMut() + Send)>>> = Cell::default();
166175
static TASK: RefCell<Task> = RefCell::default();
167176
static DEADLINE: Cell<u64> = Cell::default();
168177
}
@@ -193,9 +202,7 @@ pub fn resume<T>(fun: &mut Linger<T, impl FnMut(*mut Option<ThdResult<T>>) + Sen
193202
// represents our first caller, so we know not to read the argument.
194203
let fun: *mut (dyn FnMut(_) + Send) = fun;
195204
let fun: *mut (dyn FnMut() + Send) = fun as _;
196-
let no_fun = bootstrap.replace(NonNull::new(fun).map(|fun|
197-
(fun, group)
198-
));
205+
let no_fun = bootstrap.replace(NonNull::new(fun));
199206
debug_assert!(
200207
no_fun.is_none(),
201208
"resume(): bootstraps without an intervening schedule()",
@@ -328,18 +335,13 @@ fn switch_stack(task: &mut Task, group: Group) -> Result<bool> {
328335

329336
/// Enable preemption and call the preemptible function. Runs on the oneshot execution stack.
330337
fn schedule() {
331-
use preemption::enable_preemption;
332-
333-
let (mut fun, group) = BOOTSTRAP.with(|bootstrap| bootstrap.take()).unwrap_or_else(||
338+
let mut fun = BOOTSTRAP.with(|bootstrap| bootstrap.take()).unwrap_or_else(||
334339
abort("schedule(): called without bootstrapping")
335340
);
336341
let fun = unsafe {
337342
fun.as_mut()
338343
};
339-
stamp();
340-
enable_preemption(group.into());
341344
fun();
342-
disable_preemption();
343345

344346
// It's important that we haven't saved any errno, since we'll check it to determine whether
345347
// the preemptible function ran to completion.

tests/inger/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ fn launch_continuations() {
8585
});
8686
}
8787

88-
#[ignore]
8988
#[test]
9089
fn resume_completion() {
9190
exclusive(|| {

0 commit comments

Comments
 (0)