Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrapping libuv signal for use in Rust #9318

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/libstd/rt/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ pub mod native {
/// Mock implementations for testing
mod mock;

/// Signal handling
pub mod signal;

/// The default buffer size for various I/O operations
/// XXX: Not pub
pub static DEFAULT_BUF_SIZE: uint = 1024 * 64;
Expand Down
190 changes: 190 additions & 0 deletions src/libstd/rt/io/signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// 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.

use comm::{Port, SharedChan, stream};
use hashmap;
use option::{Some, None};
use result::{Err, Ok};
use rt::io::io_error;
use rt::local::Local;
use rt::rtio::{EventLoop, RtioSignalObject};
use rt::sched::Scheduler;

#[deriving(Eq, IterBytes)]
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 perfom 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 port to listen for registered signals.
///
/// Listener automatically unregisters its handles once it is out of scope.
/// However, clients can still unregister signums manually.
///
/// Example usage:
///
/// ```rust
/// use std::rt::io::signal;
/// use std::task;
///
/// let mut listener = signal::Listener();
/// listener.register(signal::Interrupt);
///
/// do task::spawn {
/// loop {
/// match listener.recv() {
/// signal::Interrupt => println("Got Interrupt'ed"),
/// _ => (),
/// }
/// }
/// }
///
/// ```
pub struct Listener {
/// A map from signums to handles to keep the handles in memory
priv handles: hashmap::HashMap<Signum, ~RtioSignalObject>,
/// chan is where all the handles send signums, which are received by
/// the clients from port.
priv chan: SharedChan<Signum>,
/// Clients of Listener can `recv()` from this port
port: Port<Signum>,
}

impl Listener {
pub fn new() -> Listener {
let (port, chan) = stream();
Listener {
chan: SharedChan::new(chan),
port: port,
handles: hashmap::HashMap::new(),
}
}

/// Listen for a signal, returning true when successfully registered for
/// signum. Signals can be received using `recv()`.
pub fn register(&mut self, signum: Signum) -> bool {
match self.handles.find(&signum) {
Some(_) => true, // self is already listening to signum, so succeed
None => {
let chan = self.chan.clone();
let handle = unsafe {
rtdebug!("Listener::register: borrowing io to init UvSignal");
let sched: *mut Scheduler = Local::unsafe_borrow();
rtdebug!("about to init handle");
(*sched).event_loop.signal(signum, chan)
};
match handle {
Ok(w) => {
self.handles.insert(signum, w);
true
},
Err(ioerr) => {
rtdebug!("Listener::register: failed to init: {:?}", ioerr);
io_error::cond.raise(ioerr);
false
},
}
},
}
}

/// Unregister a signal.
pub fn unregister(&mut self, signum: Signum) {
self.handles.pop(&signum);
}
}

#[cfg(test)]
mod test {
use libc;
use rt::io::timer;
use super::*;

// kill is only available on Unixes
#[cfg(unix)]
#[fixed_stack_segment]
fn sigint() {
unsafe {
libc::funcs::posix88::signal::kill(libc::getpid(), libc::SIGINT);
}
}

#[test]
fn test_io_signal_smoketest() {
let mut signal = Listener::new();
signal.register(Interrupt);
sigint();
timer::sleep(10);
match signal.port.recv() {
Interrupt => (),
s => fail2!("Expected Interrupt, got {:?}", s),
}
}

#[test]
fn test_io_signal_two_signal_one_signum() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test where you explicitly unregister a signal and make sure that nothing is received on that channel? You'll probably have to have two listeners active (so the signal doesn't kill the process) and only unregister one of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let mut s1 = Listener::new();
let mut s2 = Listener::new();
s1.register(Interrupt);
s2.register(Interrupt);
sigint();
timer::sleep(10);
match s1.port.recv() {
Interrupt => (),
s => fail2!("Expected Interrupt, got {:?}", s),
}
match s1.port.recv() {
Interrupt => (),
s => fail2!("Expected Interrupt, got {:?}", s),
}
}

