about summary refs log tree commit diff
path: root/src/libstd/io/signal.rs
blob: 598a8667d41e93a4d4e4fab53a3e702c55b717b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/*!

Signal handling

This modules provides bindings to receive signals safely, built on top of the
local I/O factory. There are a number of defined signals which can be caught,
but not all signals will work across all platforms (windows doesn't have
definitions for a number of signals.

*/

use clone::Clone;
use comm::{Sender, Receiver, channel};
use io;
use iter::Iterator;
use kinds::Send;
use mem::drop;
use option::{Some, None};
use owned::Box;
use result::{Ok, Err};
use rt::rtio::{IoFactory, LocalIo, RtioSignal, Callback};
use slice::ImmutableVector;
use vec::Vec;

/// Signals that can be sent and received
#[repr(int)]
#[deriving(PartialEq, Hash, Show)]
pub enum Signum {
    /// Equivalent to SIGBREAK, delivered when the user presses Ctrl-Break.
    Break = 21i,
    /// Equivalent to SIGHUP, delivered when the user closes the terminal
    /// window. On delivery of HangUp, the program is given approximately
    /// 10 seconds to perform any cleanup. After that, Windows will
    /// unconditionally terminate it.
    HangUp = 1i,
    /// Equivalent to SIGINT, delivered when the user presses Ctrl-c.
    Interrupt = 2i,
    /// Equivalent to SIGQUIT, delivered when the user presses Ctrl-\.
    Quit = 3i,
    /// Equivalent to SIGTSTP, delivered when the user presses Ctrl-z.
    StopTemporarily = 20i,
    /// Equivalent to SIGUSR1.
    User1 = 10i,
    /// Equivalent to SIGUSR2.
    User2 = 12i,
    /// Equivalent to SIGWINCH, delivered when the console has been resized.
    /// WindowSizeChange may not be delivered in a timely manner; size change
    /// will only be detected when the cursor is being moved.
    WindowSizeChange = 28i,
}

/// Listener provides a receiver to listen for registered signals.
///
/// Listener automatically unregisters its handles once it is out of scope.
/// However, clients can still unregister signums manually.
///
/// # Example
///
/// ```rust,no_run
/// # #![allow(unused_must_use)]
/// use std::io::signal::{Listener, Interrupt};
///
/// let mut listener = Listener::new();
/// listener.register(Interrupt);
///
/// loop {
///     match listener.rx.recv() {
///         Interrupt => println!("Got Interrupt'ed"),
///         _ => (),
///     }
/// }
/// ```
pub struct Listener {
    /// A map from signums to handles to keep the handles in memory
    handles: Vec<(Signum, Box<RtioSignal:Send>)>,
    /// This is where all the handles send signums, which are received by
    /// the clients from the receiver.
    tx: Sender<Signum>,

    /// Clients of Listener can `recv()` on this receiver. This is exposed to
    /// allow selection over it as well as manipulation of the receiver
    /// directly.
    pub rx: Receiver<Signum>,
}

impl Listener {
    /// Creates a new listener for signals. Once created, signals are bound via
    /// the `register` method (otherwise nothing will ever be received)
    pub fn new() -> Listener {
        let (tx, rx) = channel();
        Listener {
            tx: tx,
            rx: rx,
            handles: vec!(),
        }
    }

    /// Listen for a signal, returning true when successfully registered for
    /// signum. Signals can be received using `recv()`.
    ///
    /// Once a signal is registered, this listener will continue to receive
    /// notifications of signals until it is unregistered. This occurs
    /// regardless of the number of other listeners registered in other tasks
    /// (or on this task).
    ///
    /// Signals are still received if there is no task actively waiting for
    /// a signal, and a later call to `recv` will return the signal that was
    /// received while no task was waiting on it.
    ///
    /// # Error
    ///
    /// If this function fails to register a signal handler, then an error will
    /// be returned.
    pub fn register(&mut self, signum: Signum) -> io::IoResult<()> {
        struct SignalCallback {
            signum: Signum,
            tx: Sender<Signum>,
        }
        impl Callback for SignalCallback {
            fn call(&mut self) { self.tx.send(self.signum) }
        }

        if self.handles.iter().any(|&(sig, _)| sig == signum) {
            return Ok(()); // self is already listening to signum, so succeed
        }
        match LocalIo::maybe_raise(|io| {
            io.signal(signum as int, box SignalCallback {
                signum: signum,
                tx: self.tx.clone(),
            })
        }) {
            Ok(handle) => {
                self.handles.push((signum, handle));
                Ok(())
            }
            Err(e) => Err(io::IoError::from_rtio_error(e))
        }
    }

    /// Unregisters a signal. If this listener currently had a handler
    /// registered for the signal, then it will stop receiving any more
    /// notification about the signal. If the signal has already been received,
    /// it may still be returned by `recv`.
    pub fn unregister(&mut self, signum: Signum) {
        match self.handles.iter().position(|&(i, _)| i == signum) {
            Some(i) => drop(self.handles.remove(i)),
            None => {}
        }
    }
}

#[cfg(test, unix)]
mod test_unix {
    use prelude::*;
    use libc;
    use comm::Empty;
    use io::timer;
    use super::{Listener, Interrupt};

    fn sigint() {
        unsafe {
            libc::funcs::posix88::signal::kill(libc::getpid(), libc::SIGINT);
        }
    }

    #[test] #[cfg(not(target_os="android"))] // FIXME(#10378)
    fn test_io_signal_smoketest() {
        let mut signal = Listener::new();
        signal.register(Interrupt).unwrap();
        sigint();
        timer::sleep(10);
        match signal.rx.recv() {
            Interrupt => (),
            s => fail!("Expected Interrupt, got {:?}", s),
        }
    }

    #[test] #[cfg(not(target_os="android"))] // FIXME(#10378)
    fn test_io_signal_two_signal_one_signum() {
        let mut s1 = Listener::new();
        let mut s2 = Listener::new();
        s1.register(Interrupt).unwrap();
        s2.register(Interrupt).unwrap();
        sigint();
        timer::sleep(10);
        match s1.rx.recv() {
            Interrupt => (),
            s => fail!("Expected Interrupt, got {:?}", s),
        }
        match s2.rx.recv() {
            Interrupt => (),
            s => fail!("Expected Interrupt, got {:?}", s),
        }
    }

    #[test] #[cfg(not(target_os="android"))] // FIXME(#10378)
    fn test_io_signal_unregister() {
        let mut s1 = Listener::new();
        let mut s2 = Listener::new();
        s1.register(Interrupt).unwrap();
        s2.register(Interrupt).unwrap();
        s2.unregister(Interrupt);
        sigint();
        timer::sleep(10);
        assert_eq!(s2.rx.try_recv(), Err(Empty));
    }
}

#[cfg(test, windows)]
mod test_windows {
    use super::{User1, Listener};
    use result::{Ok, Err};

    #[test]
    fn test_io_signal_invalid_signum() {
        let mut s = Listener::new();
        match s.register(User1) {
            Ok(..) => {
                fail!("Unexpected successful registry of signum {:?}", User1);
            }
            Err(..) => {}
        }
    }
}