Skip to content

Commit

Permalink
Carefully destroy channels at the right time.
Browse files Browse the repository at this point in the history
When a channel is destroyed, it may attempt scheduler operations which could
move a task off of it's I/O scheduler. This is obviously a bad interaction, and
some finesse is required to make it work (making destructors run at the right
time).

Closes #10375
  • Loading branch information
alexcrichton committed Nov 9, 2013
1 parent 66a7f8f commit 87df504
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 5 deletions.
26 changes: 26 additions & 0 deletions src/librustuv/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,29 @@ impl Drop for SignalWatcher {
self.close_async_();
}
}

#[cfg(test)]
mod test {
use super::*;
use std::cell::Cell;
use super::super::local_loop;
use std::rt::io::signal;
use std::comm::{SharedChan, stream};

#[test]
fn closing_channel_during_drop_doesnt_kill_everything() {
// see issue #10375, relates to timers as well.
let (port, chan) = stream();
let chan = SharedChan::new(chan);
let _signal = SignalWatcher::new(local_loop(), signal::Interrupt,
chan);

let port = Cell::new(port);
do spawn {
port.take().try_recv();
}

// when we drop the SignalWatcher we're going to destroy the channel,
// which must wake up the task on the other end
}
}
33 changes: 28 additions & 5 deletions src/librustuv/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ impl RtioTimer for TimerWatcher {
}
}

extern fn timer_cb(handle: *uvll::uv_timer_t, _status: c_int) {
extern fn timer_cb(handle: *uvll::uv_timer_t, status: c_int) {
assert_eq!(status, 0);
let timer: &mut TimerWatcher = unsafe { UvHandle::from_uv_handle(&handle) };

match timer.action.take_unwrap() {
Expand All @@ -118,16 +119,24 @@ extern fn timer_cb(handle: *uvll::uv_timer_t, _status: c_int) {

impl Drop for TimerWatcher {
fn drop(&mut self) {
let _m = self.fire_homing_missile();
self.action = None;
self.stop();
self.close_async_();
// note that this drop is a little subtle. Dropping a channel which is
// held internally may invoke some scheduling operations. We can't take
// the channel unless we're on the home scheduler, but once we're on the
// home scheduler we should never move. Hence, we take the timer's
// action item and then move it outside of the homing block.
let _action = {
let _m = self.fire_homing_missile();
self.stop();
self.close_async_();
self.action.take()
};
}
}

#[cfg(test)]
mod test {
use super::*;
use std::cell::Cell;
use std::rt::rtio::RtioTimer;
use super::super::local_loop;

Expand Down Expand Up @@ -188,4 +197,18 @@ mod test {
let _timer = TimerWatcher::new(local_loop());
fail!();
}

#[test]
fn closing_channel_during_drop_doesnt_kill_everything() {
// see issue #10375
let mut timer = TimerWatcher::new(local_loop());
let timer_port = Cell::new(timer.period(1000));

do spawn {
timer_port.take().try_recv();
}

// when we drop the TimerWatcher we're going to destroy the channel,
// which must wake up the task on the other end
}
}

0 comments on commit 87df504

Please sign in to comment.