#[test]
fn test_io_signal_unregister() {
let mut s1 = Listener::new();
let mut s2 = Listener::new();
s1.register(Interrupt);
s2.register(Interrupt);
s2.unregister(Interrupt);
sigint();
timer::sleep(10);
if s2.port.peek() {
fail2!("Unexpected {:?}", s2.port.recv());
}
}

#[cfg(windows)]
#[test]
fn test_io_signal_invalid_signum() {
let mut s = Listener::new();
if s.register(User1) {
fail2!("Unexpected successful registry of signum {:?}", User1);
}
}
}
8 changes: 8 additions & 0 deletions src/libstd/rt/rtio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
use libc;
use option::*;
use result::*;
use comm::SharedChan;
use libc::c_int;

use rt::io::IoError;
use rt::io::signal::Signum;
use super::io::process::ProcessConfig;
use super::io::net::ip::{IpAddr, SocketAddr};
use rt::uv::uvio;
Expand All @@ -36,6 +38,7 @@ pub type PausibleIdleCallback = uvio::UvPausibleIdleCallback;
pub type RtioPipeObject = uvio::UvPipeStream;
pub type RtioUnboundPipeObject = uvio::UvUnboundPipe;
pub type RtioProcessObject = uvio::UvProcess;
pub type RtioSignalObject = uvio::UvSignal;

pub trait EventLoop {
fn run(&mut self);
Expand All @@ -45,6 +48,8 @@ pub trait EventLoop {
fn remote_callback(&mut self, ~fn()) -> ~RemoteCallbackObject;
/// The asynchronous I/O services. Not all event loops may provide one
fn io<'a>(&'a mut self) -> Option<&'a mut IoFactoryObject>;
fn signal(&mut self, signal: Signum, channel: SharedChan<Signum>)
-> Result<~RtioSignalObject, IoError>;
}

