Skip to content

Commit 88d8baa

Browse files
brsontoddaaro
authored andcommitted
std::rt: Fix a race in UvRemoteCallback's dtor that misses callbacks
Full description in comments.
1 parent bd382ee commit 88d8baa

File tree

2 files changed

+74
-7
lines changed

2 files changed

+74
-7
lines changed

src/libstd/rt/sched.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ impl Scheduler {
440440
// return Some(this);
441441
}
442442
Some(Shutdown) => {
443+
rtdebug!("shutting down");
443444
// this.event_loop.callback(Scheduler::run_sched_once);
444445
if this.sleepy {
445446
// There may be an outstanding handle on the
@@ -1166,6 +1167,51 @@ mod test {
11661167
}
11671168
}
11681169

1170+
// A regression test that the final message is always handled.
1171+
// Used to deadlock because Shutdown was never recvd.
1172+
#[test]
1173+
fn no_missed_messages() {
1174+
use rt::work_queue::WorkQueue;
1175+
use rt::sleeper_list::SleeperList;
1176+
use rt::stack::StackPool;
1177+
use rt::uv::uvio::UvEventLoop;
1178+
use rt::sched::{Shutdown, TaskFromFriend};
1179+
use util;
1180+
1181+
do run_in_bare_thread {
1182+
do stress_factor().times {
1183+
let sleepers = SleeperList::new();
1184+
let queue = WorkQueue::new();
1185+
let queues = ~[queue.clone()];
1186+
1187+
let mut sched = ~Scheduler::new(
1188+
~UvEventLoop::new(),
1189+
queue,
1190+
queues.clone(),
1191+
sleepers.clone());
1192+
1193+
let mut handle = sched.make_handle();
1194+
1195+
let sched = Cell::new(sched);
1196+
1197+
let thread = do Thread::start {
1198+
let mut sched = sched.take();
1199+
let bootstrap_task = ~Task::new_root(&mut sched.stack_pool, None, ||());
1200+
sched.bootstrap(bootstrap_task);
1201+
};
1202+
1203+
let mut stack_pool = StackPool::new();
1204+
let task = ~Task::new_root(&mut stack_pool, None, ||());
1205+
handle.send(TaskFromFriend(task));
1206+
1207+
handle.send(Shutdown);
1208+
util::ignore(handle);
1209+
1210+
thread.join();
1211+
}
1212+
}
1213+
}
1214+
11691215
#[test]
11701216
fn multithreading() {
11711217
use rt::comm::*;

src/libstd/rt/uv/uvio.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,32 @@ impl UvRemoteCallback {
162162
let exit_flag_clone = exit_flag.clone();
163163
let async = do AsyncWatcher::new(loop_) |watcher, status| {
164164
assert!(status.is_none());
165+
166+
// The synchronization logic here is subtle. To review, the uv async handle
167+
// type promises that, after it is triggered the remote callback is definitely
168+
// called at least once. UvRemoteCallback needs to maintain those semantics
169+
// while also shutting down cleanly from the dtor. In our case that means that,
170+
// when the UvRemoteCallback dtor calls `async.send()`, here `f` is always called
171+
// later.
172+
173+
// In the dtor both the exit flag is set and the async callback fired under a lock.
174+
// Here, before calling `f`, we take the lock and check the flag. Because we are
175+
// checking the flag before calling `f`, and the flag is set under the same lock
176+
// as the send, then if the flag is set then we're guaranteed to call `f` after
177+
// the final send.
178+
179+
// If the check was done after `f()` then there would be a period between that call
180+
// and the check where the dtor could be called in the other thread, missing the
181+
// final callback while still destroying the handle.
182+
183+
let should_exit = unsafe { exit_flag_clone.with_imm(|&should_exit| should_exit) };
184+
165185
f();
166-
unsafe {
167-
do exit_flag_clone.with_imm |&should_exit| {
168-
if should_exit {
169-
watcher.close(||());
170-
}
171-
}
186+
187+
if should_exit {
188+
watcher.close(||());
172189
}
190+
173191
};
174192
UvRemoteCallback {
175193
async: async,
@@ -218,7 +236,10 @@ mod test_remote {
218236
let tube_clone = tube_clone.clone();
219237
let tube_clone_cell = Cell::new(tube_clone);
220238
let remote = do sched.event_loop.remote_callback {
221-
tube_clone_cell.take().send(1);
239+
// This could be called multiple times
240+
if !tube_clone_cell.is_empty() {
241+
tube_clone_cell.take().send(1);
242+
}
222243
};
223244
remote_cell.put_back(remote);
224245
}

0 commit comments

Comments
 (0)