Skip to content

Commit c5fdd69

Browse files
committed
Carefully destroy channels at the right time.
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
1 parent 86a321b commit c5fdd69

File tree

2 files changed

+67
-9
lines changed

2 files changed

+67
-9
lines changed

src/librustuv/signal.rs

+26
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,29 @@ impl Drop for SignalWatcher {
7777
self.close_async_();
7878
}
7979
}
80+
81+
#[cfg(test)]
82+
mod test {
83+
use super::*;
84+
use std::cell::Cell;
85+
use super::super::local_loop;
86+
use std::rt::io::signal;
87+
use std::comm::{SharedChan, stream};
88+
89+
#[test]
90+
fn closing_channel_during_drop_doesnt_kill_everything() {
91+
// see issue #10375, relates to timers as well.
92+
let (port, chan) = stream();
93+
let chan = SharedChan::new(chan);
94+
let _signal = SignalWatcher::new(local_loop(), signal::Interrupt,
95+
chan);
96+
97+
let port = Cell::new(port);
98+
do spawn {
99+
port.take().try_recv();
100+
}
101+
102+
// when we drop the SignalWatcher we're going to destroy the channel,
103+
// which must wake up the task on the other end
104+
}
105+
}

src/librustuv/timer.rs

+41-9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::rt::BlockedTask;
1414
use std::rt::local::Local;
1515
use std::rt::rtio::RtioTimer;
1616
use std::rt::sched::{Scheduler, SchedHandle};
17+
use std::util;
1718

1819
use uvll;
1920
use super::{Loop, UvHandle, ForbidUnwind, ForbidSwitch};
@@ -82,9 +83,13 @@ impl RtioTimer for TimerWatcher {
8283
fn oneshot(&mut self, msecs: u64) -> PortOne<()> {
8384
let (port, chan) = oneshot();
8485

85-
let _m = self.fire_homing_missile();
86-
self.action = Some(SendOnce(chan));
87-
self.start(msecs, 0);
86+
// similarly to the destructor, we must drop the previous action outside
87+
// of the homing missile
88+
let _prev_action = {
89+
let _m = self.fire_homing_missile();
90+
self.start(msecs, 0);
91+
util::replace(&mut self.action, Some(SendOnce(chan)))
92+
};
8893

8994
return port;
9095
}
@@ -93,8 +98,14 @@ impl RtioTimer for TimerWatcher {
9398
let (port, chan) = stream();
9499

95100
let _m = self.fire_homing_missile();
96-
self.action = Some(SendMany(chan));
97-
self.start(msecs, msecs);
101+
102+
// similarly to the destructor, we must drop the previous action outside
103+
// of the homing missile
104+
let _prev_action = {
105+
let _m = self.fire_homing_missile();
106+
self.start(msecs, msecs);
107+
util::replace(&mut self.action, Some(SendMany(chan)))
108+
};
98109

99110
return port;
100111
}
@@ -120,16 +131,24 @@ extern fn timer_cb(handle: *uvll::uv_timer_t, status: c_int) {
120131

121132
impl Drop for TimerWatcher {
122133
fn drop(&mut self) {
123-
let _m = self.fire_homing_missile();
124-
self.action = None;
125-
self.stop();
126-
self.close_async_();
134+
// note that this drop is a little subtle. Dropping a channel which is
135+
// held internally may invoke some scheduling operations. We can't take
136+
// the channel unless we're on the home scheduler, but once we're on the
137+
// home scheduler we should never move. Hence, we take the timer's
138+
// action item and then move it outside of the homing block.
139+
let _action = {
140+
let _m = self.fire_homing_missile();
141+
self.stop();
142+
self.close_async_();
143+
self.action.take()
144+
};
127145
}
128146
}
129147

130148
#[cfg(test)]
131149
mod test {
132150
use super::*;
151+
use std::cell::Cell;
133152
use std::rt::rtio::RtioTimer;
134153
use super::super::local_loop;
135154

@@ -205,6 +224,19 @@ mod test {
205224
// which must wake up the task on the other end
206225
}
207226

227+
#[test]
228+
fn reset_doesnt_switch_tasks() {
229+
// similar test to the one above.
230+
let mut timer = TimerWatcher::new(local_loop());
231+
let timer_port = Cell::new(timer.period(1000));
232+
233+
do spawn {
234+
timer_port.take().try_recv();
235+
}
236+
237+
timer.oneshot(1);
238+
}
239+
208240
#[test]
209241
fn sender_goes_away_oneshot() {
210242
let port = {

0 commit comments

Comments
 (0)