pub trait RemoteCallback {
Expand Down Expand Up @@ -87,6 +92,7 @@ pub trait IoFactory {
fn pipe_init(&mut self, ipc: bool) -> Result<~RtioUnboundPipeObject, IoError>;
fn spawn(&mut self, config: ProcessConfig)
-> Result<(~RtioProcessObject, ~[Option<RtioPipeObject>]), IoError>;
fn signal_init(&mut self) -> Result<~RtioSignalObject, IoError>;
}

pub trait RtioTcpListener : RtioSocket {
Expand Down Expand Up @@ -154,3 +160,5 @@ pub trait RtioPipe {
fn read(&mut self, buf: &mut [u8]) -> Result<uint, IoError>;
fn write(&mut self, buf: &[u8]) -> Result<(), IoError>;
}

pub trait RtioSignal {}
6 changes: 6 additions & 0 deletions src/libstd/rt/uv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use cast::transmute;
use ptr::null;
use unstable::finally::Finally;
use rt::io::net::ip::SocketAddr;
use rt::io::signal::Signum;

use rt::io::IoError;

Expand All @@ -60,6 +61,7 @@ pub use self::timer::TimerWatcher;
pub use self::async::AsyncWatcher;
pub use self::process::Process;
pub use self::pipe::Pipe;
pub use self::signal::SignalWatcher;

/// The implementation of `rtio` for libuv
pub mod uvio;
Expand All @@ -75,6 +77,7 @@ pub mod async;
pub mod addrinfo;
pub mod process;
pub mod pipe;
pub mod signal;

/// XXX: Loop(*handle) is buggy with destructors. Normal structs
/// with dtors may not be destructured, but tuple structs can,
Expand Down Expand Up @@ -137,6 +140,7 @@ pub type TimerCallback = ~fn(TimerWatcher, Option<UvError>);
pub type AsyncCallback = ~fn(AsyncWatcher, Option<UvError>);
pub type UdpReceiveCallback = ~fn(UdpWatcher, int, Buf, SocketAddr, uint, Option<UvError>);
pub type UdpSendCallback = ~fn(UdpWatcher, Option<UvError>);
pub type SignalCallback = ~fn(SignalWatcher, Signum);


/// Callbacks used by StreamWatchers, set as custom data on the foreign handle.
Expand All @@ -153,6 +157,7 @@ struct WatcherData {
udp_recv_cb: Option<UdpReceiveCallback>,
udp_send_cb: Option<UdpSendCallback>,
exit_cb: Option<ExitCallback>,
signal_cb: Option<SignalCallback>,
}

pub trait WatcherInterop {
Expand Down Expand Up @@ -186,6 +191,7 @@ impl<H, W: Watcher + NativeHandle<*H>> WatcherInterop for W {
udp_recv_cb: None,
udp_send_cb: None,
exit_cb: None,
signal_cb: None,
};
let data = transmute::<~WatcherData, *c_void>(data);
uvll::set_data_for_uv_handle(self.native_handle(), data);
Expand Down
100 changes: 100 additions & 0 deletions src/libstd/rt/uv/signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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.

use cast;
use option::Some;
use libc::{c_int, c_void};
use result::{Err, Ok, Result};
use rt::io::IoError;
use rt::io::signal::Signum;
use rt::uv::{Loop, NativeHandle, NullCallback, SignalCallback, UvError, Watcher};
use rt::uv::uv_error_to_io_error;
use rt::uv::uvll;

pub struct SignalWatcher(*uvll::uv_signal_t);

impl Watcher for SignalWatcher { }

impl SignalWatcher {
pub fn new(loop_: &mut Loop) -> SignalWatcher {
unsafe {
let handle = uvll::malloc_handle(uvll::UV_SIGNAL);
assert!(handle.is_not_null());
assert!(0 == uvll::signal_init(loop_.native_handle(), handle));
let mut watcher: SignalWatcher = NativeHandle::from_native_handle(handle);
watcher.install_watcher_data();
return watcher;
}
}

pub fn start(&mut self, signum: Signum, callback: SignalCallback) -> Result<(), IoError> {
{
let data = self.get_watcher_data();
data.signal_cb = Some(callback);
}

let ret = unsafe {
uvll::signal_start(self.native_handle(), signal_cb, signum as c_int)
};

return match ret {
0 => Ok(()),
_ => Err(uv_error_to_io_error(UvError(ret))),
};

extern fn signal_cb(handle: *uvll::uv_signal_t, signum: c_int) {
let mut watcher: SignalWatcher = NativeHandle::from_native_handle(handle);
let data = watcher.get_watcher_data();
let cb = data.signal_cb.get_ref();
(*cb)(watcher, unsafe { cast::transmute(signum as i64) });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the transmute?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transmute here is to cast signum back from c_int to Signum in order to avoid repeating Signum's constituent values here. Since signal_cb is only called by start, where signum is cast as c_int, this is safe.

}
}

pub fn stop(&mut self) {
unsafe {
uvll::signal_stop(self.native_handle());
}
}

pub fn close(self, cb: NullCallback) {
let mut watcher = self;
{
let data = watcher.get_watcher_data();
assert!(data.close_cb.is_none());
data.close_cb = Some(cb);
}

unsafe {
uvll::close(watcher.native_handle(), close_cb);
}

extern fn close_cb(handle: *uvll::uv_signal_t) {
let mut watcher: SignalWatcher = NativeHandle::from_native_handle(handle);
{
let data = watcher.get_watcher_data();
data.close_cb.take_unwrap()();
}
watcher.drop_watcher_data();
unsafe {
uvll::free_handle(handle as *c_void);
}
}
}
}

impl NativeHandle<*uvll::uv_signal_t> for SignalWatcher {
fn from_native_handle(handle: *uvll::uv_signal_t) -> SignalWatcher {
SignalWatcher(handle)
}

fn native_handle(&self) -> *uvll::uv_signal_t {
match self { &SignalWatcher(ptr) => ptr }
}
}
Loading