From eca7f0760f3f8ea9ecdf9e16e5226959da12d564 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 6 Sep 2016 23:00:17 -0700 Subject: [PATCH 01/86] signal: Initial commit --- .gitignore | 2 + .travis.yml | 24 +++ Cargo.toml | 20 +++ LICENSE-APACHE | 201 ++++++++++++++++++++++++ LICENSE-MIT | 25 +++ README.md | 32 ++++ src/lib.rs | 28 ++++ src/unix.rs | 399 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/signal.rs | 97 ++++++++++++ 9 files changed, 828 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/unix.rs create mode 100644 tests/signal.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..a9d37c560 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..82a10188d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: rust + +rust: + - stable + - beta + - nightly +sudo: false +before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH +script: + - cargo build + - cargo test + - cargo doc --no-deps +after_success: + - travis-cargo --only nightly doc-upload +env: + global: + - secure: 6jMQVEVzg0JrRAHmGCSo93u5sFTkWQZffcsCKYvQhB2H4HCWQJ8ECykNeh4gbjIGt7hdpqCCL1C5HC+vUIp6uLHl41P2dvg7jT3P1dUcl3bSOEhjQZmfO6/JuSGrdHm34lQkIXzE5IpmY1PZKxmIOC5Sv3NU8Feu6bFuT+cHhy6TMW743S5b0XoViLocCcqGrsXhsc2rwets1zBP9WnkuNJgcPbU5ZBEcSGFufFYJFNMqajDLr1T1vYlGFH5Y3xFsbEVSSV6sOmZaE2zGlwDCW1aBflWOuRkTc1wrQUoVT/1DvlLI1Q8O/CVreGthiiVaFCm2hLbHXmUODQKYUG37yffglRSEPnsp+ia1xEVDqK77sykiU1BoORvK7EJB9rFxsKeRsg/c5V3Wi1FA1V278ZxddQqdH4gsMPMOwSAOFj7ni4CMoH/ihm5XgrAc8jzNWl1ZvdHpSfQbeeuog3b9KSev9Di0cv1JPaMYyt2Lfvex6hYqb47qjJTFbLf3j50mtAM1jrZaQsyxvuxB9eQaT9rRgS8xJWkp3fkb+26YK1n6JgCKooy62usK06pY6i4XZyFOlt3cxxh0fgd7cg7jbanZjrJkecDxBoKEneL2r1FqAN/BzYNiM1y4xH80kBvW+eZAOyfMmvXPvD6Ro2Cys5y0xr7B7wNPPibcbokhM4= +notifications: + email: + on_success: never +os: + - linux + - osx diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..b55545168 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tokio-signal" +version = "0.1.0" +authors = ["Alex Crichton "] +license = "MIT/Apache-2.0" +repository = "https://github.com/alexcrichton/tokio-signal" +homepage = "https://github.com/alexcrichton/tokio-signal" +documentation = "https://alexcrichton.github.io/tokio-signal" +description = """ +An implementation of an asynchronous Unix signal handling backed futures. +""" + +[dependencies] +tokio-core = { git = "https://github.com/tokio-rs/tokio-core" } +futures = { git = "https://github.com/alexcrichton/futures-rs" } + +[target.'cfg(unix)'.dependencies] +tokio-uds = { git = "https://github.com/tokio-rs/tokio-uds" } +libc = "0.2" +mio = "0.6" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..28e630cf4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..6cedacb64 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# tokio-signal + +An implementation of Unix signal handling for Tokio + +[![Build Status](https://travis-ci.org/alexcrichton/tokio-signal.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-signal) + +[Documentation](https://alexcrichton.github.io/tokio-signal) + +## Usage + +First, add this to your `Cargo.toml`: + +```toml +[dependencies] +tokio-signal = { git = "https://github.com/alexcrichton/tokio-signal" } +``` + +Next, add this to your crate: + +```rust +extern crate tokio_signal; +``` + +# License + +`tokio-signal` is primarily distributed under the terms of both the MIT +license and the Apache License (Version 2.0), with portions covered by various +BSD-like licenses. + +See LICENSE-APACHE, and LICENSE-MIT for details. + + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..2165715ab --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,28 @@ +//! Asynchronous signal handling for Tokio +//! +//! This crate implements asynchronous signal handling for Tokio, and +//! asynchronous I/O framework in Rust. The primary type exported from this +//! crate, `unix::Signal`, allows listening for arbitrary signals on Unix +//! platforms, receiving them in an asynchronous fashion. +//! +//! Note that signal handling is in general a very tricky topic and should be +//! used with great care. This crate attempts to implement 'best practice' for +//! signal handling, but it should be evaluated for your own applications' needs +//! to see if it's suitable. +//! +//! The are some fundamental limitations of this crate documented on the +//! `Signal` structure as well. +//! +//! > **Note**: This crate compiles on Windows, but currently contains no +//! > bindings. Windows does not have signals like Unix does, but it +//! > does have a way to receive ctrl-c notifications at the console. +//! > It's planned that this will be bound and exported outside the +//! > `unix` module in the future! + +#![deny(missing_docs)] + +#[macro_use] +extern crate futures; +extern crate tokio_core; + +pub mod unix; diff --git a/src/unix.rs b/src/unix.rs new file mode 100644 index 000000000..f41fe38ff --- /dev/null +++ b/src/unix.rs @@ -0,0 +1,399 @@ +//! Unix-specific types for signal handling. +//! +//! This module is only defined on Unix platforms and contains the primary +//! `Signal` type for receiving notifications of signals. + +#![cfg(unix)] + +extern crate libc; +extern crate mio; +extern crate tokio_uds; + +use std::cell::RefCell; +use std::io::{self, Write, Read}; +use std::mem; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Once, ONCE_INIT, Mutex}; + +use futures::stream::{Stream, Fuse}; +use futures::{self, Future, Complete, Oneshot, Poll, Async}; +use self::libc::c_int; +use self::tokio_uds::UnixStream; +use tokio_core::io::IoFuture; +use tokio_core::{LoopHandle, Sender, Receiver, ReadinessStream}; + +static INIT: Once = ONCE_INIT; +static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; + +/// An implementation of `Stream` for receiving a particular type of signal. +/// +/// This structure implements the `Stream` trait and represents notifications +/// of the current process receiving a particular signal. The signal being +/// listened for is passed to `Signal::new`, and the same signal number is then +/// yielded as each element for the stream. +/// +/// In general signal handling on Unix is a pretty tricky topic, and this +/// structure is no exception! There are some important limitations to keep in +/// mind when using `Signal` streams: +/// +/// * While multiple event loops are supported, the *first* event loop to +/// register a signal handler is required to be active to ensure that signals +/// for other event loops are delivered. In other words, once an event loop +/// registers a signal, it's best to keep it around and running. This is +/// normally just a problem for tests, and the "workaround" is to spawn a +/// thread in the background at the beginning of the test suite which is +/// running an event loop (and listening for a signal). +/// +/// * Signals handling in Unix already necessitates coalescing signals +/// together sometimes. This `Signal` stream is also no exception here in +/// that it will also coalesce signals. That is, even if the signal handler +/// for this process runs multiple times, the `Signal` stream may only return +/// one signal notification. Specifically, before `poll` is called, all +/// signal notifications are coalesced into one item returned from `poll`. +/// Once `poll` has been called, however, a further signal is guaranteed to +/// be yielded as an item. +/// +/// * Signal handling in general is relatively inefficient. Although some +/// improvements are possible in this crate, it's recommended to not plan on +/// having millions of signal channels open. +/// +/// * Currently the "driver task" to process incoming signals never exits. +/// +/// If you've got any questions about this feel free to open an issue on the +/// repo, though, as I'd love to chat about this! In other words, I'd love to +/// alleviate some of these limitations if possible! +pub struct Signal { + signum: c_int, + reg: ReadinessStream, + _finished: Complete<()>, +} + +struct GlobalState { + write: UnixStream, + tx: Mutex>, + signals: [GlobalSignalState; 32], +} + +struct GlobalSignalState { + ready: AtomicBool, + prev: libc::sigaction, +} + +enum Message { + NewSignal(c_int, Complete>), +} + +struct DriverTask { + handle: LoopHandle, + read: UnixStream, + rx: Fuse>, + signals: [SignalState; 32], +} + +struct SignalState { + registered: bool, + tasks: Vec<(RefCell>, mio::SetReadiness)>, +} + +impl Signal { + /// Creates a new stream which will receive notifications when the current + /// process receives the signal `signum`. + /// + /// This function will create a new stream which may be based on the + /// event loop handle provided. This function returns a future which will + /// then resolve to the signal stream, if successful. + /// + /// The `Signal` stream is an infinite stream which will receive + /// notifications whenever a signal is received. More documentation can be + /// found on `Signal` itself, but to reiterate: + /// + /// * Signals may be coalesced beyond what the kernel already does. + /// * While multiple event loops are supported, the first event loop to + /// register a signal handler must be active to deliver signal + /// notifications + /// * Once a signal handle is registered with the process the underlying + /// libc signal handler is never unregistered. + /// + /// A `Signal` stream can be created for a particular signal number + /// multiple times. When a signal is received then all the associated + /// channels will receive the signal notification. + pub fn new(signum: c_int, handle: &LoopHandle) -> IoFuture { + let mut init = None; + INIT.call_once(|| { + init = Some(global_init(handle)); + }); + let new_signal = futures::lazy(move || { + let (tx, rx) = futures::oneshot(); + let msg = Message::NewSignal(signum, tx); + let res = unsafe { + (*GLOBAL_STATE).tx.lock().unwrap().send(msg) + }; + res.expect("failed to request a new signal stream, did the \ + first event loop go away?"); + rx.then(|r| r.unwrap()) + }); + match init { + Some(init) => init.and_then(|()| new_signal).boxed(), + None => new_signal.boxed(), + } + } +} + +impl Stream for Signal { + type Item = c_int; + type Error = io::Error; + + fn poll(&mut self) -> Poll, io::Error> { + try_ready!(self.reg.poll_read()); + self.reg.get_ref() + .inner.borrow() + .as_ref().unwrap().1 + .set_readiness(mio::Ready::none()) + .expect("failed to set readiness"); + Ok(Async::Ready(Some(self.signum))) + } +} + +fn global_init(handle: &LoopHandle) -> IoFuture<()> { + let handle = handle.clone(); + let (tx, rx) = handle.clone().channel(); + let io = rx.join(UnixStream::pair(handle.clone())); + io.map(move |(rx, (read, write))| { + unsafe { + let state = Box::new(GlobalState { + write: write, + signals: { + fn new() -> GlobalSignalState { + GlobalSignalState { + ready: AtomicBool::new(false), + prev: unsafe { mem::zeroed() }, + } + } + [ + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + ] + }, + tx: Mutex::new(tx.clone()), + }); + GLOBAL_STATE = Box::into_raw(state); + + handle.clone().spawn(|_| { + DriverTask { + handle: handle, + rx: rx.fuse(), + read: read, + signals: { + fn new() -> SignalState { + SignalState { registered: false, tasks: Vec::new() } + } + [ + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + ] + }, + } + }); + } + }).boxed() +} + +impl Future for DriverTask { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + self.check_signal_drops(); + self.check_messages(); + self.check_signals(); + + // TODO: when to finish this task? + Ok(Async::NotReady) + } +} + +impl DriverTask { + fn check_signal_drops(&mut self) { + for signal in self.signals.iter_mut() { + signal.tasks.retain(|task| { + !task.0.borrow_mut().poll().is_err() + }); + } + } + + fn check_messages(&mut self) { + loop { + // Acquire the next message + let message = match self.rx.poll() { + Ok(Async::Ready(Some(e))) => e, + Ok(Async::Ready(None)) | + Ok(Async::NotReady) => break, + Err(e) => panic!("error on rx: {}", e), + }; + let (sig, complete) = match message { + Message::NewSignal(sig, complete) => (sig, complete), + }; + + // If the signal's too large, then we return an error, otherwise we + // use this index to look at the signal slot. + // + // If the signal wasn't previously registered then we do so now. + let signal = match self.signals.get_mut(sig as usize) { + Some(signal) => signal, + None => { + complete.complete(Err(io::Error::new(io::ErrorKind::Other, + "signum too large"))); + continue + } + }; + if !signal.registered { + unsafe { + let mut new: libc::sigaction = mem::zeroed(); + new.sa_sigaction = handler as usize; + new.sa_flags = libc::SA_RESTART | libc::SA_SIGINFO; + let mut prev = mem::zeroed(); + if libc::sigaction(sig, &new, &mut prev) != 0 { + complete.complete(Err(io::Error::last_os_error())); + continue + } + signal.registered = true; + } + } + + // Acquire the (registration, set_readiness) pair by... assuming + // we're on the event loop (true because of the spawn above). + let reg = MyRegistration { inner: RefCell::new(None) }; + let mut new = ReadinessStream::new(self.handle.clone(), reg); + let reg = match new.poll() { + Ok(Async::Ready(reg)) => reg, + Ok(Async::NotReady) => panic!("should be on event loop"), + Err(e) => { + complete.complete(Err(e)); + continue + } + }; + + // Create the `Signal` to pass back and then also keep a handle to + // the `SetReadiness` for ourselves internally. + let (tx, rx) = futures::oneshot(); + let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); + complete.complete(Ok(Signal { + signum: sig, + reg: reg, + _finished: tx, + })); + signal.tasks.push((RefCell::new(rx), ready)); + } + } + + fn check_signals(&mut self) { + // Drain all data from the pipe + let mut buf = [0; 32]; + let mut any = false; + loop { + match self.read.read(&mut buf) { + Ok(0) => { // EOF == something happened + any = true; + break + } + Ok(..) => any = true, // data read, but keep draining + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(e) => panic!("bad read: {}", e), + } + } + + // If nothing happened, no need to check the signals + if !any { + return + } + + for (i, slot) in self.signals.iter().enumerate() { + // No need to go farther if we haven't even registered a signal + if !slot.registered { + continue + } + + // See if this signal actually happened since we last checked + unsafe { + if !(*GLOBAL_STATE).signals[i].ready.swap(false, Ordering::SeqCst) { + continue + } + } + + // Wake up all the tasks waiting on this signal + for task in slot.tasks.iter() { + task.1.set_readiness(mio::Ready::readable()) + .expect("failed to set readiness"); + } + } + } +} + +extern fn handler(signum: c_int, + info: *mut libc::siginfo_t, + ptr: *mut libc::c_void) { + type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); + type FnHandler = extern fn(c_int); + + unsafe { + let state = match (*GLOBAL_STATE).signals.get(signum as usize) { + Some(state) => state, + None => return, + }; + + if !state.ready.swap(true, Ordering::SeqCst) { + match (&(*GLOBAL_STATE).write).write(&[1]) { + Ok(..) => {} + Err(e) => { + if e.kind() != io::ErrorKind::WouldBlock { + panic!("bad error on write fd: {}", e) + } + } + } + } + + let fnptr = state.prev.sa_sigaction; + if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { + return + } + if state.prev.sa_flags & libc::SA_SIGINFO == 0 { + let action = mem::transmute::(fnptr); + action(signum) + } else { + let action = mem::transmute::(fnptr); + action(signum, info, ptr) + } + } +} + +struct MyRegistration { + inner: RefCell>, +} + +impl mio::Evented for MyRegistration { + fn register(&self, + poll: &mio::Poll, + token: mio::Token, + events: mio::Ready, + opts: mio::PollOpt) -> io::Result<()> { + let reg = mio::Registration::new(poll, token, events, opts); + *self.inner.borrow_mut() = Some(reg); + Ok(()) + } + + fn reregister(&self, + _poll: &mio::Poll, + _token: mio::Token, + _events: mio::Ready, + _opts: mio::PollOpt) -> io::Result<()> { + Ok(()) + } + + fn deregister(&self, _poll: &mio::Poll) -> io::Result<()> { + Ok(()) + } +} diff --git a/tests/signal.rs b/tests/signal.rs new file mode 100644 index 000000000..ef7ccb771 --- /dev/null +++ b/tests/signal.rs @@ -0,0 +1,97 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio_core; +extern crate tokio_signal; + +use std::sync::mpsc::channel; +use std::sync::{Once, ONCE_INIT, Mutex, MutexGuard}; +use std::thread; +use std::time::Duration; + +use futures::Future; +use futures::stream::Stream; +use tokio_core::Loop; +use tokio_signal::unix::Signal; + +static INIT: Once = ONCE_INIT; +static mut LOCK: *mut Mutex<()> = 0 as *mut _; + +fn lock() -> MutexGuard<'static, ()> { + unsafe { + INIT.call_once(|| { + LOCK = Box::into_raw(Box::new(Mutex::new(()))); + let (tx, rx) = channel(); + thread::spawn(move || { + let mut lp = Loop::new().unwrap(); + let handle = lp.handle(); + let _signal = lp.run(Signal::new(libc::SIGALRM, &handle)).unwrap(); + tx.send(()).unwrap(); + drop(lp.run(futures::empty::<(), ()>())); + }); + rx.recv().unwrap(); + }); + (*LOCK).lock().unwrap() + } +} + +#[test] +fn simple() { + let _lock = lock(); + + let mut lp = Loop::new().unwrap(); + let handle = lp.handle(); + let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + lp.run(signal.into_future()).ok().unwrap(); +} + +#[test] +fn notify_both() { + let _lock = lock(); + + let mut lp = Loop::new().unwrap(); + let handle = lp.handle(); + let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); + let signal2 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); + } + lp.run(signal1.into_future().join(signal2.into_future())).ok().unwrap(); +} + +#[test] +fn drop_then_get_a_signal() { + let _lock = lock(); + + let mut lp = Loop::new().unwrap(); + let handle = lp.handle(); + let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); + drop(signal); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + let timeout = lp.handle().timeout(Duration::from_millis(1)); + lp.run(timeout.and_then(|t| t)).unwrap(); +} + +#[test] +fn twice() { + let _lock = lock(); + + let mut lp = Loop::new().unwrap(); + let handle = lp.handle(); + let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + let (num, signal) = lp.run(signal.into_future()).ok().unwrap(); + assert_eq!(num, Some(libc::SIGUSR1)); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + lp.run(signal.into_future()).ok().unwrap(); +} From 1b6893b6f631421c73e78b874cc817600b805140 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Sep 2016 22:14:54 -0700 Subject: [PATCH 02/86] signal: Track tokio-core master --- src/unix.rs | 108 ++++++++++++++++++++++++------------------------ tests/signal.rs | 16 +++---- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index f41fe38ff..bb577762a 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -16,11 +16,12 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Once, ONCE_INIT, Mutex}; use futures::stream::{Stream, Fuse}; -use futures::{self, Future, Complete, Oneshot, Poll, Async}; +use futures::{self, Future, IntoFuture, Complete, Oneshot, Poll, Async}; use self::libc::c_int; use self::tokio_uds::UnixStream; use tokio_core::io::IoFuture; -use tokio_core::{LoopHandle, Sender, Receiver, ReadinessStream}; +use tokio_core::reactor::{PollEvented, Handle}; +use tokio_core::channel::{channel, Sender, Receiver}; static INIT: Once = ONCE_INIT; static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; @@ -64,7 +65,7 @@ static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; /// alleviate some of these limitations if possible! pub struct Signal { signum: c_int, - reg: ReadinessStream, + reg: PollEvented, _finished: Complete<()>, } @@ -84,7 +85,7 @@ enum Message { } struct DriverTask { - handle: LoopHandle, + handle: Handle, read: UnixStream, rx: Fuse>, signals: [SignalState; 32], @@ -117,7 +118,7 @@ impl Signal { /// A `Signal` stream can be created for a particular signal number /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. - pub fn new(signum: c_int, handle: &LoopHandle) -> IoFuture { + pub fn new(signum: c_int, handle: &Handle) -> IoFuture { let mut init = None; INIT.call_once(|| { init = Some(global_init(handle)); @@ -133,7 +134,7 @@ impl Signal { rx.then(|r| r.unwrap()) }); match init { - Some(init) => init.and_then(|()| new_signal).boxed(), + Some(init) => init.into_future().and_then(|()| new_signal).boxed(), None => new_signal.boxed(), } } @@ -144,7 +145,9 @@ impl Stream for Signal { type Error = io::Error; fn poll(&mut self) -> Poll, io::Error> { - try_ready!(self.reg.poll_read()); + if !self.reg.poll_read().is_ready() { + return Ok(Async::NotReady) + } self.reg.get_ref() .inner.borrow() .as_ref().unwrap().1 @@ -154,52 +157,49 @@ impl Stream for Signal { } } -fn global_init(handle: &LoopHandle) -> IoFuture<()> { - let handle = handle.clone(); - let (tx, rx) = handle.clone().channel(); - let io = rx.join(UnixStream::pair(handle.clone())); - io.map(move |(rx, (read, write))| { - unsafe { - let state = Box::new(GlobalState { - write: write, - signals: { - fn new() -> GlobalSignalState { - GlobalSignalState { - ready: AtomicBool::new(false), - prev: unsafe { mem::zeroed() }, - } +fn global_init(handle: &Handle) -> io::Result<()> { + let (tx, rx) = try!(channel(handle)); + let (read, write) = try!(UnixStream::pair(handle)); + unsafe { + let state = Box::new(GlobalState { + write: write, + signals: { + fn new() -> GlobalSignalState { + GlobalSignalState { + ready: AtomicBool::new(false), + prev: unsafe { mem::zeroed() }, } - [ - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - ] - }, - tx: Mutex::new(tx.clone()), - }); - GLOBAL_STATE = Box::into_raw(state); - - handle.clone().spawn(|_| { - DriverTask { - handle: handle, - rx: rx.fuse(), - read: read, - signals: { - fn new() -> SignalState { - SignalState { registered: false, tasks: Vec::new() } - } - [ - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - ] - }, } - }); - } - }).boxed() + [ + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + ] + }, + tx: Mutex::new(tx.clone()), + }); + GLOBAL_STATE = Box::into_raw(state); + + handle.spawn(DriverTask { + handle: handle.clone(), + rx: rx.fuse(), + read: read, + signals: { + fn new() -> SignalState { + SignalState { registered: false, tasks: Vec::new() } + } + [ + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + new(), new(), new(), new(), new(), new(), new(), new(), + ] + }, + }); + + Ok(()) + } } impl Future for DriverTask { @@ -267,10 +267,8 @@ impl DriverTask { // Acquire the (registration, set_readiness) pair by... assuming // we're on the event loop (true because of the spawn above). let reg = MyRegistration { inner: RefCell::new(None) }; - let mut new = ReadinessStream::new(self.handle.clone(), reg); - let reg = match new.poll() { - Ok(Async::Ready(reg)) => reg, - Ok(Async::NotReady) => panic!("should be on event loop"), + let reg = match PollEvented::new(reg, &self.handle) { + Ok(reg) => reg, Err(e) => { complete.complete(Err(e)); continue diff --git a/tests/signal.rs b/tests/signal.rs index ef7ccb771..529e740c2 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -12,7 +12,7 @@ use std::time::Duration; use futures::Future; use futures::stream::Stream; -use tokio_core::Loop; +use tokio_core::reactor::{Core, Timeout}; use tokio_signal::unix::Signal; static INIT: Once = ONCE_INIT; @@ -24,7 +24,7 @@ fn lock() -> MutexGuard<'static, ()> { LOCK = Box::into_raw(Box::new(Mutex::new(()))); let (tx, rx) = channel(); thread::spawn(move || { - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let handle = lp.handle(); let _signal = lp.run(Signal::new(libc::SIGALRM, &handle)).unwrap(); tx.send(()).unwrap(); @@ -40,7 +40,7 @@ fn lock() -> MutexGuard<'static, ()> { fn simple() { let _lock = lock(); - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); unsafe { @@ -53,7 +53,7 @@ fn simple() { fn notify_both() { let _lock = lock(); - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); let signal2 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); @@ -67,22 +67,22 @@ fn notify_both() { fn drop_then_get_a_signal() { let _lock = lock(); - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); drop(signal); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } - let timeout = lp.handle().timeout(Duration::from_millis(1)); - lp.run(timeout.and_then(|t| t)).unwrap(); + let timeout = Timeout::new(Duration::from_millis(1), &lp.handle()).unwrap(); + lp.run(timeout).unwrap(); } #[test] fn twice() { let _lock = lock(); - let mut lp = Loop::new().unwrap(); + let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); unsafe { From 8291c3d46281b6b0637c01aba442fccebf5ce42b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Sep 2016 17:13:18 -0700 Subject: [PATCH 03/86] signal: Start adding windows support --- Cargo.toml | 5 + examples/log.rs | 17 +++ src/lib.rs | 34 ++++++ src/unix.rs | 3 +- src/windows.rs | 293 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 examples/log.rs create mode 100644 src/windows.rs diff --git a/Cargo.toml b/Cargo.toml index b55545168..60848333e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,8 @@ futures = { git = "https://github.com/alexcrichton/futures-rs" } tokio-uds = { git = "https://github.com/tokio-rs/tokio-uds" } libc = "0.2" mio = "0.6" + +[target.'cfg(windows)'.dependencies] +winapi = "0.2" +kernel32-sys = "0.2" +mio = "0.6" diff --git a/examples/log.rs b/examples/log.rs new file mode 100644 index 000000000..6d147362a --- /dev/null +++ b/examples/log.rs @@ -0,0 +1,17 @@ +extern crate futures; +extern crate tokio_core; +extern crate tokio_signal; + +use futures::stream::Stream; +use tokio_core::reactor::Core; + +fn main() { + let mut core = Core::new().unwrap(); + let ctrlc = tokio_signal::ctrl_c(&core.handle()); + let stream = core.run(ctrlc).unwrap(); + + core.run(stream.for_each(|()| { + println!("Ctrl-C received!"); + Ok(()) + })).unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs index 2165715ab..40ca57250 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,4 +25,38 @@ extern crate futures; extern crate tokio_core; +use futures::Future; +use futures::stream::Stream; +use tokio_core::reactor::Handle; +use tokio_core::io::{IoStream, IoFuture}; + pub mod unix; +pub mod windows; + +/// Creates a stream which receives "ctrl-c" notifications sent to a process. +/// +/// In general signals are handled very differently across Unix and Windows, but +/// this is somewhat cross platform in terms of how it can be handled. A ctrl-c +/// event to a console process can be represented as a stream for both Windows +/// and Unix. +/// +/// This function receives a `Handle` to an event loop and returns a future +/// which when resolves yields a stream receiving all signal events. Note that +/// there are a number of caveats listening for signals, and you may wish to +/// read up on the documentation in the `unix` or `windows` module to take a +/// peek. +pub fn ctrl_c(handle: &Handle) -> IoFuture> { + return ctrl_c_imp(handle); + + #[cfg(unix)] + fn ctrl_c_imp(handle: &Handle) -> IoFuture> { + unix::Signal::new(unix::libc::SIGINT, handle).map(|x| { + x.map(|_| ()).boxed() + }).boxed() + } + + #[cfg(windows)] + fn ctrl_c_imp(handle: &Handle) -> IoFuture> { + windows::Event::ctrl_c(handle).map(|x| x.boxed()).boxed() + } +} diff --git a/src/unix.rs b/src/unix.rs index bb577762a..c6f67ec3c 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -5,7 +5,7 @@ #![cfg(unix)] -extern crate libc; +pub extern crate libc; extern crate mio; extern crate tokio_uds; @@ -148,6 +148,7 @@ impl Stream for Signal { if !self.reg.poll_read().is_ready() { return Ok(Async::NotReady) } + self.reg.need_read(); self.reg.get_ref() .inner.borrow() .as_ref().unwrap().1 diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 000000000..b81bf2dad --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,293 @@ +//! Windows-specific types for signal handling. +//! +//! This module is only defined on Windows and contains the primary `Event` type +//! for receiving notifications of events. These events are listened for via the +//! `SetConsoleCtrlHandler` function which receives events of the type +//! `CTRL_C_EVENT` and `CTRL_BREAK_EVENT` + +#![cfg(windows)] + +extern crate kernel32; +extern crate mio; +extern crate winapi; + +use std::cell::RefCell; +use std::io; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Once, ONCE_INIT, Mutex}; + +use futures::stream::{Stream, Fuse}; +use futures::{self, Future, IntoFuture, Complete, Oneshot, Poll, Async}; +use tokio_core::io::IoFuture; +use tokio_core::reactor::{PollEvented, Handle}; +use tokio_core::channel::{channel, Sender, Receiver}; + +static INIT: Once = ONCE_INIT; +static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; + +/// Stream of events discovered via `SetConsoleCtrlHandler`. +/// +/// This structure can be used to listen for events of the type `CTRL_C_EVENT` +/// and `CTRL_BREAK_EVENT`. The `Stream` trait is implemented for this struct +/// and will resolve for each notification received by the process. Note that +/// there are few limitations with this as well: +/// +/// * A notification to this process notifies *all* `Event` streams for that +/// event type. +/// * Notifications to an `Event` stream **are coalesced** if they aren't +/// processed quickly enough. This means that if two notifications are +/// received back-to-back, then the stream may only receive one item about the +/// two notifications. +pub struct Event { + reg: PollEvented, + _finished: Complete<()>, +} + +struct GlobalState { + ready: mio::SetReadiness, + tx: Mutex>, + ctrl_c: GlobalEventState, + ctrl_break: GlobalEventState, +} + +struct GlobalEventState { + ready: AtomicBool, +} + +enum Message { + NewEvent(winapi::DWORD, Complete>), +} + +struct DriverTask { + handle: Handle, + reg: PollEvented, + rx: Fuse>, + ctrl_c: EventState, + ctrl_break: EventState, +} + +struct EventState { + tasks: Vec<(RefCell>, mio::SetReadiness)>, +} + +impl Event { + /// Creates a new stream listening for the `CTRL_C_EVENT` events. + /// + /// This function will register a handler via `SetConsoleCtrlHandler` and + /// deliver notifications to the returned stream. + pub fn ctrl_c(handle: &Handle) -> IoFuture { + Event::new(winapi::CTRL_C_EVENT, handle) + } + + /// Creates a new stream listening for the `CTRL_BREAK_EVENT` events. + /// + /// This function will register a handler via `SetConsoleCtrlHandler` and + /// deliver notifications to the returned stream. + pub fn ctrl_break(handle: &Handle) -> IoFuture { + Event::new(winapi::CTRL_BREAK_EVENT, handle) + } + + fn new(signum: winapi::DWORD, handle: &Handle) -> IoFuture { + let mut init = None; + INIT.call_once(|| { + init = Some(global_init(handle)); + }); + let new_signal = futures::lazy(move || { + let (tx, rx) = futures::oneshot(); + let msg = Message::NewEvent(signum, tx); + let res = unsafe { + (*GLOBAL_STATE).tx.lock().unwrap().send(msg) + }; + res.expect("failed to request a new signal stream, did the \ + first event loop go away?"); + rx.then(|r| r.unwrap()) + }); + match init { + Some(init) => init.into_future().and_then(|()| new_signal).boxed(), + None => new_signal.boxed(), + } + } +} + +impl Stream for Event { + type Item = (); + type Error = io::Error; + + fn poll(&mut self) -> Poll, io::Error> { + if !self.reg.poll_read().is_ready() { + return Ok(Async::NotReady) + } + self.reg.need_read(); + self.reg.get_ref() + .inner.borrow() + .as_ref().unwrap().1 + .set_readiness(mio::Ready::none()) + .expect("failed to set readiness"); + Ok(Async::Ready(Some(()))) + } +} + +fn global_init(handle: &Handle) -> io::Result<()> { + let (tx, rx) = try!(channel(handle)); + let reg = MyRegistration { inner: RefCell::new(None) }; + let reg = try!(PollEvented::new(reg, handle)); + let ready = reg.get_ref().inner.borrow().as_ref().unwrap().1.clone(); + unsafe { + let state = Box::new(GlobalState { + ready: ready, + ctrl_c: GlobalEventState { ready: AtomicBool::new(false) }, + ctrl_break: GlobalEventState { ready: AtomicBool::new(false) }, + tx: Mutex::new(tx.clone()), + }); + GLOBAL_STATE = Box::into_raw(state); + + let rc = kernel32::SetConsoleCtrlHandler(Some(handler), winapi::TRUE); + if rc == 0 { + Box::from_raw(GLOBAL_STATE); + GLOBAL_STATE = 0 as *mut _; + return Err(io::Error::last_os_error()) + } + + handle.spawn(DriverTask { + handle: handle.clone(), + rx: rx.fuse(), + reg: reg, + ctrl_c: EventState { tasks: Vec::new() }, + ctrl_break: EventState { tasks: Vec::new() }, + }); + + Ok(()) + } +} + +impl Future for DriverTask { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + self.check_event_drops(); + self.check_messages(); + self.check_events(); + + // TODO: when to finish this task? + Ok(Async::NotReady) + } +} + +impl DriverTask { + fn check_event_drops(&mut self) { + self.ctrl_c.tasks.retain(|task| { + !task.0.borrow_mut().poll().is_err() + }); + self.ctrl_break.tasks.retain(|task| { + !task.0.borrow_mut().poll().is_err() + }); + } + + fn check_messages(&mut self) { + loop { + // Acquire the next message + let message = match self.rx.poll() { + Ok(Async::Ready(Some(e))) => e, + Ok(Async::Ready(None)) | + Ok(Async::NotReady) => break, + Err(e) => panic!("error on rx: {}", e), + }; + let (sig, complete) = match message { + Message::NewEvent(sig, complete) => (sig, complete), + }; + + let event = if sig == winapi::CTRL_C_EVENT { + &mut self.ctrl_c + } else { + &mut self.ctrl_break + }; + + // Acquire the (registration, set_readiness) pair by... assuming + // we're on the event loop (true because of the spawn above). + let reg = MyRegistration { inner: RefCell::new(None) }; + let reg = match PollEvented::new(reg, &self.handle) { + Ok(reg) => reg, + Err(e) => { + complete.complete(Err(e)); + continue + } + }; + + // Create the `Event` to pass back and then also keep a handle to + // the `SetReadiness` for ourselves internally. + let (tx, rx) = futures::oneshot(); + let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); + complete.complete(Ok(Event { + reg: reg, + _finished: tx, + })); + event.tasks.push((RefCell::new(rx), ready)); + } + } + + fn check_events(&mut self) { + if self.reg.poll_read().is_not_ready() { + return + } + self.reg.need_read(); + self.reg.get_ref().inner.borrow().as_ref().unwrap() + .1.set_readiness(mio::Ready::none()).unwrap(); + + if unsafe { (*GLOBAL_STATE).ctrl_c.ready.swap(false, Ordering::SeqCst) } { + for task in self.ctrl_c.tasks.iter() { + task.1.set_readiness(mio::Ready::readable()).unwrap(); + } + } + if unsafe { (*GLOBAL_STATE).ctrl_break.ready.swap(false, Ordering::SeqCst) } { + for task in self.ctrl_break.tasks.iter() { + task.1.set_readiness(mio::Ready::readable()).unwrap(); + } + } + } +} + +unsafe extern "system" fn handler(ty: winapi::DWORD) -> winapi::BOOL { + let event = match ty { + winapi::CTRL_C_EVENT => &(*GLOBAL_STATE).ctrl_c, + winapi::CTRL_BREAK_EVENT => &(*GLOBAL_STATE).ctrl_break, + _ => return winapi::FALSE + }; + if event.ready.swap(true, Ordering::SeqCst) { + winapi::FALSE + } else { + drop((*GLOBAL_STATE).ready.set_readiness(mio::Ready::readable())); + // TODO: this will report that we handled a CTRL_BREAK_EVENT when in + // fact we may not have any streams actually created for that + // event. + winapi::TRUE + } +} + +struct MyRegistration { + inner: RefCell>, +} + +impl mio::Evented for MyRegistration { + fn register(&self, + poll: &mio::Poll, + token: mio::Token, + events: mio::Ready, + opts: mio::PollOpt) -> io::Result<()> { + let reg = mio::Registration::new(poll, token, events, opts); + *self.inner.borrow_mut() = Some(reg); + Ok(()) + } + + fn reregister(&self, + _poll: &mio::Poll, + _token: mio::Token, + _events: mio::Ready, + _opts: mio::PollOpt) -> io::Result<()> { + Ok(()) + } + + fn deregister(&self, _poll: &mio::Poll) -> io::Result<()> { + Ok(()) + } +} From 3486a61a0f2acc368ca511327098fab1332d05d5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 9 Sep 2016 22:04:04 -0700 Subject: [PATCH 04/86] signal: Update deps to point to crates.io --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60848333e..1a137b7a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,11 @@ An implementation of an asynchronous Unix signal handling backed futures. """ [dependencies] -tokio-core = { git = "https://github.com/tokio-rs/tokio-core" } -futures = { git = "https://github.com/alexcrichton/futures-rs" } +tokio-core = "0.1" +futures = "0.1" [target.'cfg(unix)'.dependencies] -tokio-uds = { git = "https://github.com/tokio-rs/tokio-uds" } +tokio-uds = "0.1" libc = "0.2" mio = "0.6" From 61c4047c6a0bd87a5d7458e01abc90e8a285a947 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Oct 2016 08:57:51 -0700 Subject: [PATCH 05/86] signal: Add symbolic reexports for common signals Means you don't have to import libc! Closes alexcrichton/tokio-signal#1 --- src/unix.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/unix.rs b/src/unix.rs index c6f67ec3c..204e152dd 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -96,6 +96,9 @@ struct SignalState { tasks: Vec<(RefCell>, mio::SetReadiness)>, } +pub use self::libc::{SIGINT, SIGKILL, SIGTERM, SIGUSR1, SIGUSR2}; +pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; + impl Signal { /// Creates a new stream which will receive notifications when the current /// process receives the signal `signum`. From f3f8ee431e6ea67146706dfbab7a2ef22420964b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Oct 2016 13:55:45 -0700 Subject: [PATCH 06/86] signal: Remove SIGKILL reexport --- src/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unix.rs b/src/unix.rs index 204e152dd..b5067c363 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -96,7 +96,7 @@ struct SignalState { tasks: Vec<(RefCell>, mio::SetReadiness)>, } -pub use self::libc::{SIGINT, SIGKILL, SIGTERM, SIGUSR1, SIGUSR2}; +pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; impl Signal { From 1a122018a22e031098c4ef3e25494e52e716d9b0 Mon Sep 17 00:00:00 2001 From: Chris Emerson Date: Mon, 7 Nov 2016 22:04:08 +0000 Subject: [PATCH 07/86] signal: Trivial typo fix. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 40ca57250..261dcb46a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! Asynchronous signal handling for Tokio //! -//! This crate implements asynchronous signal handling for Tokio, and +//! This crate implements asynchronous signal handling for Tokio, an //! asynchronous I/O framework in Rust. The primary type exported from this //! crate, `unix::Signal`, allows listening for arbitrary signals on Unix //! platforms, receiving them in an asynchronous fashion. From e28c350e3141a7bb459fe4c71e4627371f1cab18 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 19 Nov 2016 09:15:34 -0800 Subject: [PATCH 08/86] signal: Update travis token --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 82a10188d..9bb8f8fab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,8 @@ after_success: - travis-cargo --only nightly doc-upload env: global: - - secure: 6jMQVEVzg0JrRAHmGCSo93u5sFTkWQZffcsCKYvQhB2H4HCWQJ8ECykNeh4gbjIGt7hdpqCCL1C5HC+vUIp6uLHl41P2dvg7jT3P1dUcl3bSOEhjQZmfO6/JuSGrdHm34lQkIXzE5IpmY1PZKxmIOC5Sv3NU8Feu6bFuT+cHhy6TMW743S5b0XoViLocCcqGrsXhsc2rwets1zBP9WnkuNJgcPbU5ZBEcSGFufFYJFNMqajDLr1T1vYlGFH5Y3xFsbEVSSV6sOmZaE2zGlwDCW1aBflWOuRkTc1wrQUoVT/1DvlLI1Q8O/CVreGthiiVaFCm2hLbHXmUODQKYUG37yffglRSEPnsp+ia1xEVDqK77sykiU1BoORvK7EJB9rFxsKeRsg/c5V3Wi1FA1V278ZxddQqdH4gsMPMOwSAOFj7ni4CMoH/ihm5XgrAc8jzNWl1ZvdHpSfQbeeuog3b9KSev9Di0cv1JPaMYyt2Lfvex6hYqb47qjJTFbLf3j50mtAM1jrZaQsyxvuxB9eQaT9rRgS8xJWkp3fkb+26YK1n6JgCKooy62usK06pY6i4XZyFOlt3cxxh0fgd7cg7jbanZjrJkecDxBoKEneL2r1FqAN/BzYNiM1y4xH80kBvW+eZAOyfMmvXPvD6Ro2Cys5y0xr7B7wNPPibcbokhM4= + - secure: "SXXK7Znvm1s5WWQ94l9IP25mXA0uGIQ7ghBuumZz3nSAfxhhJQnYi5hCAbl2/cOfSbpgEtE137dgk6Nd9UDx7rIkLCSe8TWyYjzraX/vvX3xNtLh/fjsayYYRK9a6qU2HIJegZdxPgyF5h2DeBgeLks0Ue8drrFQ1s9bYZVUO0yeuZ3aLkL1FkIG6RXGItUFpb6srEYL1NLizYLxXFEG3cL+kKoFIWc2qPx3EwOqv/eii134nQsuObhWZvPqfTo7zfNP8W/6TnoiggpRH1nrZc3DI3CynTICIOJ2Ogn9gFX9LftYKuJysSwUNVN3WF5aOuLP/XjRSBLYc+PW3v0iqiGzMX3n1VpcyhcbsSNA7ZckGn1HZsWYwspAxkN3idSuVie9Mezm7IV4005juiYKEWEr6hlkv1lzd49QZkWOvLCFCMRiwOOGp4NyzilG1Q1Zs3G1wrcvstmasNpK+QUFNdOFvT2sm34rI4x2rQUvjC/OyqbAK+PjYmTHL47YKON5ymfUL3mAcwgUfBUSd4Wpx8G3VKg3gMcmQm27ah1knOGJWH6XulYTnfGfx6bLo5t2NGx+vZk0naqajD3auWnseobMDsFjhUIRrt6GlnfPqeFoJSm0unu3riAX+RDF/iqZdDfjhX4evETIw3SaTl8EQtVLwz7kJTnxSbTU4XTi+0M=" + notifications: email: on_success: never From 635149e3abe5621de4952d3898c37e16a8822072 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2017 10:21:01 -0800 Subject: [PATCH 09/86] signal: Ignore errors in signal handler Closes alexcrichton/tokio-signal#3 --- src/unix.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index b5067c363..3b2efdb8d 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -348,14 +348,9 @@ extern fn handler(signum: c_int, }; if !state.ready.swap(true, Ordering::SeqCst) { - match (&(*GLOBAL_STATE).write).write(&[1]) { - Ok(..) => {} - Err(e) => { - if e.kind() != io::ErrorKind::WouldBlock { - panic!("bad error on write fd: {}", e) - } - } - } + // Ignore errors here as we're not in a context that can panic, + // and otherwise there's not much we can do. + drop((&(*GLOBAL_STATE).write).write(&[1])); } let fnptr = state.prev.sa_sigaction; From 92b93ee176bfc784051cfa0d6a965d3ab7cd7303 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 11 Jan 2017 18:56:15 -0800 Subject: [PATCH 10/86] signal: Bump to 0.1.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1a137b7a0..2616df700 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.1.0" +version = "0.1.1" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From 4519ac8e17eba8a2d976c7b5f1677c15eca69e2f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Jan 2017 10:25:51 -0800 Subject: [PATCH 11/86] signal: Remove use of deprecated APIs on Unix --- Cargo.toml | 2 +- src/unix.rs | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2616df700..5a5a6d9cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ An implementation of an asynchronous Unix signal handling backed futures. [dependencies] tokio-core = "0.1" -futures = "0.1" +futures = "0.1.7" [target.'cfg(unix)'.dependencies] tokio-uds = "0.1" diff --git a/src/unix.rs b/src/unix.rs index 3b2efdb8d..808608ce6 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -15,13 +15,15 @@ use std::mem; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Once, ONCE_INIT, Mutex}; -use futures::stream::{Stream, Fuse}; -use futures::{self, Future, IntoFuture, Complete, Oneshot, Poll, Async}; +use futures::future; +use futures::stream::Fuse; +use futures::sync::mpsc; +use futures::sync::oneshot; +use futures::{Future, Stream, IntoFuture, Poll, Async}; use self::libc::c_int; use self::tokio_uds::UnixStream; use tokio_core::io::IoFuture; use tokio_core::reactor::{PollEvented, Handle}; -use tokio_core::channel::{channel, Sender, Receiver}; static INIT: Once = ONCE_INIT; static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; @@ -66,12 +68,12 @@ static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; pub struct Signal { signum: c_int, reg: PollEvented, - _finished: Complete<()>, + _finished: oneshot::Sender<()>, } struct GlobalState { write: UnixStream, - tx: Mutex>, + tx: Mutex>, signals: [GlobalSignalState; 32], } @@ -81,19 +83,19 @@ struct GlobalSignalState { } enum Message { - NewSignal(c_int, Complete>), + NewSignal(c_int, oneshot::Sender>), } struct DriverTask { handle: Handle, read: UnixStream, - rx: Fuse>, + rx: Fuse>, signals: [SignalState; 32], } struct SignalState { registered: bool, - tasks: Vec<(RefCell>, mio::SetReadiness)>, + tasks: Vec<(RefCell>, mio::SetReadiness)>, } pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; @@ -126,8 +128,8 @@ impl Signal { INIT.call_once(|| { init = Some(global_init(handle)); }); - let new_signal = futures::lazy(move || { - let (tx, rx) = futures::oneshot(); + let new_signal = future::lazy(move || { + let (tx, rx) = oneshot::channel(); let msg = Message::NewSignal(signum, tx); let res = unsafe { (*GLOBAL_STATE).tx.lock().unwrap().send(msg) @@ -162,7 +164,7 @@ impl Stream for Signal { } fn global_init(handle: &Handle) -> io::Result<()> { - let (tx, rx) = try!(channel(handle)); + let (tx, rx) = mpsc::unbounded(); let (read, write) = try!(UnixStream::pair(handle)); unsafe { let state = Box::new(GlobalState { @@ -232,11 +234,10 @@ impl DriverTask { fn check_messages(&mut self) { loop { // Acquire the next message - let message = match self.rx.poll() { - Ok(Async::Ready(Some(e))) => e, - Ok(Async::Ready(None)) | - Ok(Async::NotReady) => break, - Err(e) => panic!("error on rx: {}", e), + let message = match self.rx.poll().unwrap() { + Async::Ready(Some(e)) => e, + Async::Ready(None) | + Async::NotReady => break, }; let (sig, complete) = match message { Message::NewSignal(sig, complete) => (sig, complete), @@ -281,7 +282,7 @@ impl DriverTask { // Create the `Signal` to pass back and then also keep a handle to // the `SetReadiness` for ourselves internally. - let (tx, rx) = futures::oneshot(); + let (tx, rx) = oneshot::channel(); let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); complete.complete(Ok(Signal { signum: sig, From b33ae3cdd642ddf2f369ce849b0b174c02a98a86 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Jan 2017 10:29:22 -0800 Subject: [PATCH 12/86] signal: Remove deprecated API usage on Windows --- src/unix.rs | 8 ++++---- src/windows.rs | 41 +++++++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 808608ce6..00022f1f1 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -13,7 +13,7 @@ use std::cell::RefCell; use std::io::{self, Write, Read}; use std::mem; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Once, ONCE_INIT, Mutex}; +use std::sync::{Once, ONCE_INIT}; use futures::future; use futures::stream::Fuse; @@ -73,7 +73,7 @@ pub struct Signal { struct GlobalState { write: UnixStream, - tx: Mutex>, + tx: mpsc::UnboundedSender, signals: [GlobalSignalState; 32], } @@ -132,7 +132,7 @@ impl Signal { let (tx, rx) = oneshot::channel(); let msg = Message::NewSignal(signum, tx); let res = unsafe { - (*GLOBAL_STATE).tx.lock().unwrap().send(msg) + (*GLOBAL_STATE).tx.clone().send(msg) }; res.expect("failed to request a new signal stream, did the \ first event loop go away?"); @@ -183,7 +183,7 @@ fn global_init(handle: &Handle) -> io::Result<()> { new(), new(), new(), new(), new(), new(), new(), new(), ] }, - tx: Mutex::new(tx.clone()), + tx: tx, }); GLOBAL_STATE = Box::into_raw(state); diff --git a/src/windows.rs b/src/windows.rs index b81bf2dad..ddb44efa3 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -14,13 +14,15 @@ extern crate winapi; use std::cell::RefCell; use std::io; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Once, ONCE_INIT, Mutex}; +use std::sync::{Once, ONCE_INIT}; -use futures::stream::{Stream, Fuse}; -use futures::{self, Future, IntoFuture, Complete, Oneshot, Poll, Async}; +use futures::future; +use futures::stream::Fuse; +use futures::sync::mpsc; +use futures::sync::oneshot; +use futures::{Future, IntoFuture, Poll, Async, Stream}; use tokio_core::io::IoFuture; use tokio_core::reactor::{PollEvented, Handle}; -use tokio_core::channel::{channel, Sender, Receiver}; static INIT: Once = ONCE_INIT; static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; @@ -40,12 +42,12 @@ static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; /// two notifications. pub struct Event { reg: PollEvented, - _finished: Complete<()>, + _finished: oneshot::Sender<()>, } struct GlobalState { ready: mio::SetReadiness, - tx: Mutex>, + tx: mpsc::UnboundedSender, ctrl_c: GlobalEventState, ctrl_break: GlobalEventState, } @@ -55,19 +57,19 @@ struct GlobalEventState { } enum Message { - NewEvent(winapi::DWORD, Complete>), + NewEvent(winapi::DWORD, oneshot::Sender>), } struct DriverTask { handle: Handle, reg: PollEvented, - rx: Fuse>, + rx: Fuse>, ctrl_c: EventState, ctrl_break: EventState, } struct EventState { - tasks: Vec<(RefCell>, mio::SetReadiness)>, + tasks: Vec<(RefCell>, mio::SetReadiness)>, } impl Event { @@ -92,11 +94,11 @@ impl Event { INIT.call_once(|| { init = Some(global_init(handle)); }); - let new_signal = futures::lazy(move || { - let (tx, rx) = futures::oneshot(); + let new_signal = future::lazy(move || { + let (tx, rx) = oneshot::channel(); let msg = Message::NewEvent(signum, tx); let res = unsafe { - (*GLOBAL_STATE).tx.lock().unwrap().send(msg) + (*GLOBAL_STATE).tx.clone().send(msg) }; res.expect("failed to request a new signal stream, did the \ first event loop go away?"); @@ -128,7 +130,7 @@ impl Stream for Event { } fn global_init(handle: &Handle) -> io::Result<()> { - let (tx, rx) = try!(channel(handle)); + let (tx, rx) = mpsc::unbounded(); let reg = MyRegistration { inner: RefCell::new(None) }; let reg = try!(PollEvented::new(reg, handle)); let ready = reg.get_ref().inner.borrow().as_ref().unwrap().1.clone(); @@ -137,7 +139,7 @@ fn global_init(handle: &Handle) -> io::Result<()> { ready: ready, ctrl_c: GlobalEventState { ready: AtomicBool::new(false) }, ctrl_break: GlobalEventState { ready: AtomicBool::new(false) }, - tx: Mutex::new(tx.clone()), + tx: tx, }); GLOBAL_STATE = Box::into_raw(state); @@ -187,11 +189,10 @@ impl DriverTask { fn check_messages(&mut self) { loop { // Acquire the next message - let message = match self.rx.poll() { - Ok(Async::Ready(Some(e))) => e, - Ok(Async::Ready(None)) | - Ok(Async::NotReady) => break, - Err(e) => panic!("error on rx: {}", e), + let message = match self.rx.poll().unwrap() { + Async::Ready(Some(e)) => e, + Async::Ready(None) | + Async::NotReady => break, }; let (sig, complete) = match message { Message::NewEvent(sig, complete) => (sig, complete), @@ -216,7 +217,7 @@ impl DriverTask { // Create the `Event` to pass back and then also keep a handle to // the `SetReadiness` for ourselves internally. - let (tx, rx) = futures::oneshot(); + let (tx, rx) = oneshot::channel(); let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); complete.complete(Ok(Event { reg: reg, From 468b037e4e2e73a78cb18b7b596d27193fe2c5aa Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Jan 2017 10:32:16 -0800 Subject: [PATCH 13/86] signal: Update docs urls and such --- Cargo.toml | 2 +- README.md | 4 ++-- src/lib.rs | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a5a6d9cc..296f35e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" homepage = "https://github.com/alexcrichton/tokio-signal" -documentation = "https://alexcrichton.github.io/tokio-signal" +documentation = "https://docs.rs/tokio-signal/0.1" description = """ An implementation of an asynchronous Unix signal handling backed futures. """ diff --git a/README.md b/README.md index 6cedacb64..e6aeda77f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ An implementation of Unix signal handling for Tokio [![Build Status](https://travis-ci.org/alexcrichton/tokio-signal.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-signal) -[Documentation](https://alexcrichton.github.io/tokio-signal) +[Documentation](https://docs.rs/tokio-signal) ## Usage @@ -12,7 +12,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tokio-signal = { git = "https://github.com/alexcrichton/tokio-signal" } +tokio-signal = "0.1" ``` Next, add this to your crate: diff --git a/src/lib.rs b/src/lib.rs index 261dcb46a..780d7f0bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ //! > It's planned that this will be bound and exported outside the //! > `unix` module in the future! +#![doc(html_root_url = "https://docs.rs/tokio-signal/0.1")] #![deny(missing_docs)] #[macro_use] From 4142dc2faeb4a3f4c9f8ad3aa25c11d7555c04d3 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Sun, 22 Jan 2017 10:19:38 +0100 Subject: [PATCH 14/86] signal: Use tokio-core from git Just for now, as we need some yet unreleased features. --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 296f35e8a..233f72fca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,6 @@ mio = "0.6" winapi = "0.2" kernel32-sys = "0.2" mio = "0.6" + +[replace] +"tokio-core:0.1.3" = { git = "https://github.com/tokio-rs/tokio-core" } From ed4359bb26ead096452394bdb40802df75be19dc Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Sun, 22 Jan 2017 12:26:12 +0100 Subject: [PATCH 15/86] signal: Implement the wake-up part of the new signal handling Register the signal handler that wakes up someone through a self-pipe. That someone doesn't yet exist, though. Some dependencies (nix, lazy_static) added to speed up the prototyping process. They are likely to be dropped in some future commits. Some features (eg. preserving the previous signal handlers) are still missing. --- Cargo.toml | 2 ++ src/lib.rs | 2 ++ src/unix.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 233f72fca..e85932e98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ An implementation of an asynchronous Unix signal handling backed futures. [dependencies] tokio-core = "0.1" futures = "0.1.7" +lazy_static = "0.2" +nix = "0.7" [target.'cfg(unix)'.dependencies] tokio-uds = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 780d7f0bf..0af09609a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,8 @@ #[macro_use] extern crate futures; extern crate tokio_core; +#[macro_use] +extern crate lazy_static; use futures::Future; use futures::stream::Stream; diff --git a/src/unix.rs b/src/unix.rs index 00022f1f1..969e9ac2c 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -8,19 +8,88 @@ pub extern crate libc; extern crate mio; extern crate tokio_uds; +extern crate nix; + +use std::sync::{Once, ONCE_INIT}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; +use std::os::unix::io::RawFd; + +use self::libc::c_int; +use self::nix::unistd::pipe; +use self::nix::sys::signal::{SigAction, SigHandler, SigSet, SA_NOCLDSTOP, SA_RESTART, sigaction}; +use self::nix::sys::signal::Signal as NixSignal; +use self::nix::sys::socket::{send, MSG_DONTWAIT}; + +// Number of different unix signals +const SIGNUM: usize = 32; + +#[derive(Default)] +struct SignalInfo { + initialized: bool, + // TODO: Other stuff, like the previous sigaction to call +} + +struct Globals { + pending: [AtomicBool; SIGNUM], + sender: RawFd, + receiver: RawFd, + signals: [Mutex; SIGNUM], +} + +impl Globals { + fn new() -> Self { + // TODO: Better error handling + let (receiver, sender) = pipe().unwrap(); + Globals { + // Bunch of false values + pending: Default::default(), + sender: sender, + receiver: receiver, + signals: Default::default(), + } + } +} + +lazy_static! { + // TODO: Get rid of lazy_static once the prototype is done ‒ get rid of the dependency as well + // as the possible lock in there, which *might* be problematic in signals + static ref globals: Globals = Globals::new(); +} + +// Flag the relevant signal and wake up through a self-pipe +extern "C" fn pipe_wakeup(signal: c_int) { + let index = signal as usize; + // TODO: Handle the old signal handler + // It might be good enough to use some lesser ordering than this, but how to prove it? + globals.pending[index].store(true, Ordering::SeqCst); + // Send a wakeup, ignore any errors (anything reasonably possible is full pipe and then it will + // wake up anyway). + let _ = send(globals.sender, &[0u8], MSG_DONTWAIT); +} + +// Make sure we listen to the given signal and provide the recipient end of the self-pipe +fn signal_enable(signal: c_int) -> RawFd { + let index = signal as usize; + let mut siginfo = globals.signals[index].lock().unwrap(); + if !siginfo.initialized { + let action = SigAction::new(SigHandler::Handler(pipe_wakeup), SA_NOCLDSTOP | SA_RESTART, SigSet::empty()); + unsafe { sigaction(NixSignal::from_c_int(signal).unwrap(), &action).unwrap() }; + // TODO: Handle the old signal handler + siginfo.initialized = true; + } + globals.receiver +} use std::cell::RefCell; use std::io::{self, Write, Read}; use std::mem; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Once, ONCE_INIT}; use futures::future; use futures::stream::Fuse; use futures::sync::mpsc; use futures::sync::oneshot; use futures::{Future, Stream, IntoFuture, Poll, Async}; -use self::libc::c_int; use self::tokio_uds::UnixStream; use tokio_core::io::IoFuture; use tokio_core::reactor::{PollEvented, Handle}; From 04d949c380fd5c7f745f692519e3d10db3e22aca Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Sun, 22 Jan 2017 12:58:48 +0100 Subject: [PATCH 16/86] signal: Provide the new kind of Signal stream Which is just a wrapper around the futures::sync::mpsc. The sender is in a global registry. The part that connects the wakeups to the senders in the registry doesn't yet exist. --- src/unix.rs | 123 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 41 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 969e9ac2c..e99ab6d7d 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -10,16 +10,24 @@ extern crate mio; extern crate tokio_uds; extern crate nix; -use std::sync::{Once, ONCE_INIT}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use std::os::unix::io::RawFd; +use std::io; use self::libc::c_int; use self::nix::unistd::pipe; use self::nix::sys::signal::{SigAction, SigHandler, SigSet, SA_NOCLDSTOP, SA_RESTART, sigaction}; use self::nix::sys::signal::Signal as NixSignal; use self::nix::sys::socket::{send, MSG_DONTWAIT}; +use futures::{Future, IntoFuture}; +use futures::sync::mpsc::{Receiver, Sender, channel}; +use futures::{Stream, Poll}; +use tokio_core::reactor::Handle; +use tokio_core::io::IoFuture; + +pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; +pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; // Number of different unix signals const SIGNUM: usize = 32; @@ -27,6 +35,8 @@ const SIGNUM: usize = 32; #[derive(Default)] struct SignalInfo { initialized: bool, + // The ones interested in this signal + recipients: Vec>, // TODO: Other stuff, like the previous sigaction to call } @@ -81,22 +91,7 @@ fn signal_enable(signal: c_int) -> RawFd { globals.receiver } -use std::cell::RefCell; -use std::io::{self, Write, Read}; -use std::mem; - -use futures::future; -use futures::stream::Fuse; -use futures::sync::mpsc; -use futures::sync::oneshot; -use futures::{Future, Stream, IntoFuture, Poll, Async}; -use self::tokio_uds::UnixStream; -use tokio_core::io::IoFuture; -use tokio_core::reactor::{PollEvented, Handle}; - -static INIT: Once = ONCE_INIT; -static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; - +// TODO: Go through the docs, they are a copy-paste from the previous version /// An implementation of `Stream` for receiving a particular type of signal. /// /// This structure implements the `Stream` trait and represents notifications @@ -134,6 +129,74 @@ static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; /// If you've got any questions about this feel free to open an issue on the /// repo, though, as I'd love to chat about this! In other words, I'd love to /// alleviate some of these limitations if possible! +pub struct Signal(Receiver); + +impl Signal { + // TODO: Revisit the docs, they are from the previous version + /// Creates a new stream which will receive notifications when the current + /// process receives the signal `signum`. + /// + /// This function will create a new stream which may be based on the + /// event loop handle provided. This function returns a future which will + /// then resolve to the signal stream, if successful. + /// + /// The `Signal` stream is an infinite stream which will receive + /// notifications whenever a signal is received. More documentation can be + /// found on `Signal` itself, but to reiterate: + /// + /// * Signals may be coalesced beyond what the kernel already does. + /// * While multiple event loops are supported, the first event loop to + /// register a signal handler must be active to deliver signal + /// notifications + /// * Once a signal handle is registered with the process the underlying + /// libc signal handler is never unregistered. + /// + /// A `Signal` stream can be created for a particular signal number + /// multiple times. When a signal is received then all the associated + /// channels will receive the signal notification. + pub fn new(signal: c_int, handle: &Handle) -> IoFuture { + let index = signal as usize; + // One wakeup in a queue is enough + let (sender, receiver) = channel(1); + { // Hold the mutex only a short while + let mut siginfo = globals.signals[index].lock().unwrap(); + siginfo.recipients.push(sender); + } + // Turn the signal delivery on once we are ready for it + let wakeup = signal_enable(signal); + // TODO: Init the driving task for this handle + Ok(Signal(receiver)).into_future().boxed() + } +} + +impl Stream for Signal { + type Item = c_int; + type Error = io::Error; + + fn poll(&mut self) -> Poll, io::Error> { + // It seems the channel doesn't generate any errors anyway + self.0.poll().map_err(|_| io::Error::new(io::ErrorKind::Other, "Unknown futures::sync::mpsc error")) + } +} + +// TODO: Drop for Signal and remove the other end proactively? + +/* + +use std::cell::RefCell; +use std::io::{self, Write, Read}; +use std::mem; + +use futures::future; +use futures::stream::Fuse; +use futures::sync::mpsc; +use futures::sync::oneshot; +use self::tokio_uds::UnixStream; +use tokio_core::reactor::{PollEvented, Handle}; + +static INIT: Once = ONCE_INIT; +static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; + pub struct Signal { signum: c_int, reg: PollEvented, @@ -167,31 +230,7 @@ struct SignalState { tasks: Vec<(RefCell>, mio::SetReadiness)>, } -pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; -pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; - impl Signal { - /// Creates a new stream which will receive notifications when the current - /// process receives the signal `signum`. - /// - /// This function will create a new stream which may be based on the - /// event loop handle provided. This function returns a future which will - /// then resolve to the signal stream, if successful. - /// - /// The `Signal` stream is an infinite stream which will receive - /// notifications whenever a signal is received. More documentation can be - /// found on `Signal` itself, but to reiterate: - /// - /// * Signals may be coalesced beyond what the kernel already does. - /// * While multiple event loops are supported, the first event loop to - /// register a signal handler must be active to deliver signal - /// notifications - /// * Once a signal handle is registered with the process the underlying - /// libc signal handler is never unregistered. - /// - /// A `Signal` stream can be created for a particular signal number - /// multiple times. When a signal is received then all the associated - /// channels will receive the signal notification. pub fn new(signum: c_int, handle: &Handle) -> IoFuture { let mut init = None; INIT.call_once(|| { @@ -464,3 +503,5 @@ impl mio::Evented for MyRegistration { Ok(()) } } + +*/ From 367cb56e02318807b2a2560f0386dfdeb1e18239 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Sun, 22 Jan 2017 14:48:22 +0100 Subject: [PATCH 17/86] signal: The driver task Add the driver task, connecting the signal handler wakeups to the wakeups of of the streams. It is a prototype-quality code, a lot of cleanups and similar is needed. --- Cargo.toml | 1 - src/unix.rs | 480 ++++++++++++++---------------------------------- tests/signal.rs | 32 ---- 3 files changed, 138 insertions(+), 375 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e85932e98..35f0d7f88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ lazy_static = "0.2" nix = "0.7" [target.'cfg(unix)'.dependencies] -tokio-uds = "0.1" libc = "0.2" mio = "0.6" diff --git a/src/unix.rs b/src/unix.rs index e99ab6d7d..1239559bc 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -7,23 +7,27 @@ pub extern crate libc; extern crate mio; -extern crate tokio_uds; extern crate nix; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use std::os::unix::io::RawFd; +use std::collections::HashSet; use std::io; use self::libc::c_int; -use self::nix::unistd::pipe; -use self::nix::sys::signal::{SigAction, SigHandler, SigSet, SA_NOCLDSTOP, SA_RESTART, sigaction}; +use self::nix::sys::signal::{sigaction, SigAction, SigHandler, SigSet, SA_NOCLDSTOP, SA_RESTART}; use self::nix::sys::signal::Signal as NixSignal; -use self::nix::sys::socket::{send, MSG_DONTWAIT}; -use futures::{Future, IntoFuture}; +use self::nix::Error as NixError; +use self::nix::Errno; +use self::nix::sys::socket::{recv, send, socketpair, AddressFamily, SockType, SockFlag, MSG_DONTWAIT}; +use self::mio::{Evented, Token, Ready, PollOpt}; +use self::mio::Poll as MioPoll; +use self::mio::unix::EventedFd; +use futures::{Async, AsyncSink, Future, IntoFuture}; use futures::sync::mpsc::{Receiver, Sender, channel}; -use futures::{Stream, Poll}; -use tokio_core::reactor::Handle; +use futures::{Sink, Stream, Poll}; +use tokio_core::reactor::{Handle, CoreId, PollEvented}; use tokio_core::io::IoFuture; pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; @@ -45,18 +49,21 @@ struct Globals { sender: RawFd, receiver: RawFd, signals: [Mutex; SIGNUM], + drivers: Mutex>, } impl Globals { fn new() -> Self { // TODO: Better error handling - let (receiver, sender) = pipe().unwrap(); + // We use socket pair instead of pipe, as it allows send() and recv(). + let (receiver, sender) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty()).unwrap(); Globals { // Bunch of false values pending: Default::default(), sender: sender, receiver: receiver, signals: Default::default(), + drivers: Mutex::new(HashSet::new()), } } } @@ -64,7 +71,7 @@ impl Globals { lazy_static! { // TODO: Get rid of lazy_static once the prototype is done ‒ get rid of the dependency as well // as the possible lock in there, which *might* be problematic in signals - static ref globals: Globals = Globals::new(); + static ref GLOBALS: Globals = Globals::new(); } // Flag the relevant signal and wake up through a self-pipe @@ -72,23 +79,129 @@ extern "C" fn pipe_wakeup(signal: c_int) { let index = signal as usize; // TODO: Handle the old signal handler // It might be good enough to use some lesser ordering than this, but how to prove it? - globals.pending[index].store(true, Ordering::SeqCst); + GLOBALS.pending[index].store(true, Ordering::SeqCst); // Send a wakeup, ignore any errors (anything reasonably possible is full pipe and then it will // wake up anyway). - let _ = send(globals.sender, &[0u8], MSG_DONTWAIT); + let _ = send(GLOBALS.sender, &[0u8], MSG_DONTWAIT); } // Make sure we listen to the given signal and provide the recipient end of the self-pipe -fn signal_enable(signal: c_int) -> RawFd { +fn signal_enable(signal: c_int) { let index = signal as usize; - let mut siginfo = globals.signals[index].lock().unwrap(); + let mut siginfo = GLOBALS.signals[index].lock().unwrap(); if !siginfo.initialized { let action = SigAction::new(SigHandler::Handler(pipe_wakeup), SA_NOCLDSTOP | SA_RESTART, SigSet::empty()); unsafe { sigaction(NixSignal::from_c_int(signal).unwrap(), &action).unwrap() }; // TODO: Handle the old signal handler siginfo.initialized = true; } - globals.receiver +} + +struct EventedReceiver; + +impl Evented for EventedReceiver { + fn register(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { + EventedFd(&GLOBALS.receiver).register(poll, token, events, opts) + } + fn reregister(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { + EventedFd(&GLOBALS.receiver).reregister(poll, token, events, opts) + } + fn deregister(&self, poll: &MioPoll) -> io::Result<()> { + EventedFd(&GLOBALS.receiver).deregister(poll) + } +} + +// There'll be stuff inside +struct Driver { + id: CoreId, + wakeup: PollEvented, +} + +impl Future for Driver { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + // Drain the data from the pipe and maintain interest in getting more + let any_wakeup = self.drain(); + if any_wakeup { + self.broadcast(); + } + // This task just lives until the end of the event loop + Ok(Async::NotReady) + } +} + +impl Drop for Driver { + fn drop(&mut self) { + let mut drivers = GLOBALS.drivers.lock().unwrap(); + drivers.remove(&self.id); + } +} + +impl Driver { + fn new(handle: &Handle) -> Self { + Driver { + id: handle.id(), + // TODO: Any chance of errors here? + wakeup: PollEvented::new(EventedReceiver, handle).unwrap(), + } + } + // Drain all data in the pipe and maintain an interest in read-ready + fn drain(&self) -> bool { + // Inform tokio we're interested in reading. It also hints on + // if we may be readable. + if let Async::NotReady = self.wakeup.poll_read() { + return false; + } + // Read all available data (until EAGAIN) + let mut received = false; + let mut buffer = [0; 1024]; + loop { + match recv(GLOBALS.receiver, &mut buffer, MSG_DONTWAIT) { + Ok(0) => panic!("EOF on self-pipe"), + Ok(_) => received = true, + Err(NixError::Sys(Errno::EAGAIN)) => break, + Err(NixError::Sys(Errno::EINTR)) => (), + Err(e) => panic!("Bad read on self-pipe: {}", e), + } + } + + // If we got here, it's because we got EAGAIN above. Ask for more data. + self.wakeup.need_read(); + + received + } + // Go through all the signals and broadcast everything + fn broadcast(&self) { + for (sig, value) in GLOBALS.pending.iter().enumerate() { + // Any signal of this kind arrived since we checked last? + if value.swap(false, Ordering::SeqCst) { + let signum = sig as c_int; + let mut siginfo = GLOBALS.signals[sig].lock().unwrap(); + // It doesn't seem to be possible to do this through the iterators for now. + // This trick is copied from https://github.com/rust-lang/rfcs/pull/1353. + for i in (0 .. siginfo.recipients.len()).rev() { + // TODO: This thing probably generates unnecessary wakups of this task. + // But let's optimise it later on, when we know this works. + match siginfo.recipients[i].start_send(signum) { + // We don't care if it was full or not ‒ we just want to wake up the other + // side. + Ok(AsyncSink::Ready) => { + // We are required to call this if we push something inside + let _ = siginfo.recipients[i].poll_complete(); + }, + // The channel is full -> it'll get woken up anyway + Ok(AsyncSink::NotReady(_)) => (), + // The other side disappeared, drop this end. + Err(_) => { + siginfo.recipients.swap_remove(i); + }, + } + } + } + } + } } // TODO: Go through the docs, they are a copy-paste from the previous version @@ -158,12 +271,20 @@ impl Signal { let index = signal as usize; // One wakeup in a queue is enough let (sender, receiver) = channel(1); - { // Hold the mutex only a short while - let mut siginfo = globals.signals[index].lock().unwrap(); + { + let mut siginfo = GLOBALS.signals[index].lock().unwrap(); siginfo.recipients.push(sender); } // Turn the signal delivery on once we are ready for it - let wakeup = signal_enable(signal); + signal_enable(signal); + let id = handle.id(); + { + let mut drivers = GLOBALS.drivers.lock().unwrap(); + if !drivers.contains(&id) { + handle.spawn(Driver::new(handle)); + drivers.insert(id); + } + } // TODO: Init the driving task for this handle Ok(Signal(receiver)).into_future().boxed() } @@ -180,328 +301,3 @@ impl Stream for Signal { } // TODO: Drop for Signal and remove the other end proactively? - -/* - -use std::cell::RefCell; -use std::io::{self, Write, Read}; -use std::mem; - -use futures::future; -use futures::stream::Fuse; -use futures::sync::mpsc; -use futures::sync::oneshot; -use self::tokio_uds::UnixStream; -use tokio_core::reactor::{PollEvented, Handle}; - -static INIT: Once = ONCE_INIT; -static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; - -pub struct Signal { - signum: c_int, - reg: PollEvented, - _finished: oneshot::Sender<()>, -} - -struct GlobalState { - write: UnixStream, - tx: mpsc::UnboundedSender, - signals: [GlobalSignalState; 32], -} - -struct GlobalSignalState { - ready: AtomicBool, - prev: libc::sigaction, -} - -enum Message { - NewSignal(c_int, oneshot::Sender>), -} - -struct DriverTask { - handle: Handle, - read: UnixStream, - rx: Fuse>, - signals: [SignalState; 32], -} - -struct SignalState { - registered: bool, - tasks: Vec<(RefCell>, mio::SetReadiness)>, -} - -impl Signal { - pub fn new(signum: c_int, handle: &Handle) -> IoFuture { - let mut init = None; - INIT.call_once(|| { - init = Some(global_init(handle)); - }); - let new_signal = future::lazy(move || { - let (tx, rx) = oneshot::channel(); - let msg = Message::NewSignal(signum, tx); - let res = unsafe { - (*GLOBAL_STATE).tx.clone().send(msg) - }; - res.expect("failed to request a new signal stream, did the \ - first event loop go away?"); - rx.then(|r| r.unwrap()) - }); - match init { - Some(init) => init.into_future().and_then(|()| new_signal).boxed(), - None => new_signal.boxed(), - } - } -} - -impl Stream for Signal { - type Item = c_int; - type Error = io::Error; - - fn poll(&mut self) -> Poll, io::Error> { - if !self.reg.poll_read().is_ready() { - return Ok(Async::NotReady) - } - self.reg.need_read(); - self.reg.get_ref() - .inner.borrow() - .as_ref().unwrap().1 - .set_readiness(mio::Ready::none()) - .expect("failed to set readiness"); - Ok(Async::Ready(Some(self.signum))) - } -} - -fn global_init(handle: &Handle) -> io::Result<()> { - let (tx, rx) = mpsc::unbounded(); - let (read, write) = try!(UnixStream::pair(handle)); - unsafe { - let state = Box::new(GlobalState { - write: write, - signals: { - fn new() -> GlobalSignalState { - GlobalSignalState { - ready: AtomicBool::new(false), - prev: unsafe { mem::zeroed() }, - } - } - [ - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - ] - }, - tx: tx, - }); - GLOBAL_STATE = Box::into_raw(state); - - handle.spawn(DriverTask { - handle: handle.clone(), - rx: rx.fuse(), - read: read, - signals: { - fn new() -> SignalState { - SignalState { registered: false, tasks: Vec::new() } - } - [ - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - new(), new(), new(), new(), new(), new(), new(), new(), - ] - }, - }); - - Ok(()) - } -} - -impl Future for DriverTask { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), ()> { - self.check_signal_drops(); - self.check_messages(); - self.check_signals(); - - // TODO: when to finish this task? - Ok(Async::NotReady) - } -} - -impl DriverTask { - fn check_signal_drops(&mut self) { - for signal in self.signals.iter_mut() { - signal.tasks.retain(|task| { - !task.0.borrow_mut().poll().is_err() - }); - } - } - - fn check_messages(&mut self) { - loop { - // Acquire the next message - let message = match self.rx.poll().unwrap() { - Async::Ready(Some(e)) => e, - Async::Ready(None) | - Async::NotReady => break, - }; - let (sig, complete) = match message { - Message::NewSignal(sig, complete) => (sig, complete), - }; - - // If the signal's too large, then we return an error, otherwise we - // use this index to look at the signal slot. - // - // If the signal wasn't previously registered then we do so now. - let signal = match self.signals.get_mut(sig as usize) { - Some(signal) => signal, - None => { - complete.complete(Err(io::Error::new(io::ErrorKind::Other, - "signum too large"))); - continue - } - }; - if !signal.registered { - unsafe { - let mut new: libc::sigaction = mem::zeroed(); - new.sa_sigaction = handler as usize; - new.sa_flags = libc::SA_RESTART | libc::SA_SIGINFO; - let mut prev = mem::zeroed(); - if libc::sigaction(sig, &new, &mut prev) != 0 { - complete.complete(Err(io::Error::last_os_error())); - continue - } - signal.registered = true; - } - } - - // Acquire the (registration, set_readiness) pair by... assuming - // we're on the event loop (true because of the spawn above). - let reg = MyRegistration { inner: RefCell::new(None) }; - let reg = match PollEvented::new(reg, &self.handle) { - Ok(reg) => reg, - Err(e) => { - complete.complete(Err(e)); - continue - } - }; - - // Create the `Signal` to pass back and then also keep a handle to - // the `SetReadiness` for ourselves internally. - let (tx, rx) = oneshot::channel(); - let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); - complete.complete(Ok(Signal { - signum: sig, - reg: reg, - _finished: tx, - })); - signal.tasks.push((RefCell::new(rx), ready)); - } - } - - fn check_signals(&mut self) { - // Drain all data from the pipe - let mut buf = [0; 32]; - let mut any = false; - loop { - match self.read.read(&mut buf) { - Ok(0) => { // EOF == something happened - any = true; - break - } - Ok(..) => any = true, // data read, but keep draining - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, - Err(e) => panic!("bad read: {}", e), - } - } - - // If nothing happened, no need to check the signals - if !any { - return - } - - for (i, slot) in self.signals.iter().enumerate() { - // No need to go farther if we haven't even registered a signal - if !slot.registered { - continue - } - - // See if this signal actually happened since we last checked - unsafe { - if !(*GLOBAL_STATE).signals[i].ready.swap(false, Ordering::SeqCst) { - continue - } - } - - // Wake up all the tasks waiting on this signal - for task in slot.tasks.iter() { - task.1.set_readiness(mio::Ready::readable()) - .expect("failed to set readiness"); - } - } - } -} - -extern fn handler(signum: c_int, - info: *mut libc::siginfo_t, - ptr: *mut libc::c_void) { - type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); - type FnHandler = extern fn(c_int); - - unsafe { - let state = match (*GLOBAL_STATE).signals.get(signum as usize) { - Some(state) => state, - None => return, - }; - - if !state.ready.swap(true, Ordering::SeqCst) { - // Ignore errors here as we're not in a context that can panic, - // and otherwise there's not much we can do. - drop((&(*GLOBAL_STATE).write).write(&[1])); - } - - let fnptr = state.prev.sa_sigaction; - if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { - return - } - if state.prev.sa_flags & libc::SA_SIGINFO == 0 { - let action = mem::transmute::(fnptr); - action(signum) - } else { - let action = mem::transmute::(fnptr); - action(signum, info, ptr) - } - } -} - -struct MyRegistration { - inner: RefCell>, -} - -impl mio::Evented for MyRegistration { - fn register(&self, - poll: &mio::Poll, - token: mio::Token, - events: mio::Ready, - opts: mio::PollOpt) -> io::Result<()> { - let reg = mio::Registration::new(poll, token, events, opts); - *self.inner.borrow_mut() = Some(reg); - Ok(()) - } - - fn reregister(&self, - _poll: &mio::Poll, - _token: mio::Token, - _events: mio::Ready, - _opts: mio::PollOpt) -> io::Result<()> { - Ok(()) - } - - fn deregister(&self, _poll: &mio::Poll) -> io::Result<()> { - Ok(()) - } -} - -*/ diff --git a/tests/signal.rs b/tests/signal.rs index 529e740c2..06bcefa6e 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -5,9 +5,6 @@ extern crate libc; extern crate tokio_core; extern crate tokio_signal; -use std::sync::mpsc::channel; -use std::sync::{Once, ONCE_INIT, Mutex, MutexGuard}; -use std::thread; use std::time::Duration; use futures::Future; @@ -15,31 +12,8 @@ use futures::stream::Stream; use tokio_core::reactor::{Core, Timeout}; use tokio_signal::unix::Signal; -static INIT: Once = ONCE_INIT; -static mut LOCK: *mut Mutex<()> = 0 as *mut _; - -fn lock() -> MutexGuard<'static, ()> { - unsafe { - INIT.call_once(|| { - LOCK = Box::into_raw(Box::new(Mutex::new(()))); - let (tx, rx) = channel(); - thread::spawn(move || { - let mut lp = Core::new().unwrap(); - let handle = lp.handle(); - let _signal = lp.run(Signal::new(libc::SIGALRM, &handle)).unwrap(); - tx.send(()).unwrap(); - drop(lp.run(futures::empty::<(), ()>())); - }); - rx.recv().unwrap(); - }); - (*LOCK).lock().unwrap() - } -} - #[test] fn simple() { - let _lock = lock(); - let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); @@ -51,8 +25,6 @@ fn simple() { #[test] fn notify_both() { - let _lock = lock(); - let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); @@ -65,8 +37,6 @@ fn notify_both() { #[test] fn drop_then_get_a_signal() { - let _lock = lock(); - let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); @@ -80,8 +50,6 @@ fn drop_then_get_a_signal() { #[test] fn twice() { - let _lock = lock(); - let mut lp = Core::new().unwrap(); let handle = lp.handle(); let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); From ba0921a01d77096337788db4bbfa2135d9aa703d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 10:02:42 -0800 Subject: [PATCH 18/86] signal: Various cleanups: * Drop nix/lazy_static * Use previously registered handlers * Handle some more errors --- Cargo.toml | 8 +- src/lib.rs | 2 - src/unix.rs | 240 +++++++++++++++++++++++++++++++--------------------- 3 files changed, 144 insertions(+), 106 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35f0d7f88..3015dfdc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,19 +11,15 @@ An implementation of an asynchronous Unix signal handling backed futures. """ [dependencies] -tokio-core = "0.1" +tokio-core = "0.1.4" futures = "0.1.7" -lazy_static = "0.2" -nix = "0.7" [target.'cfg(unix)'.dependencies] libc = "0.2" mio = "0.6" +mio-uds = "0.6" [target.'cfg(windows)'.dependencies] winapi = "0.2" kernel32-sys = "0.2" mio = "0.6" - -[replace] -"tokio-core:0.1.3" = { git = "https://github.com/tokio-rs/tokio-core" } diff --git a/src/lib.rs b/src/lib.rs index 0af09609a..780d7f0bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,8 +25,6 @@ #[macro_use] extern crate futures; extern crate tokio_core; -#[macro_use] -extern crate lazy_static; use futures::Future; use futures::stream::Stream; diff --git a/src/unix.rs b/src/unix.rs index 1239559bc..7aa1618eb 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -7,28 +7,28 @@ pub extern crate libc; extern crate mio; -extern crate nix; +extern crate mio_uds; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; -use std::os::unix::io::RawFd; +use std::cell::UnsafeCell; use std::collections::HashSet; +use std::io::prelude::*; use std::io; +use std::mem; +use std::os::unix::prelude::*; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Mutex, Once, ONCE_INIT}; +use futures::future; +use futures::sync::mpsc::{Receiver, Sender, channel}; +use futures::{Async, AsyncSink, Future, IntoFuture}; +use futures::{Sink, Stream, Poll}; use self::libc::c_int; -use self::nix::sys::signal::{sigaction, SigAction, SigHandler, SigSet, SA_NOCLDSTOP, SA_RESTART}; -use self::nix::sys::signal::Signal as NixSignal; -use self::nix::Error as NixError; -use self::nix::Errno; -use self::nix::sys::socket::{recv, send, socketpair, AddressFamily, SockType, SockFlag, MSG_DONTWAIT}; -use self::mio::{Evented, Token, Ready, PollOpt}; use self::mio::Poll as MioPoll; use self::mio::unix::EventedFd; -use futures::{Async, AsyncSink, Future, IntoFuture}; -use futures::sync::mpsc::{Receiver, Sender, channel}; -use futures::{Sink, Stream, Poll}; -use tokio_core::reactor::{Handle, CoreId, PollEvented}; +use self::mio::{Evented, Token, Ready, PollOpt}; +use self::mio_uds::UnixStream; use tokio_core::io::IoFuture; +use tokio_core::reactor::{Handle, CoreId, PollEvented}; pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; @@ -36,82 +36,133 @@ pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; // Number of different unix signals const SIGNUM: usize = 32; -#[derive(Default)] struct SignalInfo { - initialized: bool, + pending: AtomicBool, // The ones interested in this signal - recipients: Vec>, - // TODO: Other stuff, like the previous sigaction to call + recipients: Mutex>>, + + init: Once, + initialized: UnsafeCell, + prev: UnsafeCell, } struct Globals { - pending: [AtomicBool; SIGNUM], - sender: RawFd, - receiver: RawFd, - signals: [Mutex; SIGNUM], + sender: UnixStream, + receiver: UnixStream, + signals: [SignalInfo; SIGNUM], drivers: Mutex>, } -impl Globals { - fn new() -> Self { - // TODO: Better error handling - // We use socket pair instead of pipe, as it allows send() and recv(). - let (receiver, sender) = socketpair(AddressFamily::Unix, SockType::Stream, 0, SockFlag::empty()).unwrap(); - Globals { - // Bunch of false values - pending: Default::default(), - sender: sender, - receiver: receiver, - signals: Default::default(), - drivers: Mutex::new(HashSet::new()), +impl Default for SignalInfo { + fn default() -> SignalInfo { + SignalInfo { + pending: AtomicBool::new(false), + init: ONCE_INIT, + initialized: UnsafeCell::new(false), + recipients: Mutex::new(Vec::new()), + prev: UnsafeCell::new(unsafe { mem::zeroed() }), } } } -lazy_static! { - // TODO: Get rid of lazy_static once the prototype is done ‒ get rid of the dependency as well - // as the possible lock in there, which *might* be problematic in signals - static ref GLOBALS: Globals = Globals::new(); +static mut GLOBALS: *mut Globals = 0 as *mut Globals; + +fn globals() -> &'static Globals { + static INIT: Once = ONCE_INIT; + + unsafe { + INIT.call_once(|| { + let (receiver, sender) = UnixStream::pair().unwrap(); + let globals = Globals { + sender: sender, + receiver: receiver, + signals: Default::default(), + drivers: Mutex::new(HashSet::new()), + }; + GLOBALS = Box::into_raw(Box::new(globals)); + }); + &*GLOBALS + } } // Flag the relevant signal and wake up through a self-pipe -extern "C" fn pipe_wakeup(signal: c_int) { - let index = signal as usize; - // TODO: Handle the old signal handler - // It might be good enough to use some lesser ordering than this, but how to prove it? - GLOBALS.pending[index].store(true, Ordering::SeqCst); - // Send a wakeup, ignore any errors (anything reasonably possible is full pipe and then it will - // wake up anyway). - let _ = send(GLOBALS.sender, &[0u8], MSG_DONTWAIT); +extern fn handler(signum: c_int, + info: *mut libc::siginfo_t, + ptr: *mut libc::c_void) { + type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); + type FnHandler = extern fn(c_int); + unsafe { + let slot = match (*GLOBALS).signals.get(signum as usize) { + Some(slot) => slot, + None => return, + }; + slot.pending.store(true, Ordering::SeqCst); + + // Send a wakeup, ignore any errors (anything reasonably possible is + // full pipe and then it will wake up anyway). + drop((*GLOBALS).sender.write(&[1])); + + let fnptr = (*slot.prev.get()).sa_sigaction; + if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { + return + } + if (*slot.prev.get()).sa_flags & libc::SA_SIGINFO == 0 { + let action = mem::transmute::(fnptr); + action(signum) + } else { + let action = mem::transmute::(fnptr); + action(signum, info, ptr) + } + } } -// Make sure we listen to the given signal and provide the recipient end of the self-pipe -fn signal_enable(signal: c_int) { - let index = signal as usize; - let mut siginfo = GLOBALS.signals[index].lock().unwrap(); - if !siginfo.initialized { - let action = SigAction::new(SigHandler::Handler(pipe_wakeup), SA_NOCLDSTOP | SA_RESTART, SigSet::empty()); - unsafe { sigaction(NixSignal::from_c_int(signal).unwrap(), &action).unwrap() }; - // TODO: Handle the old signal handler - siginfo.initialized = true; - } +// Make sure we listen to the given signal and provide the recipient end of the +// self-pipe +fn signal_enable(signal: c_int) -> io::Result<()> { + let siginfo = &globals().signals[signal as usize]; + unsafe { + let mut err = None; + siginfo.init.call_once(|| { + let mut new: libc::sigaction = mem::zeroed(); + new.sa_sigaction = handler as usize; + new.sa_flags = libc::SA_RESTART | + libc::SA_SIGINFO | + libc::SA_NOCLDSTOP; + if libc::sigaction(signal, &new, &mut *siginfo.prev.get()) != 0 { + err = Some(io::Error::last_os_error()); + } else { + *siginfo.initialized.get() = true; + } + }); + if let Some(err) = err { + return Err(err) + } + if *siginfo.initialized.get() { + Ok(()) + } else { + Err(io::Error::new(io::ErrorKind::Other, + "failed to register signal handler")) + } + } } struct EventedReceiver; impl Evented for EventedReceiver { fn register(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { - EventedFd(&GLOBALS.receiver).register(poll, token, events, opts) + let fd = globals().receiver.as_raw_fd(); + EventedFd(&fd).register(poll, token, events, opts) } fn reregister(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { - EventedFd(&GLOBALS.receiver).reregister(poll, token, events, opts) + let fd = globals().receiver.as_raw_fd(); + EventedFd(&fd).reregister(poll, token, events, opts) } fn deregister(&self, poll: &MioPoll) -> io::Result<()> { - EventedFd(&GLOBALS.receiver).deregister(poll) + let fd = globals().receiver.as_raw_fd(); + EventedFd(&fd).deregister(poll) } } -// There'll be stuff inside struct Driver { id: CoreId, wakeup: PollEvented, @@ -134,7 +185,7 @@ impl Future for Driver { impl Drop for Driver { fn drop(&mut self) { - let mut drivers = GLOBALS.drivers.lock().unwrap(); + let mut drivers = globals().drivers.lock().unwrap(); drivers.remove(&self.id); } } @@ -156,13 +207,11 @@ impl Driver { } // Read all available data (until EAGAIN) let mut received = false; - let mut buffer = [0; 1024]; loop { - match recv(GLOBALS.receiver, &mut buffer, MSG_DONTWAIT) { + match (&globals().receiver).read(&mut [0; 128]) { Ok(0) => panic!("EOF on self-pipe"), Ok(_) => received = true, - Err(NixError::Sys(Errno::EAGAIN)) => break, - Err(NixError::Sys(Errno::EINTR)) => (), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, Err(e) => panic!("Bad read on self-pipe: {}", e), } } @@ -174,30 +223,26 @@ impl Driver { } // Go through all the signals and broadcast everything fn broadcast(&self) { - for (sig, value) in GLOBALS.pending.iter().enumerate() { + for (sig, slot) in globals().signals.iter().enumerate() { // Any signal of this kind arrived since we checked last? - if value.swap(false, Ordering::SeqCst) { - let signum = sig as c_int; - let mut siginfo = GLOBALS.signals[sig].lock().unwrap(); - // It doesn't seem to be possible to do this through the iterators for now. - // This trick is copied from https://github.com/rust-lang/rfcs/pull/1353. - for i in (0 .. siginfo.recipients.len()).rev() { - // TODO: This thing probably generates unnecessary wakups of this task. - // But let's optimise it later on, when we know this works. - match siginfo.recipients[i].start_send(signum) { - // We don't care if it was full or not ‒ we just want to wake up the other - // side. - Ok(AsyncSink::Ready) => { - // We are required to call this if we push something inside - let _ = siginfo.recipients[i].poll_complete(); - }, - // The channel is full -> it'll get woken up anyway - Ok(AsyncSink::NotReady(_)) => (), - // The other side disappeared, drop this end. - Err(_) => { - siginfo.recipients.swap_remove(i); - }, - } + if !slot.pending.swap(false, Ordering::SeqCst) { + continue + } + + let signum = sig as c_int; + let mut recipients = slot.recipients.lock().unwrap(); + + // Notify all waiters on this signal that the signal has been + // received. If we can't push a message into the queue then we don't + // worry about it as everything is coalesced anyway. + for i in (0..recipients.len()).rev() { + // TODO: This thing probably generates unnecessary wakups of + // this task. But let's optimise it later on, when we + // know this works. + match recipients[i].start_send(signum) { + Ok(AsyncSink::Ready) => {} + Ok(AsyncSink::NotReady(_)) => {} + Err(_) => { recipients.swap_remove(i); } } } } @@ -268,25 +313,24 @@ impl Signal { /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. pub fn new(signal: c_int, handle: &Handle) -> IoFuture { - let index = signal as usize; - // One wakeup in a queue is enough - let (sender, receiver) = channel(1); - { - let mut siginfo = GLOBALS.signals[index].lock().unwrap(); - siginfo.recipients.push(sender); - } // Turn the signal delivery on once we are ready for it - signal_enable(signal); + if let Err(e) = signal_enable(signal) { + return future::err(e).boxed() + } + + // One wakeup in a queue is enough + let (tx, rx) = channel(1); + globals().signals[signal as usize].recipients.lock().unwrap().push(tx); let id = handle.id(); { - let mut drivers = GLOBALS.drivers.lock().unwrap(); + let mut drivers = globals().drivers.lock().unwrap(); if !drivers.contains(&id) { handle.spawn(Driver::new(handle)); drivers.insert(id); } } // TODO: Init the driving task for this handle - Ok(Signal(receiver)).into_future().boxed() + Ok(Signal(rx)).into_future().boxed() } } From 699b9ab89e75bad8e7520b92c0b22d740ac757e9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 10:06:54 -0800 Subject: [PATCH 19/86] signal: Handle a few more errors --- src/unix.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 7aa1618eb..6f50b14c6 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -20,7 +20,7 @@ use std::sync::{Mutex, Once, ONCE_INIT}; use futures::future; use futures::sync::mpsc::{Receiver, Sender, channel}; -use futures::{Async, AsyncSink, Future, IntoFuture}; +use futures::{Async, AsyncSink, Future}; use futures::{Sink, Stream, Poll}; use self::libc::c_int; use self::mio::Poll as MioPoll; @@ -191,13 +191,13 @@ impl Drop for Driver { } impl Driver { - fn new(handle: &Handle) -> Self { - Driver { + fn new(handle: &Handle) -> io::Result { + Ok(Driver { id: handle.id(), - // TODO: Any chance of errors here? - wakeup: PollEvented::new(EventedReceiver, handle).unwrap(), - } + wakeup: try!(PollEvented::new(EventedReceiver, handle)), + }) } + // Drain all data in the pipe and maintain an interest in read-ready fn drain(&self) -> bool { // Inform tokio we're interested in reading. It also hints on @@ -313,24 +313,28 @@ impl Signal { /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. pub fn new(signal: c_int, handle: &Handle) -> IoFuture { - // Turn the signal delivery on once we are ready for it - if let Err(e) = signal_enable(signal) { - return future::err(e).boxed() - } + let result = (|| { + // Turn the signal delivery on once we are ready for it + try!(signal_enable(signal)); - // One wakeup in a queue is enough - let (tx, rx) = channel(1); - globals().signals[signal as usize].recipients.lock().unwrap().push(tx); - let id = handle.id(); - { + // Ensure there's a driver for our associated event loop processing + // signals. + let id = handle.id(); let mut drivers = globals().drivers.lock().unwrap(); if !drivers.contains(&id) { - handle.spawn(Driver::new(handle)); + handle.spawn(try!(Driver::new(handle))); drivers.insert(id); } - } - // TODO: Init the driving task for this handle - Ok(Signal(rx)).into_future().boxed() + drop(drivers); + + // One wakeup in a queue is enough, no need for us to buffer up any + // more. + let (tx, rx) = channel(1); + globals().signals[signal as usize].recipients.lock().unwrap().push(tx); + Ok(Signal(rx)) + })(); + + future::result(result).boxed() } } From 70e4ed67ade06ea7a6cbe15aac67e4be25be6b5e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 10:47:09 -0800 Subject: [PATCH 20/86] signal: Touch up more impls and comments --- src/unix.rs | 92 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 6f50b14c6..0e11b00df 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -85,7 +85,16 @@ fn globals() -> &'static Globals { } } -// Flag the relevant signal and wake up through a self-pipe +/// Our global signal handler for all signals registered by this module. +/// +/// The purpose of this signal handler is to primarily: +/// +/// 1. Flag that our specific signal was received (e.g. store an atomic flag) +/// 2. Wake up driver tasks by writing a byte to a pipe +/// +/// Those two operations shoudl both be async-signal safe. After that's done we +/// just try to call a previous signal handler, if any, to be "good denizens of +/// the internet" extern fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc::c_void) { @@ -116,10 +125,18 @@ extern fn handler(signum: c_int, } } -// Make sure we listen to the given signal and provide the recipient end of the -// self-pipe +/// Enable this module to receive signal notifications for the `signal` +/// provided. +/// +/// This will register the signal handler if it hasn't already been registered, +/// returning any error along the way if that fails. fn signal_enable(signal: c_int) -> io::Result<()> { - let siginfo = &globals().signals[signal as usize]; + let siginfo = match globals().signals.get(signal as usize) { + Some(slot) => slot, + None => { + return Err(io::Error::new(io::ErrorKind::Other, "signal too large")) + } + }; unsafe { let mut err = None; siginfo.init.call_once(|| { @@ -146,6 +163,12 @@ fn signal_enable(signal: c_int) -> io::Result<()> { } } +/// A helper struct to register our global receiving end of the signal pipe on +/// multiple event loops. +/// +/// This structure represents registering the receiving end on all event loops, +/// and uses `EventedFd` in mio to do so. It's stored in each driver task and is +/// used to read data and register interest in new signals coming in. struct EventedReceiver; impl Evented for EventedReceiver { @@ -163,6 +186,12 @@ impl Evented for EventedReceiver { } } +impl Read for EventedReceiver { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (&globals().receiver).read(buf) + } +} + struct Driver { id: CoreId, wakeup: PollEvented, @@ -198,30 +227,30 @@ impl Driver { }) } - // Drain all data in the pipe and maintain an interest in read-ready - fn drain(&self) -> bool { - // Inform tokio we're interested in reading. It also hints on - // if we may be readable. - if let Async::NotReady = self.wakeup.poll_read() { - return false; - } - // Read all available data (until EAGAIN) + /// Drain all data in the global receiver, returning whether data was to be + /// had. + /// + /// If this function returns `true` then some signal has been received since + /// we last checked, otherwise `false` indicates that no signal has been + /// received. + fn drain(&mut self) -> bool { let mut received = false; loop { - match (&globals().receiver).read(&mut [0; 128]) { + match self.wakeup.read(&mut [0; 128]) { Ok(0) => panic!("EOF on self-pipe"), Ok(_) => received = true, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, Err(e) => panic!("Bad read on self-pipe: {}", e), } } - - // If we got here, it's because we got EAGAIN above. Ask for more data. - self.wakeup.need_read(); - received } - // Go through all the signals and broadcast everything + + /// Go through all the signals and broadcast everything. + /// + /// Driver tasks wake up for *any* signal and simply process all globally + /// registered signal streams, so each task is sort of cooperatively working + /// for all the rest as well. fn broadcast(&self) { for (sig, slot) in globals().signals.iter().enumerate() { // Any signal of this kind arrived since we checked last? @@ -234,7 +263,8 @@ impl Driver { // Notify all waiters on this signal that the signal has been // received. If we can't push a message into the queue then we don't - // worry about it as everything is coalesced anyway. + // worry about it as everything is coalesced anyway. If the channel + // has gone away then we can remove that slot. for i in (0..recipients.len()).rev() { // TODO: This thing probably generates unnecessary wakups of // this task. But let's optimise it later on, when we @@ -249,7 +279,6 @@ impl Driver { } } -// TODO: Go through the docs, they are a copy-paste from the previous version /// An implementation of `Stream` for receiving a particular type of signal. /// /// This structure implements the `Stream` trait and represents notifications @@ -261,14 +290,6 @@ impl Driver { /// structure is no exception! There are some important limitations to keep in /// mind when using `Signal` streams: /// -/// * While multiple event loops are supported, the *first* event loop to -/// register a signal handler is required to be active to ensure that signals -/// for other event loops are delivered. In other words, once an event loop -/// registers a signal, it's best to keep it around and running. This is -/// normally just a problem for tests, and the "workaround" is to spawn a -/// thread in the background at the beginning of the test suite which is -/// running an event loop (and listening for a signal). -/// /// * Signals handling in Unix already necessitates coalescing signals /// together sometimes. This `Signal` stream is also no exception here in /// that it will also coalesce signals. That is, even if the signal handler @@ -278,11 +299,16 @@ impl Driver { /// Once `poll` has been called, however, a further signal is guaranteed to /// be yielded as an item. /// +/// Put another way, any element pulled off the returned stream corresponds to +/// *at least one* signal, but possibly more. +/// /// * Signal handling in general is relatively inefficient. Although some /// improvements are possible in this crate, it's recommended to not plan on /// having millions of signal channels open. /// -/// * Currently the "driver task" to process incoming signals never exits. +/// * Currently the "driver task" to process incoming signals never exits. This +/// driver task runs in the background of the event loop provided, and +/// in general you shouldn't need to worry about it. /// /// If you've got any questions about this feel free to open an issue on the /// repo, though, as I'd love to chat about this! In other words, I'd love to @@ -290,9 +316,8 @@ impl Driver { pub struct Signal(Receiver); impl Signal { - // TODO: Revisit the docs, they are from the previous version /// Creates a new stream which will receive notifications when the current - /// process receives the signal `signum`. + /// process receives the signal `signal`. /// /// This function will create a new stream which may be based on the /// event loop handle provided. This function returns a future which will @@ -303,10 +328,7 @@ impl Signal { /// found on `Signal` itself, but to reiterate: /// /// * Signals may be coalesced beyond what the kernel already does. - /// * While multiple event loops are supported, the first event loop to - /// register a signal handler must be active to deliver signal - /// notifications - /// * Once a signal handle is registered with the process the underlying + /// * Once a signal handler is registered with the process the underlying /// libc signal handler is never unregistered. /// /// A `Signal` stream can be created for a particular signal number From 955cd2836d6ceff359a5431481ffdf3f8dbb9dd1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 10:54:55 -0800 Subject: [PATCH 21/86] signal: Clear out old `Signal` on drop --- src/unix.rs | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 0e11b00df..039abedca 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -15,7 +15,7 @@ use std::io::prelude::*; use std::io; use std::mem; use std::os::unix::prelude::*; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use std::sync::{Mutex, Once, ONCE_INIT}; use futures::future; @@ -39,7 +39,7 @@ const SIGNUM: usize = 32; struct SignalInfo { pending: AtomicBool, // The ones interested in this signal - recipients: Mutex>>, + recipients: Mutex)>>, init: Once, initialized: UnsafeCell, @@ -269,7 +269,7 @@ impl Driver { // TODO: This thing probably generates unnecessary wakups of // this task. But let's optimise it later on, when we // know this works. - match recipients[i].start_send(signum) { + match recipients[i].1.start_send(signum) { Ok(AsyncSink::Ready) => {} Ok(AsyncSink::NotReady(_)) => {} Err(_) => { recipients.swap_remove(i); } @@ -313,7 +313,11 @@ impl Driver { /// If you've got any questions about this feel free to open an issue on the /// repo, though, as I'd love to chat about this! In other words, I'd love to /// alleviate some of these limitations if possible! -pub struct Signal(Receiver); +pub struct Signal { + signal: c_int, + token: usize, + rx: Receiver, +} impl Signal { /// Creates a new stream which will receive notifications when the current @@ -335,6 +339,8 @@ impl Signal { /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. pub fn new(signal: c_int, handle: &Handle) -> IoFuture { + static TOKENS: AtomicUsize = ATOMIC_USIZE_INIT; + let result = (|| { // Turn the signal delivery on once we are ready for it try!(signal_enable(signal)); @@ -352,8 +358,14 @@ impl Signal { // One wakeup in a queue is enough, no need for us to buffer up any // more. let (tx, rx) = channel(1); - globals().signals[signal as usize].recipients.lock().unwrap().push(tx); - Ok(Signal(rx)) + let token = TOKENS.fetch_add(1, Ordering::SeqCst); + let idx = signal as usize; + globals().signals[idx].recipients.lock().unwrap().push((token, tx)); + Ok(Signal { + rx: rx, + token: token, + signal: signal, + }) })(); future::result(result).boxed() @@ -365,9 +377,15 @@ impl Stream for Signal { type Error = io::Error; fn poll(&mut self) -> Poll, io::Error> { - // It seems the channel doesn't generate any errors anyway - self.0.poll().map_err(|_| io::Error::new(io::ErrorKind::Other, "Unknown futures::sync::mpsc error")) + // receivers don't generate errors + self.rx.poll().map_err(|_| panic!()) } } -// TODO: Drop for Signal and remove the other end proactively? +impl Drop for Signal { + fn drop(&mut self) { + let idx = self.signal as usize; + let mut list = globals().signals[idx].recipients.lock().unwrap(); + list.retain(|pair| pair.0 != self.token); + } +} From b6bacc1ca37ccf4fcabd43649f99e7c5c59f2510 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 10:59:31 -0800 Subject: [PATCH 22/86] signal: Clarify a comment --- src/unix.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 039abedca..b138314e9 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -267,8 +267,10 @@ impl Driver { // has gone away then we can remove that slot. for i in (0..recipients.len()).rev() { // TODO: This thing probably generates unnecessary wakups of - // this task. But let's optimise it later on, when we - // know this works. + // this task when `NotReady` is received because we don't + // actually want to get woken up to continue sending a + // message. Let's optimise it later on though, as we know + // this works. match recipients[i].1.start_send(signum) { Ok(AsyncSink::Ready) => {} Ok(AsyncSink::NotReady(_)) => {} From 4afef9391a43d5ae85af880a5c774df2acd5c3c1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 11:02:36 -0800 Subject: [PATCH 23/86] signal: Bump to 0.1.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3015dfdc5..ac3e9ae2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.1.1" +version = "0.1.2" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From 8e58a9d8d40ba68fc7f63ca2475707d9ba7f1dcc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 24 Jan 2017 11:04:45 -0800 Subject: [PATCH 24/86] signal: Add badges/categories --- Cargo.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ac3e9ae2e..6b40011fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,11 @@ documentation = "https://docs.rs/tokio-signal/0.1" description = """ An implementation of an asynchronous Unix signal handling backed futures. """ +categories = ["asynchronous"] + +[badges] +travis-ci = { repository = "alexcrichton/tokio-signal" } +appveyor = { repository = "alexcrichton/tokio-signal" } [dependencies] tokio-core = "0.1.4" From cf1afd2d90c12fd7c2378c41d7ba7e010e2d23c1 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Thu, 26 Jan 2017 21:26:58 +0100 Subject: [PATCH 25/86] signal: Style: Replace tabs with spaces Mixing tabs and spaces breaks indentation for people (and github) if they use different tab width. --- src/unix.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index b138314e9..b004c1b79 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -41,7 +41,7 @@ struct SignalInfo { // The ones interested in this signal recipients: Mutex)>>, - init: Once, + init: Once, initialized: UnsafeCell, prev: UnsafeCell, } @@ -98,8 +98,8 @@ fn globals() -> &'static Globals { extern fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc::c_void) { - type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); - type FnHandler = extern fn(c_int); + type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); + type FnHandler = extern fn(c_int); unsafe { let slot = match (*GLOBALS).signals.get(signum as usize) { Some(slot) => slot, @@ -137,7 +137,7 @@ fn signal_enable(signal: c_int) -> io::Result<()> { return Err(io::Error::new(io::ErrorKind::Other, "signal too large")) } }; - unsafe { + unsafe { let mut err = None; siginfo.init.call_once(|| { let mut new: libc::sigaction = mem::zeroed(); @@ -160,7 +160,7 @@ fn signal_enable(signal: c_int) -> io::Result<()> { Err(io::Error::new(io::ErrorKind::Other, "failed to register signal handler")) } - } + } } /// A helper struct to register our global receiving end of the signal pipe on From edba77e8df20503afe4bd2dd7dd370811aac4871 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Thu, 26 Jan 2017 20:41:06 +0100 Subject: [PATCH 26/86] signal: A test running multiple event loops Run multiple loops (both in parallel and sequentially) to make sure broadcasting to multiple of them works and we work even after the initial loop has gone away. --- tests/signal.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/signal.rs b/tests/signal.rs index 06bcefa6e..b7bb3fb73 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -6,6 +6,8 @@ extern crate tokio_core; extern crate tokio_signal; use std::time::Duration; +use std::thread; +use std::sync::mpsc::channel; use futures::Future; use futures::stream::Stream; @@ -63,3 +65,37 @@ fn twice() { } lp.run(signal.into_future()).ok().unwrap(); } + +#[test] +fn multi_loop() { + // An "ordinary" (non-future) channel + let (sender, receiver) = channel(); + // Run multiple times, to make sure there are no race conditions + for _ in 0..10 { + // Run multiple event loops, each one in its own thread + let threads: Vec<_> = (0..4) + .map(|_| { + let sender = sender.clone(); + thread::spawn(move || { + let mut lp = Core::new().unwrap(); + let handle = lp.handle(); + let signal = lp.run(Signal::new(libc::SIGHUP, &handle)).unwrap(); + sender.send(()).unwrap(); + lp.run(signal.into_future()).ok().unwrap(); + }) + }) + .collect(); + // Wait for them to declare they're ready + for &_ in threads.iter() { + receiver.recv().unwrap(); + } + // Send a signal + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGHUP), 0); + } + // Make sure the threads terminated correctly + for t in threads { + t.join().unwrap(); + } + } +} From 7da00f38323304a8ab5141268054bf5cfd53d02d Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Thu, 26 Jan 2017 21:22:19 +0100 Subject: [PATCH 27/86] signal: Use IDs that don't run out Replace the sequential counting (which might be exhausted) by an address of an object (in a box, so it doesn't change). This is also a unique, so it is acceptable ID. --- src/unix.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index b004c1b79..2021feee1 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -15,7 +15,7 @@ use std::io::prelude::*; use std::io; use std::mem; use std::os::unix::prelude::*; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, Once, ONCE_INIT}; use futures::future; @@ -39,7 +39,7 @@ const SIGNUM: usize = 32; struct SignalInfo { pending: AtomicBool, // The ones interested in this signal - recipients: Mutex)>>, + recipients: Mutex>>>, init: Once, initialized: UnsafeCell, @@ -271,7 +271,7 @@ impl Driver { // actually want to get woken up to continue sending a // message. Let's optimise it later on though, as we know // this works. - match recipients[i].1.start_send(signum) { + match recipients[i].start_send(signum) { Ok(AsyncSink::Ready) => {} Ok(AsyncSink::NotReady(_)) => {} Err(_) => { recipients.swap_remove(i); } @@ -317,10 +317,19 @@ impl Driver { /// alleviate some of these limitations if possible! pub struct Signal { signal: c_int, - token: usize, + // Used only as an identifier. We place the real sender into a Box, so it + // stays on the same address forever. That gives us a unique pointer, so we + // can use this to identify the sender in a Vec and delete it when we are + // dropped. + id: *const Sender, rx: Receiver, } +// The raw pointer prevents the compiler from determining it as Send +// automatically. But the only thing we use the raw pointer for is to identify +// the correct Box to delete, not manipulate any data through that. +unsafe impl Send for Signal {} + impl Signal { /// Creates a new stream which will receive notifications when the current /// process receives the signal `signal`. @@ -341,8 +350,6 @@ impl Signal { /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. pub fn new(signal: c_int, handle: &Handle) -> IoFuture { - static TOKENS: AtomicUsize = ATOMIC_USIZE_INIT; - let result = (|| { // Turn the signal delivery on once we are ready for it try!(signal_enable(signal)); @@ -360,12 +367,13 @@ impl Signal { // One wakeup in a queue is enough, no need for us to buffer up any // more. let (tx, rx) = channel(1); - let token = TOKENS.fetch_add(1, Ordering::SeqCst); + let tx = Box::new(tx); + let id: *const _ = &*tx; let idx = signal as usize; - globals().signals[idx].recipients.lock().unwrap().push((token, tx)); + globals().signals[idx].recipients.lock().unwrap().push(tx); Ok(Signal { rx: rx, - token: token, + id: id, signal: signal, }) })(); @@ -388,6 +396,6 @@ impl Drop for Signal { fn drop(&mut self) { let idx = self.signal as usize; let mut list = globals().signals[idx].recipients.lock().unwrap(); - list.retain(|pair| pair.0 != self.token); + list.retain(|sender| &**sender as *const _ != self.id); } } From 78ca103f3a0bd3a3594387273df916b848c90b1a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 9 Mar 2017 09:06:37 -0800 Subject: [PATCH 28/86] signal: Update to tokio-io --- Cargo.toml | 8 ++++---- src/lib.rs | 3 ++- src/unix.rs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b40011fc..a3ac2bccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,15 +16,15 @@ travis-ci = { repository = "alexcrichton/tokio-signal" } appveyor = { repository = "alexcrichton/tokio-signal" } [dependencies] -tokio-core = "0.1.4" -futures = "0.1.7" +futures = "0.1.11" +mio = "0.6.5" +tokio-core = "0.1.6" +tokio-io = "0.1" [target.'cfg(unix)'.dependencies] libc = "0.2" -mio = "0.6" mio-uds = "0.6" [target.'cfg(windows)'.dependencies] winapi = "0.2" kernel32-sys = "0.2" -mio = "0.6" diff --git a/src/lib.rs b/src/lib.rs index 780d7f0bf..1611f9f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,11 +25,12 @@ #[macro_use] extern crate futures; extern crate tokio_core; +extern crate tokio_io; use futures::Future; use futures::stream::Stream; use tokio_core::reactor::Handle; -use tokio_core::io::{IoStream, IoFuture}; +use tokio_io::{IoStream, IoFuture}; pub mod unix; pub mod windows; diff --git a/src/unix.rs b/src/unix.rs index 2021feee1..b8ae0d2a6 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -27,7 +27,7 @@ use self::mio::Poll as MioPoll; use self::mio::unix::EventedFd; use self::mio::{Evented, Token, Ready, PollOpt}; use self::mio_uds::UnixStream; -use tokio_core::io::IoFuture; +use tokio_io::IoFuture; use tokio_core::reactor::{Handle, CoreId, PollEvented}; pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; From c601f68c9f2686790531d8d125e5be1cf5e2b0ea Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Jun 2017 12:29:31 -0700 Subject: [PATCH 29/86] signal: Add some examples to crate docs Closes alexcrichton/tokio-signal#11 --- .travis.yml | 1 + README.md | 26 +++++++++++++++++++-- src/lib.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9bb8f8fab..6927df2ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ script: - cargo build - cargo test - cargo doc --no-deps + - rustdoc --test README.md -L target/debug/deps after_success: - travis-cargo --only nightly doc-upload env: diff --git a/README.md b/README.md index e6aeda77f..e35e72702 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,32 @@ First, add this to your `Cargo.toml`: tokio-signal = "0.1" ``` -Next, add this to your crate: +Next you an use this in conjunction with the `tokio-core` and `futures` crates: -```rust +```rust,no_run +extern crate futures; +extern crate tokio_core; extern crate tokio_signal; + +use tokio_core::reactor::Core; +use futures::{Future, Stream}; + +fn main() { + let mut core = Core::new().unwrap(); + let handle = core.handle(); + + // Create an infinite stream of "Ctrl+C" notifications. Each item received + // on this stream may represent multiple ctrl-c signals. + let ctrl_c = tokio_signal::ctrl_c(&handle).flatten_stream(); + + // Process each ctrl-c as it comes in + let prog = ctrl_c.for_each(|()| { + println!("ctrl-c received!"); + Ok(()) + }); + + core.run(prog).unwrap(); +} ``` # License diff --git a/src/lib.rs b/src/lib.rs index 1611f9f1f..7b09a40ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,11 +13,67 @@ //! The are some fundamental limitations of this crate documented on the //! `Signal` structure as well. //! -//! > **Note**: This crate compiles on Windows, but currently contains no -//! > bindings. Windows does not have signals like Unix does, but it -//! > does have a way to receive ctrl-c notifications at the console. -//! > It's planned that this will be bound and exported outside the -//! > `unix` module in the future! +//! # Examples +//! +//! Print out all ctrl-C notifications received +//! +//! ```rust,no_run +//! extern crate futures; +//! extern crate tokio_core; +//! extern crate tokio_signal; +//! +//! use tokio_core::reactor::Core; +//! use futures::{Future, Stream}; +//! +//! fn main() { +//! let mut core = Core::new().unwrap(); +//! let handle = core.handle(); +//! +//! // Create an infinite stream of "Ctrl+C" notifications. Each item received +//! // on this stream may represent multiple ctrl-c signals. +//! let ctrl_c = tokio_signal::ctrl_c(&handle).flatten_stream(); +//! +//! // Process each ctrl-c as it comes in +//! let prog = ctrl_c.for_each(|()| { +//! println!("ctrl-c received!"); +//! Ok(()) +//! }); +//! +//! core.run(prog).unwrap(); +//! } +//! ``` +//! +//! Wait for SIGHUP on Unix +//! +//! ```rust,no_run +//! # extern crate futures; +//! # extern crate tokio_core; +//! # extern crate tokio_signal; +//! # #[cfg(unix)] +//! # mod foo { +//! # +//! extern crate futures; +//! extern crate tokio_core; +//! extern crate tokio_signal; +//! +//! use tokio_core::reactor::Core; +//! use futures::{Future, Stream}; +//! use tokio_signal::unix::{Signal, SIGHUP}; +//! +//! fn main() { +//! let mut core = Core::new().unwrap(); +//! let handle = core.handle(); +//! +//! // Like the previous example, this is an infinite stream of signals +//! // being received, and signals may be coalesced while pending. +//! let stream = Signal::new(SIGHUP, &handle).flatten_stream(); +//! +//! // Convert out stream into a future and block the program +//! core.run(stream.into_future()).ok().unwrap(); +//! } +//! # } +//! # fn main() {} +//! ``` #![doc(html_root_url = "https://docs.rs/tokio-signal/0.1")] #![deny(missing_docs)] From 36b58d8fa84a1087bce6de60297dfca29c426b5e Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 22:38:42 +0200 Subject: [PATCH 30/86] signal: new example: SIGHUP, shows how to receive other signals than ctrl+C --- examples/sighup-example.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 examples/sighup-example.rs diff --git a/examples/sighup-example.rs b/examples/sighup-example.rs new file mode 100644 index 000000000..38ca57214 --- /dev/null +++ b/examples/sighup-example.rs @@ -0,0 +1,33 @@ +extern crate futures; +extern crate tokio_core; +extern crate tokio_signal; + +use futures::stream::Stream; +use tokio_core::reactor::Core; +use tokio_signal::unix::{Signal,SIGHUP}; + +fn main() { + // set up a Tokio event loop + let mut core = Core::new().unwrap(); + + // on Unix, we can listen to whatever signal we want, in this case: SIGHUP + let sighup = Signal::new(SIGHUP, &core.handle()); + let stream = core.run(sighup).unwrap(); + + println!("Waiting for SIGHUPS (Ctrl+C to quit)"); + println!(" TIP: use `pkill -sighup sighup-example` from a second terminal to send a SIGHUP \ + to all processes named 'sighup-example' (i.e. this binary)"); + + // for_each is a powerful primitive provided by the Futures crate + // it turns a Stream into a Future that completes after all stream-items + // have been completed. + let future = stream.for_each(|the_signal| { + println!("*Got a signal {}* I should probably reload my config or something", the_signal); + Ok(()) + }); + + // Up until now, we haven't really DONE anything, just prepared + // now it's time to actually schedule, and thus execute, the stream + // on our event loop, and loop forever + core.run(future).unwrap(); +} From 010c2223ca5c7697de471d3f098f888725f64ae8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 7 Jun 2017 14:53:04 -0700 Subject: [PATCH 31/86] signal: Touch up the sighup-example slightly --- examples/sighup-example.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/sighup-example.rs b/examples/sighup-example.rs index 38ca57214..f6e61d2bb 100644 --- a/examples/sighup-example.rs +++ b/examples/sighup-example.rs @@ -2,7 +2,7 @@ extern crate futures; extern crate tokio_core; extern crate tokio_signal; -use futures::stream::Stream; +use futures::{Stream, Future}; use tokio_core::reactor::Core; use tokio_signal::unix::{Signal,SIGHUP}; @@ -11,18 +11,19 @@ fn main() { let mut core = Core::new().unwrap(); // on Unix, we can listen to whatever signal we want, in this case: SIGHUP - let sighup = Signal::new(SIGHUP, &core.handle()); - let stream = core.run(sighup).unwrap(); + let stream = Signal::new(SIGHUP, &core.handle()).flatten_stream(); println!("Waiting for SIGHUPS (Ctrl+C to quit)"); - println!(" TIP: use `pkill -sighup sighup-example` from a second terminal to send a SIGHUP \ - to all processes named 'sighup-example' (i.e. this binary)"); + println!(" TIP: use `pkill -sighup sighup-example` from a second terminal \ + to send a SIGHUP to all processes named 'sighup-example' \ + (i.e. this binary)"); // for_each is a powerful primitive provided by the Futures crate // it turns a Stream into a Future that completes after all stream-items // have been completed. let future = stream.for_each(|the_signal| { - println!("*Got a signal {}* I should probably reload my config or something", the_signal); + println!("*Got signal {:#x}* I should probably reload my config \ + or something", the_signal); Ok(()) }); From 48eda3fe2fd1300f7060fec0fd706ba7b17e2f82 Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:15:40 +0200 Subject: [PATCH 32/86] signal: Upgrade ctrl+c example into `cargo run`-able version with proper Cargo.toml --- examples/ctrl-c/Cargo.toml | 9 +++++++++ examples/{log.rs => ctrl-c/src/main.rs} | 0 2 files changed, 9 insertions(+) create mode 100644 examples/ctrl-c/Cargo.toml rename examples/{log.rs => ctrl-c/src/main.rs} (100%) diff --git a/examples/ctrl-c/Cargo.toml b/examples/ctrl-c/Cargo.toml new file mode 100644 index 000000000..80716c381 --- /dev/null +++ b/examples/ctrl-c/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ctrl-c" +version = "0.1.0" +authors = ["Jules Kerssemakers "] + +[dependencies] +futures = "0.1.14" +tokio-core = "0.1.7" +tokio-signal = "0.1" diff --git a/examples/log.rs b/examples/ctrl-c/src/main.rs similarity index 100% rename from examples/log.rs rename to examples/ctrl-c/src/main.rs From 175f9afea9b7afb6695478fbd3af6a1bbb889ff5 Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:18:26 +0200 Subject: [PATCH 33/86] signal: ctrl+C example: Prompt user to do something. So we don't stay at a blank terminal without any feedback after `cargo run` --- examples/ctrl-c/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/ctrl-c/src/main.rs b/examples/ctrl-c/src/main.rs index 6d147362a..3dadad73e 100644 --- a/examples/ctrl-c/src/main.rs +++ b/examples/ctrl-c/src/main.rs @@ -10,6 +10,8 @@ fn main() { let ctrlc = tokio_signal::ctrl_c(&core.handle()); let stream = core.run(ctrlc).unwrap(); + println!("This program is now waiting for you to press Ctrl+C"); + core.run(stream.for_each(|()| { println!("Ctrl-C received!"); Ok(()) From f5eadc74f1094259874831aecb25af5d1a548af7 Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:20:25 +0200 Subject: [PATCH 34/86] signal: Ctrl+C example: add explanatory comments --- examples/ctrl-c/src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/ctrl-c/src/main.rs b/examples/ctrl-c/src/main.rs index 3dadad73e..cee695806 100644 --- a/examples/ctrl-c/src/main.rs +++ b/examples/ctrl-c/src/main.rs @@ -6,12 +6,19 @@ use futures::stream::Stream; use tokio_core::reactor::Core; fn main() { + // set up a Tokio event loop let mut core = Core::new().unwrap(); + + // tokio_signal provides a convenience builder for Ctrl+C + // this even works cross-platform, linux and windows! let ctrlc = tokio_signal::ctrl_c(&core.handle()); let stream = core.run(ctrlc).unwrap(); println!("This program is now waiting for you to press Ctrl+C"); + // Up until now, we haven't really DONE anything, just prepared + // now it's time to actually schedule, and thus execute, the stream + // on our event loop core.run(stream.for_each(|()| { println!("Ctrl-C received!"); Ok(()) From da47cfbd58e6902b8e35ddaf368d141a3ce9c692 Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:20:51 +0200 Subject: [PATCH 35/86] signal: Ctrl+C example: clarify control flow after receiving Ctrl+C: unreachable!() --- examples/ctrl-c/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/ctrl-c/src/main.rs b/examples/ctrl-c/src/main.rs index cee695806..be8962215 100644 --- a/examples/ctrl-c/src/main.rs +++ b/examples/ctrl-c/src/main.rs @@ -23,4 +23,7 @@ fn main() { println!("Ctrl-C received!"); Ok(()) })).unwrap(); + + println!("this won't be printed, because the received Ctrl+C will also kill the program"); + unreachable!(); } From 3db92496f6d192b44713281a43fe91c93cd4fba1 Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:23:10 +0200 Subject: [PATCH 36/86] signal: Ctrl+C example: highlight power of Stream::for_each() --- examples/ctrl-c/src/main.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/ctrl-c/src/main.rs b/examples/ctrl-c/src/main.rs index be8962215..16edd592a 100644 --- a/examples/ctrl-c/src/main.rs +++ b/examples/ctrl-c/src/main.rs @@ -16,13 +16,18 @@ fn main() { println!("This program is now waiting for you to press Ctrl+C"); + // for_each is a powerful primitive provided by the Futures crate + // it turns a Stream into a Future that completes after all stream-items + // have been completed. + let future = stream.for_each(|()| { + println!("Ctrl+C received!"); + Ok(()) + }); + // Up until now, we haven't really DONE anything, just prepared // now it's time to actually schedule, and thus execute, the stream // on our event loop - core.run(stream.for_each(|()| { - println!("Ctrl-C received!"); - Ok(()) - })).unwrap(); + core.run(future).unwrap(); println!("this won't be printed, because the received Ctrl+C will also kill the program"); unreachable!(); From 20e7598e8d179fc74e4689164fd1bb832bfb64dc Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:30:25 +0200 Subject: [PATCH 37/86] signal: Ctrl+C example: Don't forget proper attribution for original example --- examples/ctrl-c/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ctrl-c/Cargo.toml b/examples/ctrl-c/Cargo.toml index 80716c381..757504b5e 100644 --- a/examples/ctrl-c/Cargo.toml +++ b/examples/ctrl-c/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ctrl-c" version = "0.1.0" -authors = ["Jules Kerssemakers "] +authors = ["Jules Kerssemakers ", "Alex Crichton "] [dependencies] futures = "0.1.14" From 1c893ef6d3abef29dc9ec70f971d6ce45ce2af2f Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Wed, 7 Jun 2017 21:47:08 +0200 Subject: [PATCH 38/86] signal: undo nested example cargo project .. after learning about `cargo run --example` --- examples/{ctrl-c/src/main.rs => ctrl-c.rs} | 0 examples/ctrl-c/Cargo.toml | 9 --------- 2 files changed, 9 deletions(-) rename examples/{ctrl-c/src/main.rs => ctrl-c.rs} (100%) delete mode 100644 examples/ctrl-c/Cargo.toml diff --git a/examples/ctrl-c/src/main.rs b/examples/ctrl-c.rs similarity index 100% rename from examples/ctrl-c/src/main.rs rename to examples/ctrl-c.rs diff --git a/examples/ctrl-c/Cargo.toml b/examples/ctrl-c/Cargo.toml deleted file mode 100644 index 757504b5e..000000000 --- a/examples/ctrl-c/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "ctrl-c" -version = "0.1.0" -authors = ["Jules Kerssemakers ", "Alex Crichton "] - -[dependencies] -futures = "0.1.14" -tokio-core = "0.1.7" -tokio-signal = "0.1" From 6a7092b9f7528a56c000fd81747ff34f68f29a2d Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Thu, 8 Jun 2017 15:57:06 +0200 Subject: [PATCH 39/86] signal: Ctrl+C example: Defer stream initialisation (and explain how/why) --- examples/ctrl-c.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index 16edd592a..6e3a10a55 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -2,7 +2,7 @@ extern crate futures; extern crate tokio_core; extern crate tokio_signal; -use futures::stream::Stream; +use futures::{Stream, Future}; use tokio_core::reactor::Core; fn main() { @@ -10,9 +10,13 @@ fn main() { let mut core = Core::new().unwrap(); // tokio_signal provides a convenience builder for Ctrl+C - // this even works cross-platform, linux and windows! - let ctrlc = tokio_signal::ctrl_c(&core.handle()); - let stream = core.run(ctrlc).unwrap(); + // this even works cross-platform: linux and windows! + // + // `fn ctrl_c()` produces a `Future` of the actual stream-initialisation + // the `flatten_stream()` convenience method lazily defers that + // initialisation, allowing us to use it 'as if' it is already the + // stream we want, reducing boilerplate Future-handling. + let stream = tokio_signal::ctrl_c(&core.handle()).flatten_stream(); println!("This program is now waiting for you to press Ctrl+C"); From 72e2209bd85fffc37940cb27ac75e4dcadd256ce Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Thu, 8 Jun 2017 15:59:04 +0200 Subject: [PATCH 40/86] signal: Ctrl+C example: more explanations --- examples/ctrl-c.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index 6e3a10a55..fe9db7771 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -18,13 +18,19 @@ fn main() { // stream we want, reducing boilerplate Future-handling. let stream = tokio_signal::ctrl_c(&core.handle()).flatten_stream(); - println!("This program is now waiting for you to press Ctrl+C"); + println!("This program is now waiting for you to press Ctrl+C + * If running via `cargo run --example ctrl-c`, Ctrl+C also kills it, \ + due to https://github.com/rust-lang-nursery/rustup.rs/issues/806 + * If running the binary directly, the Ctrl+C is properly trapped. \ + Terminate by opening a second terminal and issue `pkill -sigkil ctrl-c`"); - // for_each is a powerful primitive provided by the Futures crate - // it turns a Stream into a Future that completes after all stream-items - // have been completed. + // Stream::for_each is a powerful primitive provided by the Futures crate. + // It turns a Stream into a Future that completes after all stream-items + // have been completed, or the first time the closure returns an error let future = stream.for_each(|()| { println!("Ctrl+C received!"); + + // return Ok-result to continue handling the stream Ok(()) }); From 934c59613338e5b775eeb3a087fcefb77f2b792d Mon Sep 17 00:00:00 2001 From: Jules Kerssemakers Date: Thu, 8 Jun 2017 21:32:50 +0200 Subject: [PATCH 41/86] signal: Ctrl+C example: quit after 10 signals. --- examples/ctrl-c.rs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index fe9db7771..ae67cac17 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -5,6 +5,9 @@ extern crate tokio_signal; use futures::{Stream, Future}; use tokio_core::reactor::Core; +/// how many signals to handle before exiting +const STOP_AFTER: u64 = 10; + fn main() { // set up a Tokio event loop let mut core = Core::new().unwrap(); @@ -16,19 +19,32 @@ fn main() { // the `flatten_stream()` convenience method lazily defers that // initialisation, allowing us to use it 'as if' it is already the // stream we want, reducing boilerplate Future-handling. - let stream = tokio_signal::ctrl_c(&core.handle()).flatten_stream(); + let endless_stream = tokio_signal::ctrl_c(&core.handle()).flatten_stream(); + // don't keep going forever: convert the endless stream to a bounded one. + let limited_stream = endless_stream.take(STOP_AFTER); - println!("This program is now waiting for you to press Ctrl+C + // how many Ctrl+C have we received so far? + let mut counter = 0; + + println!("This program is now waiting for you to press Ctrl+C {0} times. * If running via `cargo run --example ctrl-c`, Ctrl+C also kills it, \ due to https://github.com/rust-lang-nursery/rustup.rs/issues/806 - * If running the binary directly, the Ctrl+C is properly trapped. \ - Terminate by opening a second terminal and issue `pkill -sigkil ctrl-c`"); + * If running the binary directly, the Ctrl+C is properly trapped. + Terminate by repeating Ctrl+C {0} times, or ahead of time by \ + opening a second terminal and issuing `pkill -sigkil ctrl-c`", + STOP_AFTER); // Stream::for_each is a powerful primitive provided by the Futures crate. // It turns a Stream into a Future that completes after all stream-items // have been completed, or the first time the closure returns an error - let future = stream.for_each(|()| { - println!("Ctrl+C received!"); + let future = limited_stream.for_each(|()| { + + // Note how we manipulate the counter without any fancy synchronisation. + // The borrowchecker realises there can't be any conflicts, so the closure + // can just capture it. + counter += 1; + println!("Ctrl+C received {} times! {} more before exit", + counter, STOP_AFTER-counter); // return Ok-result to continue handling the stream Ok(()) @@ -39,6 +55,5 @@ fn main() { // on our event loop core.run(future).unwrap(); - println!("this won't be printed, because the received Ctrl+C will also kill the program"); - unreachable!(); + println!("Stream ended, quiting the program."); } From d41c60e21d04e58b09fc6f75cea7e096ac696da2 Mon Sep 17 00:00:00 2001 From: Raphael Nestler Date: Mon, 19 Jun 2017 14:38:02 +0200 Subject: [PATCH 42/86] signal: Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e35e72702..776a545b2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ First, add this to your `Cargo.toml`: tokio-signal = "0.1" ``` -Next you an use this in conjunction with the `tokio-core` and `futures` crates: +Next you can use this in conjunction with the `tokio-core` and `futures` crates: ```rust,no_run extern crate futures; From 9bf3228f730c2bc7355c21610e5ca3f62154f037 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 29 Jun 2017 22:56:15 -0700 Subject: [PATCH 43/86] signal: Add an example for waiting on two signals Relies on `Stream::select` to merge streams. Closes alexcrichton/tokio-signal#16 --- examples/multiple.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 examples/multiple.rs diff --git a/examples/multiple.rs b/examples/multiple.rs new file mode 100644 index 000000000..3a67db789 --- /dev/null +++ b/examples/multiple.rs @@ -0,0 +1,38 @@ +//! A small example of how to listen for two signals at the same time + +extern crate futures; +extern crate tokio_core; +extern crate tokio_signal; + +use futures::{Stream, Future}; +use tokio_core::reactor::Core; +use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; + +fn main() { + let mut core = Core::new().unwrap(); + let handle = core.handle(); + + // Create a stream for each of the signals we'd like to handle. + let sigint = Signal::new(SIGINT, &handle).flatten_stream(); + let sigterm = Signal::new(SIGTERM, &handle).flatten_stream(); + + // Use the `select` combinator to merge these two streams into one + let stream = sigint.select(sigterm); + + // Wait for a signal to arrive + println!("Waiting for SIGINT or SIGTERM"); + println!(" TIP: use `pkill -sigint multiple` from a second terminal \ + to send a SIGINT to all processes named 'multiple' \ + (i.e. this binary)"); + let (item, _rest) = core.run(stream.into_future()).ok().unwrap(); + + // Figure out which signal we received + let item = item.unwrap(); + if item == SIGINT { + println!("received SIGINT"); + } else { + assert_eq!(item, SIGTERM); + println!("received SIGTERM"); + } +} + From 7ab97f99c807fe56109365fc9c3de66078f8b698 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 30 Oct 2017 14:16:11 -0700 Subject: [PATCH 44/86] signal: Clarify wording of license information in README. This text historically was copied verbatim from rust-lang/rust's own README [1] with the intention of licensing projects the same as rustc's own license, namely a dual MIT/Apache-2.0 license. The clause about "various BSD-like licenses" isn't actually correct for almost all projects other than rust-lang/rust and the wording around "both" was slightly ambiguous. This commit updates the wording to match more precisely what's in the standard library [2], namely clarifying that there aren't any BSD-like licenses in this repository and that the source is licensable under either license, at your own discretion. [1]: https://github.com/rust-lang/rust/tree/f0fe716dbcbf2363ab8f929325d32a17e51039d0#license [2]: https://github.com/rust-lang/rust/blob/f0fe716dbcbf2363ab8f929325d32a17e51039d0/src/libstd/lib.rs#L5-L9 --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 776a545b2..491ff56f9 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,17 @@ fn main() { # License -`tokio-signal` is primarily distributed under the terms of both the MIT -license and the Apache License (Version 2.0), with portions covered by various -BSD-like licenses. +This project is licensed under either of -See LICENSE-APACHE, and LICENSE-MIT for details. + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) +at your option. +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. From a4895fe364d7120a3027b16a48f12f14f8e84cfc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 Nov 2017 05:36:54 -0800 Subject: [PATCH 45/86] signal: Tweak travis config --- .travis.yml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6927df2ed..0b08695a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,25 @@ language: rust - -rust: - - stable - - beta - - nightly sudo: false -before_script: - - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + +matrix: + include: + - rust: stable + - os: osx + - rust: beta + - rust: nightly + + - rust: nightly + before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH + script: + - cargo doc --no-deps --all-features + after_success: + - travis-cargo --only nightly doc-upload + script: - - cargo build - cargo test - - cargo doc --no-deps - rustdoc --test README.md -L target/debug/deps -after_success: - - travis-cargo --only nightly doc-upload + env: global: - secure: "SXXK7Znvm1s5WWQ94l9IP25mXA0uGIQ7ghBuumZz3nSAfxhhJQnYi5hCAbl2/cOfSbpgEtE137dgk6Nd9UDx7rIkLCSe8TWyYjzraX/vvX3xNtLh/fjsayYYRK9a6qU2HIJegZdxPgyF5h2DeBgeLks0Ue8drrFQ1s9bYZVUO0yeuZ3aLkL1FkIG6RXGItUFpb6srEYL1NLizYLxXFEG3cL+kKoFIWc2qPx3EwOqv/eii134nQsuObhWZvPqfTo7zfNP8W/6TnoiggpRH1nrZc3DI3CynTICIOJ2Ogn9gFX9LftYKuJysSwUNVN3WF5aOuLP/XjRSBLYc+PW3v0iqiGzMX3n1VpcyhcbsSNA7ZckGn1HZsWYwspAxkN3idSuVie9Mezm7IV4005juiYKEWEr6hlkv1lzd49QZkWOvLCFCMRiwOOGp4NyzilG1Q1Zs3G1wrcvstmasNpK+QUFNdOFvT2sm34rI4x2rQUvjC/OyqbAK+PjYmTHL47YKON5ymfUL3mAcwgUfBUSd4Wpx8G3VKg3gMcmQm27ah1knOGJWH6XulYTnfGfx6bLo5t2NGx+vZk0naqajD3auWnseobMDsFjhUIRrt6GlnfPqeFoJSm0unu3riAX+RDF/iqZdDfjhX4evETIw3SaTl8EQtVLwz7kJTnxSbTU4XTi+0M=" @@ -21,6 +27,3 @@ env: notifications: email: on_success: never -os: - - linux - - osx From 4fa1b2b58c140f4752711bf4a72405a703c8e735 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 3 Jan 2018 08:41:41 -0800 Subject: [PATCH 46/86] signal: Update to winapi 0.3 --- Cargo.toml | 6 +++--- src/lib.rs | 19 +++++++++++++------ src/unix.rs | 2 +- src/windows.rs | 50 ++++++++++++++++++++++++++++---------------------- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3ac2bccc..d2718c22f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,6 @@ tokio-io = "0.1" libc = "0.2" mio-uds = "0.6" -[target.'cfg(windows)'.dependencies] -winapi = "0.2" -kernel32-sys = "0.2" +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3" +features = ["minwindef", "wincon"] diff --git a/src/lib.rs b/src/lib.rs index 7b09a40ad..3927b25f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,19 +78,24 @@ #![doc(html_root_url = "https://docs.rs/tokio-signal/0.1")] #![deny(missing_docs)] -#[macro_use] extern crate futures; extern crate tokio_core; extern crate tokio_io; +use std::io; + use futures::Future; use futures::stream::Stream; use tokio_core::reactor::Handle; -use tokio_io::{IoStream, IoFuture}; pub mod unix; pub mod windows; +/// A future whose error is `io::Error` +pub type IoFuture = Box + Send>; +/// A stream whose error is `io::Error` +pub type IoStream = Box + Send>; + /// Creates a stream which receives "ctrl-c" notifications sent to a process. /// /// In general signals are handled very differently across Unix and Windows, but @@ -108,13 +113,15 @@ pub fn ctrl_c(handle: &Handle) -> IoFuture> { #[cfg(unix)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { - unix::Signal::new(unix::libc::SIGINT, handle).map(|x| { - x.map(|_| ()).boxed() - }).boxed() + Box::new(unix::Signal::new(unix::libc::SIGINT, handle).map(|x| { + Box::new(x.map(|_| ())) as Box + Send> + })) } #[cfg(windows)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { - windows::Event::ctrl_c(handle).map(|x| x.boxed()).boxed() + Box::new(windows::Event::ctrl_c(handle).map(|x| { + Box::new(x) as Box + Send> + })) } } diff --git a/src/unix.rs b/src/unix.rs index b8ae0d2a6..8ad08c4e7 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -378,7 +378,7 @@ impl Signal { }) })(); - future::result(result).boxed() + Box::new(future::result(result)) } } diff --git a/src/windows.rs b/src/windows.rs index ddb44efa3..d686c27e5 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -7,7 +7,6 @@ #![cfg(windows)] -extern crate kernel32; extern crate mio; extern crate winapi; @@ -21,8 +20,15 @@ use futures::stream::Fuse; use futures::sync::mpsc; use futures::sync::oneshot; use futures::{Future, IntoFuture, Poll, Async, Stream}; -use tokio_core::io::IoFuture; use tokio_core::reactor::{PollEvented, Handle}; +use self::winapi::shared::minwindef::*; +use self::winapi::um::wincon::*; + +use IoFuture; + +extern "system" { + fn SetConsoleCtrlHandler(HandlerRoutine: usize, Add: BOOL) -> BOOL; +} static INIT: Once = ONCE_INIT; static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; @@ -57,7 +63,7 @@ struct GlobalEventState { } enum Message { - NewEvent(winapi::DWORD, oneshot::Sender>), + NewEvent(DWORD, oneshot::Sender>), } struct DriverTask { @@ -78,7 +84,7 @@ impl Event { /// This function will register a handler via `SetConsoleCtrlHandler` and /// deliver notifications to the returned stream. pub fn ctrl_c(handle: &Handle) -> IoFuture { - Event::new(winapi::CTRL_C_EVENT, handle) + Event::new(CTRL_C_EVENT, handle) } /// Creates a new stream listening for the `CTRL_BREAK_EVENT` events. @@ -86,10 +92,10 @@ impl Event { /// This function will register a handler via `SetConsoleCtrlHandler` and /// deliver notifications to the returned stream. pub fn ctrl_break(handle: &Handle) -> IoFuture { - Event::new(winapi::CTRL_BREAK_EVENT, handle) + Event::new(CTRL_BREAK_EVENT, handle) } - fn new(signum: winapi::DWORD, handle: &Handle) -> IoFuture { + fn new(signum: DWORD, handle: &Handle) -> IoFuture { let mut init = None; INIT.call_once(|| { init = Some(global_init(handle)); @@ -98,15 +104,15 @@ impl Event { let (tx, rx) = oneshot::channel(); let msg = Message::NewEvent(signum, tx); let res = unsafe { - (*GLOBAL_STATE).tx.clone().send(msg) + (*GLOBAL_STATE).tx.clone().unbounded_send(msg) }; res.expect("failed to request a new signal stream, did the \ first event loop go away?"); rx.then(|r| r.unwrap()) }); match init { - Some(init) => init.into_future().and_then(|()| new_signal).boxed(), - None => new_signal.boxed(), + Some(init) => Box::new(init.into_future().and_then(|()| new_signal)), + None => Box::new(new_signal), } } } @@ -123,7 +129,7 @@ impl Stream for Event { self.reg.get_ref() .inner.borrow() .as_ref().unwrap().1 - .set_readiness(mio::Ready::none()) + .set_readiness(mio::Ready::empty()) .expect("failed to set readiness"); Ok(Async::Ready(Some(()))) } @@ -143,7 +149,7 @@ fn global_init(handle: &Handle) -> io::Result<()> { }); GLOBAL_STATE = Box::into_raw(state); - let rc = kernel32::SetConsoleCtrlHandler(Some(handler), winapi::TRUE); + let rc = SetConsoleCtrlHandler(handler as usize, TRUE); if rc == 0 { Box::from_raw(GLOBAL_STATE); GLOBAL_STATE = 0 as *mut _; @@ -198,7 +204,7 @@ impl DriverTask { Message::NewEvent(sig, complete) => (sig, complete), }; - let event = if sig == winapi::CTRL_C_EVENT { + let event = if sig == CTRL_C_EVENT { &mut self.ctrl_c } else { &mut self.ctrl_break @@ -210,7 +216,7 @@ impl DriverTask { let reg = match PollEvented::new(reg, &self.handle) { Ok(reg) => reg, Err(e) => { - complete.complete(Err(e)); + drop(complete.send(Err(e))); continue } }; @@ -219,10 +225,10 @@ impl DriverTask { // the `SetReadiness` for ourselves internally. let (tx, rx) = oneshot::channel(); let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); - complete.complete(Ok(Event { + drop(complete.send(Ok(Event { reg: reg, _finished: tx, - })); + }))); event.tasks.push((RefCell::new(rx), ready)); } } @@ -233,7 +239,7 @@ impl DriverTask { } self.reg.need_read(); self.reg.get_ref().inner.borrow().as_ref().unwrap() - .1.set_readiness(mio::Ready::none()).unwrap(); + .1.set_readiness(mio::Ready::empty()).unwrap(); if unsafe { (*GLOBAL_STATE).ctrl_c.ready.swap(false, Ordering::SeqCst) } { for task in self.ctrl_c.tasks.iter() { @@ -248,20 +254,20 @@ impl DriverTask { } } -unsafe extern "system" fn handler(ty: winapi::DWORD) -> winapi::BOOL { +unsafe extern "system" fn handler(ty: DWORD) -> BOOL { let event = match ty { - winapi::CTRL_C_EVENT => &(*GLOBAL_STATE).ctrl_c, - winapi::CTRL_BREAK_EVENT => &(*GLOBAL_STATE).ctrl_break, - _ => return winapi::FALSE + CTRL_C_EVENT => &(*GLOBAL_STATE).ctrl_c, + CTRL_BREAK_EVENT => &(*GLOBAL_STATE).ctrl_break, + _ => return FALSE }; if event.ready.swap(true, Ordering::SeqCst) { - winapi::FALSE + FALSE } else { drop((*GLOBAL_STATE).ready.set_readiness(mio::Ready::readable())); // TODO: this will report that we handled a CTRL_BREAK_EVENT when in // fact we may not have any streams actually created for that // event. - winapi::TRUE + TRUE } } From 0df1882f219ea90357830a4041126b3e29325718 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 3 Jan 2018 08:43:48 -0800 Subject: [PATCH 47/86] signal: Bump to 0.1.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d2718c22f..38ad575ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.1.2" +version = "0.1.3" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From 8ddebf430999a07286a4d0c7e7da66e43ff09609 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 31 Jan 2018 07:05:26 -0800 Subject: [PATCH 48/86] signal: Fix compile on Android Closes alexcrichton/tokio-signal#19 --- src/unix.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 8ad08c4e7..34d59dc22 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -138,13 +138,23 @@ fn signal_enable(signal: c_int) -> io::Result<()> { } }; unsafe { + #[cfg(target_os = "android")] + fn flags() -> libc::c_ulong { + (libc::SA_RESTART as libc::c_ulong) | + libc::SA_SIGINFO | + (libc::SA_NOCLDSTOP as libc::c_ulong) + } + #[cfg(not(target_os = "android"))] + fn flags() -> c_int { + libc::SA_RESTART | + libc::SA_SIGINFO | + libc::SA_NOCLDSTOP + } let mut err = None; siginfo.init.call_once(|| { let mut new: libc::sigaction = mem::zeroed(); new.sa_sigaction = handler as usize; - new.sa_flags = libc::SA_RESTART | - libc::SA_SIGINFO | - libc::SA_NOCLDSTOP; + new.sa_flags = flags(); if libc::sigaction(signal, &new, &mut *siginfo.prev.get()) != 0 { err = Some(io::Error::last_os_error()); } else { From 5ecd929b1a5e4992da83c67944099fb6e91ba3d6 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 2 Feb 2018 15:02:18 -0800 Subject: [PATCH 49/86] signal: Bump to 0.1.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 38ad575ea..05bb1c2e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.1.3" +version = "0.1.4" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From 9c9760cfbb4a0fde0dd2581be6f44598369c6ae4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 9 Mar 2018 11:27:50 -0800 Subject: [PATCH 50/86] signal: Fix a bug with most recent tokio-core release --- src/unix.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/unix.rs b/src/unix.rs index 34d59dc22..6bc5d49f2 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -184,7 +184,12 @@ struct EventedReceiver; impl Evented for EventedReceiver { fn register(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { let fd = globals().receiver.as_raw_fd(); - EventedFd(&fd).register(poll, token, events, opts) + match EventedFd(&fd).register(poll, token, events, opts) { + Ok(()) => Ok(()), + // Due to tokio-rs/tokio-core#307 + Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()), + Err(e) => Err(e), + } } fn reregister(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { let fd = globals().receiver.as_raw_fd(); From 7a24ed75097934e394405710c7dc4dd06e77a794 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 9 Mar 2018 11:28:11 -0800 Subject: [PATCH 51/86] signal: Bump to 0.1.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 05bb1c2e8..da8c81272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.1.4" +version = "0.1.5" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From 2848df9b6cfd9b136f9ea0d81ef64b453c4029dd Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Thu, 3 May 2018 19:12:23 +0200 Subject: [PATCH 52/86] signal: Run rustfmt 0.4.2 --- examples/ctrl-c.rs | 16 ++++-- examples/multiple.rs | 11 ++-- examples/sighup-example.rs | 19 +++--- src/lib.rs | 16 +++--- src/unix.rs | 73 ++++++++++++----------- src/windows.rs | 115 +++++++++++++++++++++++-------------- tests/signal.rs | 10 ++-- 7 files changed, 155 insertions(+), 105 deletions(-) diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index ae67cac17..ff2786f0e 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -2,7 +2,7 @@ extern crate futures; extern crate tokio_core; extern crate tokio_signal; -use futures::{Stream, Future}; +use futures::{Future, Stream}; use tokio_core::reactor::Core; /// how many signals to handle before exiting @@ -26,25 +26,29 @@ fn main() { // how many Ctrl+C have we received so far? let mut counter = 0; - println!("This program is now waiting for you to press Ctrl+C {0} times. + println!( + "This program is now waiting for you to press Ctrl+C {0} times. * If running via `cargo run --example ctrl-c`, Ctrl+C also kills it, \ due to https://github.com/rust-lang-nursery/rustup.rs/issues/806 * If running the binary directly, the Ctrl+C is properly trapped. Terminate by repeating Ctrl+C {0} times, or ahead of time by \ opening a second terminal and issuing `pkill -sigkil ctrl-c`", - STOP_AFTER); + STOP_AFTER + ); // Stream::for_each is a powerful primitive provided by the Futures crate. // It turns a Stream into a Future that completes after all stream-items // have been completed, or the first time the closure returns an error let future = limited_stream.for_each(|()| { - // Note how we manipulate the counter without any fancy synchronisation. // The borrowchecker realises there can't be any conflicts, so the closure // can just capture it. counter += 1; - println!("Ctrl+C received {} times! {} more before exit", - counter, STOP_AFTER-counter); + println!( + "Ctrl+C received {} times! {} more before exit", + counter, + STOP_AFTER - counter + ); // return Ok-result to continue handling the stream Ok(()) diff --git a/examples/multiple.rs b/examples/multiple.rs index 3a67db789..093b83c6a 100644 --- a/examples/multiple.rs +++ b/examples/multiple.rs @@ -4,7 +4,7 @@ extern crate futures; extern crate tokio_core; extern crate tokio_signal; -use futures::{Stream, Future}; +use futures::{Future, Stream}; use tokio_core::reactor::Core; use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; @@ -21,9 +21,11 @@ fn main() { // Wait for a signal to arrive println!("Waiting for SIGINT or SIGTERM"); - println!(" TIP: use `pkill -sigint multiple` from a second terminal \ - to send a SIGINT to all processes named 'multiple' \ - (i.e. this binary)"); + println!( + " TIP: use `pkill -sigint multiple` from a second terminal \ + to send a SIGINT to all processes named 'multiple' \ + (i.e. this binary)" + ); let (item, _rest) = core.run(stream.into_future()).ok().unwrap(); // Figure out which signal we received @@ -35,4 +37,3 @@ fn main() { println!("received SIGTERM"); } } - diff --git a/examples/sighup-example.rs b/examples/sighup-example.rs index f6e61d2bb..88da9b8c3 100644 --- a/examples/sighup-example.rs +++ b/examples/sighup-example.rs @@ -2,9 +2,9 @@ extern crate futures; extern crate tokio_core; extern crate tokio_signal; -use futures::{Stream, Future}; +use futures::{Future, Stream}; use tokio_core::reactor::Core; -use tokio_signal::unix::{Signal,SIGHUP}; +use tokio_signal::unix::{Signal, SIGHUP}; fn main() { // set up a Tokio event loop @@ -14,16 +14,21 @@ fn main() { let stream = Signal::new(SIGHUP, &core.handle()).flatten_stream(); println!("Waiting for SIGHUPS (Ctrl+C to quit)"); - println!(" TIP: use `pkill -sighup sighup-example` from a second terminal \ - to send a SIGHUP to all processes named 'sighup-example' \ - (i.e. this binary)"); + println!( + " TIP: use `pkill -sighup sighup-example` from a second terminal \ + to send a SIGHUP to all processes named 'sighup-example' \ + (i.e. this binary)" + ); // for_each is a powerful primitive provided by the Futures crate // it turns a Stream into a Future that completes after all stream-items // have been completed. let future = stream.for_each(|the_signal| { - println!("*Got signal {:#x}* I should probably reload my config \ - or something", the_signal); + println!( + "*Got signal {:#x}* I should probably reload my config \ + or something", + the_signal + ); Ok(()) }); diff --git a/src/lib.rs b/src/lib.rs index 3927b25f9..93a284f95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,8 +84,8 @@ extern crate tokio_io; use std::io; -use futures::Future; use futures::stream::Stream; +use futures::Future; use tokio_core::reactor::Handle; pub mod unix; @@ -113,15 +113,17 @@ pub fn ctrl_c(handle: &Handle) -> IoFuture> { #[cfg(unix)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { - Box::new(unix::Signal::new(unix::libc::SIGINT, handle).map(|x| { - Box::new(x.map(|_| ())) as Box + Send> - })) + Box::new( + unix::Signal::new(unix::libc::SIGINT, handle) + .map(|x| Box::new(x.map(|_| ())) as Box + Send>), + ) } #[cfg(windows)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { - Box::new(windows::Event::ctrl_c(handle).map(|x| { - Box::new(x) as Box + Send> - })) + Box::new( + windows::Event::ctrl_c(handle) + .map(|x| Box::new(x) as Box + Send>), + ) } } diff --git a/src/unix.rs b/src/unix.rs index 6bc5d49f2..9971804f2 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -11,27 +11,27 @@ extern crate mio_uds; use std::cell::UnsafeCell; use std::collections::HashSet; -use std::io::prelude::*; use std::io; +use std::io::prelude::*; use std::mem; use std::os::unix::prelude::*; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, Once, ONCE_INIT}; -use futures::future; -use futures::sync::mpsc::{Receiver, Sender, channel}; -use futures::{Async, AsyncSink, Future}; -use futures::{Sink, Stream, Poll}; use self::libc::c_int; -use self::mio::Poll as MioPoll; use self::mio::unix::EventedFd; -use self::mio::{Evented, Token, Ready, PollOpt}; +use self::mio::Poll as MioPoll; +use self::mio::{Evented, PollOpt, Ready, Token}; use self::mio_uds::UnixStream; +use futures::future; +use futures::sync::mpsc::{channel, Receiver, Sender}; +use futures::{Async, AsyncSink, Future}; +use futures::{Poll, Sink, Stream}; +use tokio_core::reactor::{CoreId, Handle, PollEvented}; use tokio_io::IoFuture; -use tokio_core::reactor::{Handle, CoreId, PollEvented}; -pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2}; -pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP}; +pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; +pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; // Number of different unix signals const SIGNUM: usize = 32; @@ -95,11 +95,9 @@ fn globals() -> &'static Globals { /// Those two operations shoudl both be async-signal safe. After that's done we /// just try to call a previous signal handler, if any, to be "good denizens of /// the internet" -extern fn handler(signum: c_int, - info: *mut libc::siginfo_t, - ptr: *mut libc::c_void) { - type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); - type FnHandler = extern fn(c_int); +extern "C" fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc::c_void) { + type FnSigaction = extern "C" fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); + type FnHandler = extern "C" fn(c_int); unsafe { let slot = match (*GLOBALS).signals.get(signum as usize) { Some(slot) => slot, @@ -113,7 +111,7 @@ extern fn handler(signum: c_int, let fnptr = (*slot.prev.get()).sa_sigaction; if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { - return + return; } if (*slot.prev.get()).sa_flags & libc::SA_SIGINFO == 0 { let action = mem::transmute::(fnptr); @@ -133,22 +131,17 @@ extern fn handler(signum: c_int, fn signal_enable(signal: c_int) -> io::Result<()> { let siginfo = match globals().signals.get(signal as usize) { Some(slot) => slot, - None => { - return Err(io::Error::new(io::ErrorKind::Other, "signal too large")) - } + None => return Err(io::Error::new(io::ErrorKind::Other, "signal too large")), }; unsafe { #[cfg(target_os = "android")] fn flags() -> libc::c_ulong { - (libc::SA_RESTART as libc::c_ulong) | - libc::SA_SIGINFO | - (libc::SA_NOCLDSTOP as libc::c_ulong) + (libc::SA_RESTART as libc::c_ulong) | libc::SA_SIGINFO + | (libc::SA_NOCLDSTOP as libc::c_ulong) } #[cfg(not(target_os = "android"))] fn flags() -> c_int { - libc::SA_RESTART | - libc::SA_SIGINFO | - libc::SA_NOCLDSTOP + libc::SA_RESTART | libc::SA_SIGINFO | libc::SA_NOCLDSTOP } let mut err = None; siginfo.init.call_once(|| { @@ -162,13 +155,15 @@ fn signal_enable(signal: c_int) -> io::Result<()> { } }); if let Some(err) = err { - return Err(err) + return Err(err); } if *siginfo.initialized.get() { Ok(()) } else { - Err(io::Error::new(io::ErrorKind::Other, - "failed to register signal handler")) + Err(io::Error::new( + io::ErrorKind::Other, + "failed to register signal handler", + )) } } } @@ -182,7 +177,13 @@ fn signal_enable(signal: c_int) -> io::Result<()> { struct EventedReceiver; impl Evented for EventedReceiver { - fn register(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { + fn register( + &self, + poll: &MioPoll, + token: Token, + events: Ready, + opts: PollOpt, + ) -> io::Result<()> { let fd = globals().receiver.as_raw_fd(); match EventedFd(&fd).register(poll, token, events, opts) { Ok(()) => Ok(()), @@ -191,7 +192,13 @@ impl Evented for EventedReceiver { Err(e) => Err(e), } } - fn reregister(&self, poll: &MioPoll, token: Token, events: Ready, opts: PollOpt) -> io::Result<()> { + fn reregister( + &self, + poll: &MioPoll, + token: Token, + events: Ready, + opts: PollOpt, + ) -> io::Result<()> { let fd = globals().receiver.as_raw_fd(); EventedFd(&fd).reregister(poll, token, events, opts) } @@ -270,7 +277,7 @@ impl Driver { for (sig, slot) in globals().signals.iter().enumerate() { // Any signal of this kind arrived since we checked last? if !slot.pending.swap(false, Ordering::SeqCst) { - continue + continue; } let signum = sig as c_int; @@ -289,7 +296,9 @@ impl Driver { match recipients[i].start_send(signum) { Ok(AsyncSink::Ready) => {} Ok(AsyncSink::NotReady(_)) => {} - Err(_) => { recipients.swap_remove(i); } + Err(_) => { + recipients.swap_remove(i); + } } } } diff --git a/src/windows.rs b/src/windows.rs index d686c27e5..436dda519 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -15,14 +15,14 @@ use std::io; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Once, ONCE_INIT}; +use self::winapi::shared::minwindef::*; +use self::winapi::um::wincon::*; use futures::future; use futures::stream::Fuse; use futures::sync::mpsc; use futures::sync::oneshot; -use futures::{Future, IntoFuture, Poll, Async, Stream}; -use tokio_core::reactor::{PollEvented, Handle}; -use self::winapi::shared::minwindef::*; -use self::winapi::um::wincon::*; +use futures::{Async, Future, IntoFuture, Poll, Stream}; +use tokio_core::reactor::{Handle, PollEvented}; use IoFuture; @@ -103,11 +103,11 @@ impl Event { let new_signal = future::lazy(move || { let (tx, rx) = oneshot::channel(); let msg = Message::NewEvent(signum, tx); - let res = unsafe { - (*GLOBAL_STATE).tx.clone().unbounded_send(msg) - }; - res.expect("failed to request a new signal stream, did the \ - first event loop go away?"); + let res = unsafe { (*GLOBAL_STATE).tx.clone().unbounded_send(msg) }; + res.expect( + "failed to request a new signal stream, did the \ + first event loop go away?", + ); rx.then(|r| r.unwrap()) }); match init { @@ -123,28 +123,38 @@ impl Stream for Event { fn poll(&mut self) -> Poll, io::Error> { if !self.reg.poll_read().is_ready() { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } self.reg.need_read(); - self.reg.get_ref() - .inner.borrow() - .as_ref().unwrap().1 - .set_readiness(mio::Ready::empty()) - .expect("failed to set readiness"); + self.reg + .get_ref() + .inner + .borrow() + .as_ref() + .unwrap() + .1 + .set_readiness(mio::Ready::empty()) + .expect("failed to set readiness"); Ok(Async::Ready(Some(()))) } } fn global_init(handle: &Handle) -> io::Result<()> { let (tx, rx) = mpsc::unbounded(); - let reg = MyRegistration { inner: RefCell::new(None) }; + let reg = MyRegistration { + inner: RefCell::new(None), + }; let reg = try!(PollEvented::new(reg, handle)); let ready = reg.get_ref().inner.borrow().as_ref().unwrap().1.clone(); unsafe { let state = Box::new(GlobalState { ready: ready, - ctrl_c: GlobalEventState { ready: AtomicBool::new(false) }, - ctrl_break: GlobalEventState { ready: AtomicBool::new(false) }, + ctrl_c: GlobalEventState { + ready: AtomicBool::new(false), + }, + ctrl_break: GlobalEventState { + ready: AtomicBool::new(false), + }, tx: tx, }); GLOBAL_STATE = Box::into_raw(state); @@ -153,7 +163,7 @@ fn global_init(handle: &Handle) -> io::Result<()> { if rc == 0 { Box::from_raw(GLOBAL_STATE); GLOBAL_STATE = 0 as *mut _; - return Err(io::Error::last_os_error()) + return Err(io::Error::last_os_error()); } handle.spawn(DriverTask { @@ -184,12 +194,12 @@ impl Future for DriverTask { impl DriverTask { fn check_event_drops(&mut self) { - self.ctrl_c.tasks.retain(|task| { - !task.0.borrow_mut().poll().is_err() - }); - self.ctrl_break.tasks.retain(|task| { - !task.0.borrow_mut().poll().is_err() - }); + self.ctrl_c + .tasks + .retain(|task| !task.0.borrow_mut().poll().is_err()); + self.ctrl_break + .tasks + .retain(|task| !task.0.borrow_mut().poll().is_err()); } fn check_messages(&mut self) { @@ -197,8 +207,7 @@ impl DriverTask { // Acquire the next message let message = match self.rx.poll().unwrap() { Async::Ready(Some(e)) => e, - Async::Ready(None) | - Async::NotReady => break, + Async::Ready(None) | Async::NotReady => break, }; let (sig, complete) = match message { Message::NewEvent(sig, complete) => (sig, complete), @@ -212,12 +221,14 @@ impl DriverTask { // Acquire the (registration, set_readiness) pair by... assuming // we're on the event loop (true because of the spawn above). - let reg = MyRegistration { inner: RefCell::new(None) }; + let reg = MyRegistration { + inner: RefCell::new(None), + }; let reg = match PollEvented::new(reg, &self.handle) { Ok(reg) => reg, Err(e) => { drop(complete.send(Err(e))); - continue + continue; } }; @@ -235,18 +246,30 @@ impl DriverTask { fn check_events(&mut self) { if self.reg.poll_read().is_not_ready() { - return + return; } self.reg.need_read(); - self.reg.get_ref().inner.borrow().as_ref().unwrap() - .1.set_readiness(mio::Ready::empty()).unwrap(); + self.reg + .get_ref() + .inner + .borrow() + .as_ref() + .unwrap() + .1 + .set_readiness(mio::Ready::empty()) + .unwrap(); if unsafe { (*GLOBAL_STATE).ctrl_c.ready.swap(false, Ordering::SeqCst) } { for task in self.ctrl_c.tasks.iter() { task.1.set_readiness(mio::Ready::readable()).unwrap(); } } - if unsafe { (*GLOBAL_STATE).ctrl_break.ready.swap(false, Ordering::SeqCst) } { + if unsafe { + (*GLOBAL_STATE) + .ctrl_break + .ready + .swap(false, Ordering::SeqCst) + } { for task in self.ctrl_break.tasks.iter() { task.1.set_readiness(mio::Ready::readable()).unwrap(); } @@ -258,7 +281,7 @@ unsafe extern "system" fn handler(ty: DWORD) -> BOOL { let event = match ty { CTRL_C_EVENT => &(*GLOBAL_STATE).ctrl_c, CTRL_BREAK_EVENT => &(*GLOBAL_STATE).ctrl_break, - _ => return FALSE + _ => return FALSE, }; if event.ready.swap(true, Ordering::SeqCst) { FALSE @@ -276,21 +299,25 @@ struct MyRegistration { } impl mio::Evented for MyRegistration { - fn register(&self, - poll: &mio::Poll, - token: mio::Token, - events: mio::Ready, - opts: mio::PollOpt) -> io::Result<()> { + fn register( + &self, + poll: &mio::Poll, + token: mio::Token, + events: mio::Ready, + opts: mio::PollOpt, + ) -> io::Result<()> { let reg = mio::Registration::new(poll, token, events, opts); *self.inner.borrow_mut() = Some(reg); Ok(()) } - fn reregister(&self, - _poll: &mio::Poll, - _token: mio::Token, - _events: mio::Ready, - _opts: mio::PollOpt) -> io::Result<()> { + fn reregister( + &self, + _poll: &mio::Poll, + _token: mio::Token, + _events: mio::Ready, + _opts: mio::PollOpt, + ) -> io::Result<()> { Ok(()) } diff --git a/tests/signal.rs b/tests/signal.rs index b7bb3fb73..c16c5bc19 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -5,12 +5,12 @@ extern crate libc; extern crate tokio_core; extern crate tokio_signal; -use std::time::Duration; -use std::thread; use std::sync::mpsc::channel; +use std::thread; +use std::time::Duration; -use futures::Future; use futures::stream::Stream; +use futures::Future; use tokio_core::reactor::{Core, Timeout}; use tokio_signal::unix::Signal; @@ -34,7 +34,9 @@ fn notify_both() { unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); } - lp.run(signal1.into_future().join(signal2.into_future())).ok().unwrap(); + lp.run(signal1.into_future().join(signal2.into_future())) + .ok() + .unwrap(); } #[test] From e97e8cb7fe42e7b07f3b220e082a7fdbad38bc1c Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Thu, 3 May 2018 21:19:58 +0200 Subject: [PATCH 53/86] signal: Update the windows implementation to work with tokio BREAKING CHANGE `ctrl_c` now takes a `tokio_reactor::Handle` --- Cargo.toml | 6 +++++- examples/ctrl-c.rs | 2 +- src/lib.rs | 18 +++++++++++------- src/windows.rs | 30 ++++++++++++++++-------------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da8c81272..b7829fe4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,13 +18,17 @@ appveyor = { repository = "alexcrichton/tokio-signal" } [dependencies] futures = "0.1.11" mio = "0.6.5" -tokio-core = "0.1.6" +tokio-reactor = "0.1.0" +tokio-executor = "0.1.0" tokio-io = "0.1" [target.'cfg(unix)'.dependencies] libc = "0.2" mio-uds = "0.6" +[dev-dependencies] +tokio-core = "0.1.17" + [target.'cfg(windows)'.dependencies.winapi] version = "0.3" features = ["minwindef", "wincon"] diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index ff2786f0e..e650506dd 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -19,7 +19,7 @@ fn main() { // the `flatten_stream()` convenience method lazily defers that // initialisation, allowing us to use it 'as if' it is already the // stream we want, reducing boilerplate Future-handling. - let endless_stream = tokio_signal::ctrl_c(&core.handle()).flatten_stream(); + let endless_stream = tokio_signal::ctrl_c(&core.handle().new_tokio_handle()).flatten_stream(); // don't keep going forever: convert the endless stream to a bounded one. let limited_stream = endless_stream.take(STOP_AFTER); diff --git a/src/lib.rs b/src/lib.rs index 93a284f95..a3423b0d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,14 +79,16 @@ #![deny(missing_docs)] extern crate futures; -extern crate tokio_core; +extern crate mio; +extern crate tokio_executor; extern crate tokio_io; +extern crate tokio_reactor; use std::io; use futures::stream::Stream; -use futures::Future; -use tokio_core::reactor::Handle; +use futures::{future, Future}; +use tokio_reactor::Handle; pub mod unix; pub mod windows; @@ -121,9 +123,11 @@ pub fn ctrl_c(handle: &Handle) -> IoFuture> { #[cfg(windows)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { - Box::new( - windows::Event::ctrl_c(handle) - .map(|x| Box::new(x) as Box + Send>), - ) + let handle = handle.clone(); + // Use lazy to ensure that `ctrl_c` gets called while on an event loop + Box::new(future::lazy(move || { + windows::Event::ctrl_c(&handle) + .map(|x| Box::new(x) as Box + Send>) + })) } } diff --git a/src/windows.rs b/src/windows.rs index 436dda519..c3cf505ab 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -15,14 +15,15 @@ use std::io; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Once, ONCE_INIT}; -use self::winapi::shared::minwindef::*; -use self::winapi::um::wincon::*; use futures::future; use futures::stream::Fuse; use futures::sync::mpsc; use futures::sync::oneshot; use futures::{Async, Future, IntoFuture, Poll, Stream}; -use tokio_core::reactor::{Handle, PollEvented}; +use tokio_reactor::{Handle, PollEvented}; +use mio::Ready; +use self::winapi::shared::minwindef::*; +use self::winapi::um::wincon::*; use IoFuture; @@ -122,10 +123,10 @@ impl Stream for Event { type Error = io::Error; fn poll(&mut self) -> Poll, io::Error> { - if !self.reg.poll_read().is_ready() { + if !self.reg.poll_read_ready(Ready::readable())?.is_ready() { return Ok(Async::NotReady); } - self.reg.need_read(); + self.reg.clear_read_ready(Ready::readable())?; self.reg .get_ref() .inner @@ -144,7 +145,7 @@ fn global_init(handle: &Handle) -> io::Result<()> { let reg = MyRegistration { inner: RefCell::new(None), }; - let reg = try!(PollEvented::new(reg, handle)); + let reg = try!(PollEvented::new_with_handle(reg, handle)); let ready = reg.get_ref().inner.borrow().as_ref().unwrap().1.clone(); unsafe { let state = Box::new(GlobalState { @@ -166,13 +167,13 @@ fn global_init(handle: &Handle) -> io::Result<()> { return Err(io::Error::last_os_error()); } - handle.spawn(DriverTask { + ::tokio_executor::spawn(Box::new(DriverTask { handle: handle.clone(), rx: rx.fuse(), reg: reg, ctrl_c: EventState { tasks: Vec::new() }, ctrl_break: EventState { tasks: Vec::new() }, - }); + })); Ok(()) } @@ -185,7 +186,7 @@ impl Future for DriverTask { fn poll(&mut self) -> Poll<(), ()> { self.check_event_drops(); self.check_messages(); - self.check_events(); + self.check_events().unwrap(); // TODO: when to finish this task? Ok(Async::NotReady) @@ -224,7 +225,7 @@ impl DriverTask { let reg = MyRegistration { inner: RefCell::new(None), }; - let reg = match PollEvented::new(reg, &self.handle) { + let reg = match PollEvented::new_with_handle(reg, &self.handle) { Ok(reg) => reg, Err(e) => { drop(complete.send(Err(e))); @@ -244,11 +245,11 @@ impl DriverTask { } } - fn check_events(&mut self) { - if self.reg.poll_read().is_not_ready() { - return; + fn check_events(&mut self) -> io::Result<()> { + if self.reg.poll_read_ready(Ready::readable())?.is_not_ready() { + return Ok(()); } - self.reg.need_read(); + self.reg.clear_read_ready(Ready::readable())?; self.reg .get_ref() .inner @@ -274,6 +275,7 @@ impl DriverTask { task.1.set_readiness(mio::Ready::readable()).unwrap(); } } + Ok(()) } } From 1fdff707b85d538bb471ace16af850d5ecad5e9a Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Thu, 3 May 2018 21:58:32 +0200 Subject: [PATCH 54/86] signal: test: Add a test for ctrl_c on unix --- examples/ctrl-c.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index e650506dd..cbb253a2b 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -61,3 +61,38 @@ fn main() { println!("Stream ended, quiting the program."); } + +#[cfg(test)] +// `Child::kill` terminates the application instead of sending the equivalent to SIGKILL on windows +#[cfg(unix)] +mod tests { + use super::*; + + use std::env; + use std::path::Path; + use std::process::Command; + + #[test] + fn ctrl_c() { + let args = env::args().collect::>(); + let ctrl_c_child = "ctrl_c_child"; + if args.len() >= 3 && args.last().map(|s| &s[..]) == Some(ctrl_c_child) { + super::main(); + } else { + let ctrl_c_path = Path::new("target") + .join("debug") + .join("examples") + .join("ctrl-c"); + let mut child = Command::new(ctrl_c_path) + .args(&["ctrl_c", "--nocapture", ctrl_c_child]) + .spawn() + .unwrap(); + + for i in 0..STOP_AFTER { + println!("Kill {}", i); + child.kill().unwrap(); + } + child.wait().unwrap(); + } + } +} From f759e4d70f12b6074912311e87058433b9bbf6b7 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 5 May 2018 00:33:02 +0200 Subject: [PATCH 55/86] signal: panic --- Cargo.toml | 1 + examples/multiple.rs | 1 + examples/sighup-example.rs | 2 +- src/lib.rs | 11 ++++--- src/unix.rs | 66 +++++++++++++++----------------------- tests/signal.rs | 37 +++++++++++++++++---- 6 files changed, 65 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7829fe4b..92ad04524 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ mio-uds = "0.6" [dev-dependencies] tokio-core = "0.1.17" +tokio = "0.1.0" [target.'cfg(windows)'.dependencies.winapi] version = "0.3" diff --git a/examples/multiple.rs b/examples/multiple.rs index 093b83c6a..39c0e5031 100644 --- a/examples/multiple.rs +++ b/examples/multiple.rs @@ -11,6 +11,7 @@ use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; fn main() { let mut core = Core::new().unwrap(); let handle = core.handle(); + let handle = handle.new_tokio_handle(); // Create a stream for each of the signals we'd like to handle. let sigint = Signal::new(SIGINT, &handle).flatten_stream(); diff --git a/examples/sighup-example.rs b/examples/sighup-example.rs index 88da9b8c3..a9a76db7a 100644 --- a/examples/sighup-example.rs +++ b/examples/sighup-example.rs @@ -11,7 +11,7 @@ fn main() { let mut core = Core::new().unwrap(); // on Unix, we can listen to whatever signal we want, in this case: SIGHUP - let stream = Signal::new(SIGHUP, &core.handle()).flatten_stream(); + let stream = Signal::new(SIGHUP, &core.handle().new_tokio_handle()).flatten_stream(); println!("Waiting for SIGHUPS (Ctrl+C to quit)"); println!( diff --git a/src/lib.rs b/src/lib.rs index a3423b0d1..1fa8cff15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ //! fn main() { //! let mut core = Core::new().unwrap(); //! let handle = core.handle(); +//! let handle = handle.new_tokio_handle(); //! //! // Create an infinite stream of "Ctrl+C" notifications. Each item received //! // on this stream may represent multiple ctrl-c signals. @@ -63,6 +64,7 @@ //! fn main() { //! let mut core = Core::new().unwrap(); //! let handle = core.handle(); +//! let handle = handle.new_tokio_handle(); //! //! // Like the previous example, this is an infinite stream of signals //! // being received, and signals may be coalesced while pending. @@ -115,10 +117,11 @@ pub fn ctrl_c(handle: &Handle) -> IoFuture> { #[cfg(unix)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { - Box::new( - unix::Signal::new(unix::libc::SIGINT, handle) - .map(|x| Box::new(x.map(|_| ())) as Box + Send>), - ) + let handle = handle.clone(); + Box::new(future::lazy(move || { + unix::Signal::new(unix::libc::SIGINT, &handle) + .map(|x| Box::new(x.map(|_| ())) as Box + Send>) + })) } #[cfg(windows)] diff --git a/src/unix.rs b/src/unix.rs index 9971804f2..df5c17e37 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -10,7 +10,6 @@ extern crate mio; extern crate mio_uds; use std::cell::UnsafeCell; -use std::collections::HashSet; use std::io; use std::io::prelude::*; use std::mem; @@ -27,7 +26,7 @@ use futures::future; use futures::sync::mpsc::{channel, Receiver, Sender}; use futures::{Async, AsyncSink, Future}; use futures::{Poll, Sink, Stream}; -use tokio_core::reactor::{CoreId, Handle, PollEvented}; +use tokio_reactor::{Handle, PollEvented}; use tokio_io::IoFuture; pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; @@ -50,7 +49,6 @@ struct Globals { sender: UnixStream, receiver: UnixStream, signals: [SignalInfo; SIGNUM], - drivers: Mutex>, } impl Default for SignalInfo { @@ -77,7 +75,6 @@ fn globals() -> &'static Globals { sender: sender, receiver: receiver, signals: Default::default(), - drivers: Mutex::new(HashSet::new()), }; GLOBALS = Box::into_raw(Box::new(globals)); }); @@ -215,7 +212,6 @@ impl Read for EventedReceiver { } struct Driver { - id: CoreId, wakeup: PollEvented, } @@ -234,18 +230,10 @@ impl Future for Driver { } } -impl Drop for Driver { - fn drop(&mut self) { - let mut drivers = globals().drivers.lock().unwrap(); - drivers.remove(&self.id); - } -} - impl Driver { fn new(handle: &Handle) -> io::Result { Ok(Driver { - id: handle.id(), - wakeup: try!(PollEvented::new(EventedReceiver, handle)), + wakeup: try!(PollEvented::new_with_handle(EventedReceiver, handle)), }) } @@ -374,35 +362,31 @@ impl Signal { /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. pub fn new(signal: c_int, handle: &Handle) -> IoFuture { - let result = (|| { - // Turn the signal delivery on once we are ready for it - try!(signal_enable(signal)); + let handle = handle.clone(); + Box::new(future::lazy(move || { + let result = (|| { + // Turn the signal delivery on once we are ready for it + try!(signal_enable(signal)); - // Ensure there's a driver for our associated event loop processing - // signals. - let id = handle.id(); - let mut drivers = globals().drivers.lock().unwrap(); - if !drivers.contains(&id) { - handle.spawn(try!(Driver::new(handle))); - drivers.insert(id); - } - drop(drivers); + // Ensure there's a driver for our associated event loop processing + // signals. + ::tokio_executor::spawn(try!(Driver::new(&handle))); - // One wakeup in a queue is enough, no need for us to buffer up any - // more. - let (tx, rx) = channel(1); - let tx = Box::new(tx); - let id: *const _ = &*tx; - let idx = signal as usize; - globals().signals[idx].recipients.lock().unwrap().push(tx); - Ok(Signal { - rx: rx, - id: id, - signal: signal, - }) - })(); - - Box::new(future::result(result)) + // One wakeup in a queue is enough, no need for us to buffer up any + // more. + let (tx, rx) = channel(1); + let tx = Box::new(tx); + let id: *const _ = &*tx; + let idx = signal as usize; + globals().signals[idx].recipients.lock().unwrap().push(tx); + Ok(Signal { + rx: rx, + id: id, + signal: signal, + }) + })(); + future::result(result) + })) } } diff --git a/tests/signal.rs b/tests/signal.rs index c16c5bc19..a53a49b27 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -2,6 +2,7 @@ extern crate futures; extern crate libc; +extern crate tokio; extern crate tokio_core; extern crate tokio_signal; @@ -10,7 +11,7 @@ use std::thread; use std::time::Duration; use futures::stream::Stream; -use futures::Future; +use futures::{future, Future, IntoFuture}; use tokio_core::reactor::{Core, Timeout}; use tokio_signal::unix::Signal; @@ -18,19 +19,38 @@ use tokio_signal::unix::Signal; fn simple() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1, &handle.new_tokio_handle())) + .unwrap(); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } lp.run(signal.into_future()).ok().unwrap(); } +#[test] +fn tokio_simple() { + tokio::run( + future::lazy(|| { + Signal::new(libc::SIGUSR1, &tokio::reactor::Handle::default()) + .into_future() + .and_then(|signal| { + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + signal.into_future().map(|_| ()).map_err(|(err, _)| err) + }) + }).map_err(|err| panic!("{}", err)), + ) +} + #[test] fn notify_both() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); - let signal2 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap(); + let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle.new_tokio_handle())) + .unwrap(); + let signal2 = lp.run(Signal::new(libc::SIGUSR2, &handle.new_tokio_handle())) + .unwrap(); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); } @@ -43,7 +63,8 @@ fn notify_both() { fn drop_then_get_a_signal() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1, &handle.new_tokio_handle())) + .unwrap(); drop(signal); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); @@ -56,7 +77,8 @@ fn drop_then_get_a_signal() { fn twice() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1, &handle.new_tokio_handle())) + .unwrap(); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } @@ -81,7 +103,8 @@ fn multi_loop() { thread::spawn(move || { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGHUP, &handle)).unwrap(); + let signal = lp.run(Signal::new(libc::SIGHUP, &handle.new_tokio_handle())) + .unwrap(); sender.send(()).unwrap(); lp.run(signal.into_future()).ok().unwrap(); }) From 209232befd970cad384543b540f0624b2f3e9c2f Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 5 May 2018 18:55:30 +0200 Subject: [PATCH 56/86] signal: Ensure that the driver dies once the signal does `tokio::run` expects that all futures finish processing so we can't leave `Driver` around forever or `tokio::run` would never return. --- src/unix.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/unix.rs b/src/unix.rs index df5c17e37..2cb6d0d8e 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -328,6 +328,7 @@ impl Driver { /// repo, though, as I'd love to chat about this! In other words, I'd love to /// alleviate some of these limitations if possible! pub struct Signal { + driver: Driver, signal: c_int, // Used only as an identifier. We place the real sender into a Box, so it // stays on the same address forever. That gives us a unique pointer, so we @@ -370,7 +371,7 @@ impl Signal { // Ensure there's a driver for our associated event loop processing // signals. - ::tokio_executor::spawn(try!(Driver::new(&handle))); + let driver = try!(Driver::new(&handle)); // One wakeup in a queue is enough, no need for us to buffer up any // more. @@ -380,6 +381,7 @@ impl Signal { let idx = signal as usize; globals().signals[idx].recipients.lock().unwrap().push(tx); Ok(Signal { + driver: driver, rx: rx, id: id, signal: signal, @@ -395,6 +397,7 @@ impl Stream for Signal { type Error = io::Error; fn poll(&mut self) -> Poll, io::Error> { + self.driver.poll().unwrap(); // receivers don't generate errors self.rx.poll().map_err(|_| panic!()) } From e73b8a0cc934cd6daaa7d7af38410b561ea24a2f Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 5 May 2018 20:09:22 +0200 Subject: [PATCH 57/86] signal: refactor: Prefer the implicit handle passing used by tokio --- README.md | 9 +++---- examples/ctrl-c.rs | 2 +- examples/multiple.rs | 6 ++--- examples/sighup-example.rs | 2 +- src/lib.rs | 29 +++++++++++++++------- src/unix.rs | 24 +++++++++++++++++- src/windows.rs | 20 +++++++++++++-- tests/signal.rs | 51 +++++++++++++++++++------------------- 8 files changed, 93 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 491ff56f9..a97654a91 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,16 @@ Next you can use this in conjunction with the `tokio-core` and `futures` crates: ```rust,no_run extern crate futures; -extern crate tokio_core; +extern crate tokio; extern crate tokio_signal; -use tokio_core::reactor::Core; use futures::{Future, Stream}; fn main() { - let mut core = Core::new().unwrap(); - let handle = core.handle(); // Create an infinite stream of "Ctrl+C" notifications. Each item received // on this stream may represent multiple ctrl-c signals. - let ctrl_c = tokio_signal::ctrl_c(&handle).flatten_stream(); + let ctrl_c = tokio_signal::ctrl_c().flatten_stream(); // Process each ctrl-c as it comes in let prog = ctrl_c.for_each(|()| { @@ -39,7 +36,7 @@ fn main() { Ok(()) }); - core.run(prog).unwrap(); + tokio::run(prog.map_err(|err| panic!("{}", err))); } ``` diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index cbb253a2b..1d3ce602d 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -19,7 +19,7 @@ fn main() { // the `flatten_stream()` convenience method lazily defers that // initialisation, allowing us to use it 'as if' it is already the // stream we want, reducing boilerplate Future-handling. - let endless_stream = tokio_signal::ctrl_c(&core.handle().new_tokio_handle()).flatten_stream(); + let endless_stream = tokio_signal::ctrl_c().flatten_stream(); // don't keep going forever: convert the endless stream to a bounded one. let limited_stream = endless_stream.take(STOP_AFTER); diff --git a/examples/multiple.rs b/examples/multiple.rs index 39c0e5031..380523188 100644 --- a/examples/multiple.rs +++ b/examples/multiple.rs @@ -10,12 +10,10 @@ use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; fn main() { let mut core = Core::new().unwrap(); - let handle = core.handle(); - let handle = handle.new_tokio_handle(); // Create a stream for each of the signals we'd like to handle. - let sigint = Signal::new(SIGINT, &handle).flatten_stream(); - let sigterm = Signal::new(SIGTERM, &handle).flatten_stream(); + let sigint = Signal::new(SIGINT).flatten_stream(); + let sigterm = Signal::new(SIGTERM).flatten_stream(); // Use the `select` combinator to merge these two streams into one let stream = sigint.select(sigterm); diff --git a/examples/sighup-example.rs b/examples/sighup-example.rs index a9a76db7a..e48e23ea3 100644 --- a/examples/sighup-example.rs +++ b/examples/sighup-example.rs @@ -11,7 +11,7 @@ fn main() { let mut core = Core::new().unwrap(); // on Unix, we can listen to whatever signal we want, in this case: SIGHUP - let stream = Signal::new(SIGHUP, &core.handle().new_tokio_handle()).flatten_stream(); + let stream = Signal::new(SIGHUP).flatten_stream(); println!("Waiting for SIGHUPS (Ctrl+C to quit)"); println!( diff --git a/src/lib.rs b/src/lib.rs index 1fa8cff15..df8ad783a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,12 +27,10 @@ //! //! fn main() { //! let mut core = Core::new().unwrap(); -//! let handle = core.handle(); -//! let handle = handle.new_tokio_handle(); //! //! // Create an infinite stream of "Ctrl+C" notifications. Each item received //! // on this stream may represent multiple ctrl-c signals. -//! let ctrl_c = tokio_signal::ctrl_c(&handle).flatten_stream(); +//! let ctrl_c = tokio_signal::ctrl_c().flatten_stream(); //! //! // Process each ctrl-c as it comes in //! let prog = ctrl_c.for_each(|()| { @@ -63,12 +61,10 @@ //! //! fn main() { //! let mut core = Core::new().unwrap(); -//! let handle = core.handle(); -//! let handle = handle.new_tokio_handle(); //! //! // Like the previous example, this is an infinite stream of signals //! // being received, and signals may be coalesced while pending. -//! let stream = Signal::new(SIGHUP, &handle).flatten_stream(); +//! let stream = Signal::new(SIGHUP).flatten_stream(); //! //! // Convert out stream into a future and block the program //! core.run(stream.into_future()).ok().unwrap(); @@ -100,6 +96,21 @@ pub type IoFuture = Box + Send>; /// A stream whose error is `io::Error` pub type IoStream = Box + Send>; +/// Creates a stream which receives "ctrl-c" notifications sent to a process. +/// +/// In general signals are handled very differently across Unix and Windows, but +/// this is somewhat cross platform in terms of how it can be handled. A ctrl-c +/// event to a console process can be represented as a stream for both Windows +/// and Unix. +/// +/// This function binds to the default event loop. Note that +/// there are a number of caveats listening for signals, and you may wish to +/// read up on the documentation in the `unix` or `windows` module to take a +/// peek. +pub fn ctrl_c() -> IoFuture> { + ctrl_c_handle(&Handle::current()) +} + /// Creates a stream which receives "ctrl-c" notifications sent to a process. /// /// In general signals are handled very differently across Unix and Windows, but @@ -112,14 +123,14 @@ pub type IoStream = Box + Send>; /// there are a number of caveats listening for signals, and you may wish to /// read up on the documentation in the `unix` or `windows` module to take a /// peek. -pub fn ctrl_c(handle: &Handle) -> IoFuture> { +pub fn ctrl_c_handle(handle: &Handle) -> IoFuture> { return ctrl_c_imp(handle); #[cfg(unix)] fn ctrl_c_imp(handle: &Handle) -> IoFuture> { let handle = handle.clone(); Box::new(future::lazy(move || { - unix::Signal::new(unix::libc::SIGINT, &handle) + unix::Signal::with_handle(unix::libc::SIGINT, &handle) .map(|x| Box::new(x.map(|_| ())) as Box + Send>) })) } @@ -129,7 +140,7 @@ pub fn ctrl_c(handle: &Handle) -> IoFuture> { let handle = handle.clone(); // Use lazy to ensure that `ctrl_c` gets called while on an event loop Box::new(future::lazy(move || { - windows::Event::ctrl_c(&handle) + windows::Event::ctrl_c_handle(&handle) .map(|x| Box::new(x) as Box + Send>) })) } diff --git a/src/unix.rs b/src/unix.rs index 2cb6d0d8e..234fcc3fe 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -344,6 +344,28 @@ pub struct Signal { unsafe impl Send for Signal {} impl Signal { + /// Creates a new stream which will receive notifications when the current + /// process receives the signal `signal`. + /// + /// This function will create a new stream which binds to the default event + /// loop. This function returns a future which will + /// then resolve to the signal stream, if successful. + /// + /// The `Signal` stream is an infinite stream which will receive + /// notifications whenever a signal is received. More documentation can be + /// found on `Signal` itself, but to reiterate: + /// + /// * Signals may be coalesced beyond what the kernel already does. + /// * Once a signal handler is registered with the process the underlying + /// libc signal handler is never unregistered. + /// + /// A `Signal` stream can be created for a particular signal number + /// multiple times. When a signal is received then all the associated + /// channels will receive the signal notification. + pub fn new(signal: c_int) -> IoFuture { + Signal::with_handle(signal, &Handle::current()) + } + /// Creates a new stream which will receive notifications when the current /// process receives the signal `signal`. /// @@ -362,7 +384,7 @@ impl Signal { /// A `Signal` stream can be created for a particular signal number /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. - pub fn new(signal: c_int, handle: &Handle) -> IoFuture { + pub fn with_handle(signal: c_int, handle: &Handle) -> IoFuture { let handle = handle.clone(); Box::new(future::lazy(move || { let result = (|| { diff --git a/src/windows.rs b/src/windows.rs index c3cf505ab..f105df063 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -84,7 +84,15 @@ impl Event { /// /// This function will register a handler via `SetConsoleCtrlHandler` and /// deliver notifications to the returned stream. - pub fn ctrl_c(handle: &Handle) -> IoFuture { + pub fn ctrl_c() -> IoFuture { + Event::ctrl_c_handle(&Handle::current()) + } + + /// Creates a new stream listening for the `CTRL_C_EVENT` events. + /// + /// This function will register a handler via `SetConsoleCtrlHandler` and + /// deliver notifications to the returned stream. + pub fn ctrl_c_handle(handle: &Handle) -> IoFuture { Event::new(CTRL_C_EVENT, handle) } @@ -92,7 +100,15 @@ impl Event { /// /// This function will register a handler via `SetConsoleCtrlHandler` and /// deliver notifications to the returned stream. - pub fn ctrl_break(handle: &Handle) -> IoFuture { + pub fn ctrl_break() -> IoFuture { + Event::ctrl_break_handle(&Handle::current()) + } + + /// Creates a new stream listening for the `CTRL_BREAK_EVENT` events. + /// + /// This function will register a handler via `SetConsoleCtrlHandler` and + /// deliver notifications to the returned stream. + pub fn ctrl_break_handle(handle: &Handle) -> IoFuture { Event::new(CTRL_BREAK_EVENT, handle) } diff --git a/tests/signal.rs b/tests/signal.rs index a53a49b27..54b6916b2 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -11,16 +11,14 @@ use std::thread; use std::time::Duration; use futures::stream::Stream; -use futures::{future, Future, IntoFuture}; +use futures::{Future, IntoFuture}; use tokio_core::reactor::{Core, Timeout}; use tokio_signal::unix::Signal; #[test] fn simple() { let mut lp = Core::new().unwrap(); - let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGUSR1, &handle.new_tokio_handle())) - .unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } @@ -30,16 +28,15 @@ fn simple() { #[test] fn tokio_simple() { tokio::run( - future::lazy(|| { - Signal::new(libc::SIGUSR1, &tokio::reactor::Handle::default()) - .into_future() - .and_then(|signal| { - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - signal.into_future().map(|_| ()).map_err(|(err, _)| err) - }) - }).map_err(|err| panic!("{}", err)), + Signal::new(libc::SIGUSR1) + .into_future() + .and_then(|signal| { + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + signal.into_future().map(|_| ()).map_err(|(err, _)| err) + }) + .map_err(|err| panic!("{}", err)), ) } @@ -47,10 +44,14 @@ fn tokio_simple() { fn notify_both() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle.new_tokio_handle())) - .unwrap(); - let signal2 = lp.run(Signal::new(libc::SIGUSR2, &handle.new_tokio_handle())) - .unwrap(); + let signal1 = lp.run(Signal::with_handle( + libc::SIGUSR2, + &handle.new_tokio_handle(), + )).unwrap(); + let signal2 = lp.run(Signal::with_handle( + libc::SIGUSR2, + &handle.new_tokio_handle(), + )).unwrap(); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); } @@ -63,8 +64,10 @@ fn notify_both() { fn drop_then_get_a_signal() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGUSR1, &handle.new_tokio_handle())) - .unwrap(); + let signal = lp.run(Signal::with_handle( + libc::SIGUSR1, + &handle.new_tokio_handle(), + )).unwrap(); drop(signal); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); @@ -76,9 +79,7 @@ fn drop_then_get_a_signal() { #[test] fn twice() { let mut lp = Core::new().unwrap(); - let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGUSR1, &handle.new_tokio_handle())) - .unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } @@ -102,9 +103,7 @@ fn multi_loop() { let sender = sender.clone(); thread::spawn(move || { let mut lp = Core::new().unwrap(); - let handle = lp.handle(); - let signal = lp.run(Signal::new(libc::SIGHUP, &handle.new_tokio_handle())) - .unwrap(); + let signal = lp.run(Signal::new(libc::SIGHUP)).unwrap(); sender.send(()).unwrap(); lp.run(signal.into_future()).ok().unwrap(); }) From 45ba6e2652119d507ad3c3d73f0602ef9cfd2e3e Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 5 May 2018 20:33:51 +0200 Subject: [PATCH 58/86] signal: Remove test that were accidentally included in the ctrl-c example --- examples/ctrl-c.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/examples/ctrl-c.rs b/examples/ctrl-c.rs index 1d3ce602d..df821a3f5 100644 --- a/examples/ctrl-c.rs +++ b/examples/ctrl-c.rs @@ -61,38 +61,3 @@ fn main() { println!("Stream ended, quiting the program."); } - -#[cfg(test)] -// `Child::kill` terminates the application instead of sending the equivalent to SIGKILL on windows -#[cfg(unix)] -mod tests { - use super::*; - - use std::env; - use std::path::Path; - use std::process::Command; - - #[test] - fn ctrl_c() { - let args = env::args().collect::>(); - let ctrl_c_child = "ctrl_c_child"; - if args.len() >= 3 && args.last().map(|s| &s[..]) == Some(ctrl_c_child) { - super::main(); - } else { - let ctrl_c_path = Path::new("target") - .join("debug") - .join("examples") - .join("ctrl-c"); - let mut child = Command::new(ctrl_c_path) - .args(&["ctrl_c", "--nocapture", ctrl_c_child]) - .spawn() - .unwrap(); - - for i in 0..STOP_AFTER { - println!("Kill {}", i); - child.kill().unwrap(); - } - child.wait().unwrap(); - } - } -} From 484fda7a2358df1638b874e63df428b961a7d7ea Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sat, 5 May 2018 20:30:57 +0200 Subject: [PATCH 59/86] signal: Add an appveyor build file It is not possible to test much on windows but this will at least verify that it can be built --- appveyor.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..6067b9d26 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,35 @@ +environment: + matrix: + # Stable channel + - TARGET: x86_64-pc-windows-gnu + CHANNEL: stable + - TARGET: x86_64-pc-windows-msvc + CHANNEL: stable + # Beta channel + - TARGET: x86_64-pc-windows-msvc + CHANNEL: beta + # Nightly channel + - TARGET: x86_64-pc-windows-msvc + CHANNEL: nightly + +# Install Rust and Cargo +# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) +install: + - curl -sSf -o rustup-init.exe https://win.rustup.rs + - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -Vv + - cargo -V + +# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents +# the "directory does not contain a project or solution file" error. +# source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113 +build: false + +test_script: + - cargo build --example ctrl-c + - rustdoc --test README.md -L target/debug/deps + +branches: + only: + - master From 9a4e4f230842b4a6de23ebf0f058c99b2ec004e5 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 6 May 2018 21:31:16 +0200 Subject: [PATCH 60/86] signal: Don't use the depreceated new method of Registration --- src/windows.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/windows.rs b/src/windows.rs index f105df063..a318483f3 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -324,7 +324,8 @@ impl mio::Evented for MyRegistration { events: mio::Ready, opts: mio::PollOpt, ) -> io::Result<()> { - let reg = mio::Registration::new(poll, token, events, opts); + let reg = mio::Registration::new2(); + reg.0.register(poll, token, events, opts)?; *self.inner.borrow_mut() = Some(reg); Ok(()) } From 31b51004f2dfaebae441f4d2c7efca5bf7ce4f5a Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 6 May 2018 21:48:40 +0200 Subject: [PATCH 61/86] signal: Increase number of signals to 33 for the sake of FreeBSD Fixes alexcrichton/tokio-signal#21 --- src/unix.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 234fcc3fe..bf4a01391 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -33,7 +33,8 @@ pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; // Number of different unix signals -const SIGNUM: usize = 32; +// (FreeBSD has 33) +const SIGNUM: usize = 33; struct SignalInfo { pending: AtomicBool, @@ -48,7 +49,7 @@ struct SignalInfo { struct Globals { sender: UnixStream, receiver: UnixStream, - signals: [SignalInfo; SIGNUM], + signals: Vec, } impl Default for SignalInfo { @@ -74,7 +75,7 @@ fn globals() -> &'static Globals { let globals = Globals { sender: sender, receiver: receiver, - signals: Default::default(), + signals: (0..SIGNUM).map(|_| Default::default()).collect(), }; GLOBALS = Box::into_raw(Box::new(globals)); }); From 40c77bd17e4f3c1c838e9c557c135c76f1d5f329 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 6 May 2018 21:50:41 +0200 Subject: [PATCH 62/86] signal: Version 0.2 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..8266f32e4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ + +## v0.2.0 (2018-05-07) + +#### Features + * Uses `tokio` instead of `tokio_core` (#24) + * Supports all 33 signals on FreeBSD (#27) + diff --git a/Cargo.toml b/Cargo.toml index 92ad04524..df8250e2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.1.5" +version = "0.2.0" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From b23ab94cd57af921539be0790fd56f8c43e11ffe Mon Sep 17 00:00:00 2001 From: jjl Date: Tue, 8 May 2018 21:35:54 +0200 Subject: [PATCH 63/86] signal: Change reference from 'tokio-core' to 'tokio' in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a97654a91..1d11bdb29 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ First, add this to your `Cargo.toml`: tokio-signal = "0.1" ``` -Next you can use this in conjunction with the `tokio-core` and `futures` crates: +Next you can use this in conjunction with the `tokio` and `futures` crates: ```rust,no_run extern crate futures; From 4374f5be703e3f1fc96caa6dc4f57ea1cad37968 Mon Sep 17 00:00:00 2001 From: Michael Hadley Date: Thu, 24 May 2018 17:01:48 -0700 Subject: [PATCH 64/86] signal: Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d11bdb29..5c01aa618 100644 --- a/README.md +++ b/README.md @@ -54,5 +54,5 @@ at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Serde by you, as defined in the Apache-2.0 license, shall be +for inclusion in tokio-signal by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. From 3f80953dee5cba450dc161315d7b8c58862c6462 Mon Sep 17 00:00:00 2001 From: Niv Kaminer Date: Tue, 22 May 2018 23:24:35 +0300 Subject: [PATCH 65/86] signal: account for definition mismatch on aarch64 android --- src/unix.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index bf4a01391..85cbc8fe0 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -111,7 +111,11 @@ extern "C" fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc: if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { return; } - if (*slot.prev.get()).sa_flags & libc::SA_SIGINFO == 0 { + #[cfg(all(target_os = "android", target_pointer_width = "64"))] + let contains_siginfo = (*slot.prev.get()).sa_flags & libc::SA_SIGINFO as libc::c_uint; + #[cfg(not(all(target_os = "android", target_pointer_width = "64")))] + let contains_siginfo = (*slot.prev.get()).sa_flags & libc::SA_SIGINFO; + if contains_siginfo == 0 { let action = mem::transmute::(fnptr); action(signum) } else { @@ -132,11 +136,16 @@ fn signal_enable(signal: c_int) -> io::Result<()> { None => return Err(io::Error::new(io::ErrorKind::Other, "signal too large")), }; unsafe { - #[cfg(target_os = "android")] + #[cfg(all(target_os = "android", target_pointer_width = "32"))] fn flags() -> libc::c_ulong { (libc::SA_RESTART as libc::c_ulong) | libc::SA_SIGINFO | (libc::SA_NOCLDSTOP as libc::c_ulong) } + #[cfg(all(target_os = "android", target_pointer_width = "64"))] + fn flags() -> libc::c_uint { + (libc::SA_RESTART as libc::c_uint) | (libc::SA_SIGINFO as libc::c_uint) + | (libc::SA_NOCLDSTOP as libc::c_uint) + } #[cfg(not(target_os = "android"))] fn flags() -> c_int { libc::SA_RESTART | libc::SA_SIGINFO | libc::SA_NOCLDSTOP From 6e1a833825604682f1a31c2a1b2a1a9ee958edeb Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 28 Jun 2018 23:57:27 +0100 Subject: [PATCH 66/86] signal: Update mio dependency to 0.6.14 This allows tokio-signal to build with `-Z minimal-versions` - see https://github.com/rust-lang/cargo/issues/5657#issuecomment-401110172 for more details. Earlier versions depend on log 0.3.1, which itself depends on libc 0.1, which doesn't build on any post-1.0 version of rust. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index df8250e2a..430752c03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ appveyor = { repository = "alexcrichton/tokio-signal" } [dependencies] futures = "0.1.11" -mio = "0.6.5" +mio = "0.6.14" tokio-reactor = "0.1.0" tokio-executor = "0.1.0" tokio-io = "0.1" From 3a81d7746a71ba990d65b9f42ac23466e8c86106 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 15 Jul 2018 20:22:34 -0700 Subject: [PATCH 67/86] signal: Split up all integration tests to run in their own process * Cargo runs each integration-style-test in its own process. Since the tests use global data structures specific to the process, we should run them in an isolated manner to avoid having cross-test interactions * Fixes alexcrichton/tokio-signal#39 --- .travis.yml | 2 +- tests/drop_then_get_a_signal.rs | 28 ++++++++++ tests/multi_loop.rs | 47 ++++++++++++++++ tests/notify_both.rs | 32 +++++++++++ tests/signal.rs | 99 --------------------------------- tests/simple.rs | 20 +++++++ tests/twice.rs | 26 +++++++++ 7 files changed, 154 insertions(+), 100 deletions(-) create mode 100644 tests/drop_then_get_a_signal.rs create mode 100644 tests/multi_loop.rs create mode 100644 tests/notify_both.rs create mode 100644 tests/simple.rs create mode 100644 tests/twice.rs diff --git a/.travis.yml b/.travis.yml index 0b08695a1..fd408ebf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: - travis-cargo --only nightly doc-upload script: - - cargo test + - cargo test --no-fail-fast - rustdoc --test README.md -L target/debug/deps env: diff --git a/tests/drop_then_get_a_signal.rs b/tests/drop_then_get_a_signal.rs new file mode 100644 index 000000000..0d53c5cac --- /dev/null +++ b/tests/drop_then_get_a_signal.rs @@ -0,0 +1,28 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio; +extern crate tokio_core; +extern crate tokio_signal; + +use std::time::Duration; + +use tokio_core::reactor::{Core, Timeout}; +use tokio_signal::unix::Signal; + +#[test] +fn drop_then_get_a_signal() { + let mut lp = Core::new().unwrap(); + let handle = lp.handle(); + let signal = lp.run(Signal::with_handle( + libc::SIGUSR1, + &handle.new_tokio_handle(), + )).unwrap(); + drop(signal); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + let timeout = Timeout::new(Duration::from_millis(1), &lp.handle()).unwrap(); + lp.run(timeout).unwrap(); +} diff --git a/tests/multi_loop.rs b/tests/multi_loop.rs new file mode 100644 index 000000000..e899b2338 --- /dev/null +++ b/tests/multi_loop.rs @@ -0,0 +1,47 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio; +extern crate tokio_core; +extern crate tokio_signal; + +use std::sync::mpsc::channel; +use std::thread; + +use futures::stream::Stream; +use tokio_core::reactor::Core; +use tokio_signal::unix::Signal; + +#[test] +fn multi_loop() { + // An "ordinary" (non-future) channel + let (sender, receiver) = channel(); + // Run multiple times, to make sure there are no race conditions + for _ in 0..10 { + // Run multiple event loops, each one in its own thread + let threads: Vec<_> = (0..4) + .map(|_| { + let sender = sender.clone(); + thread::spawn(move || { + let mut lp = Core::new().unwrap(); + let signal = lp.run(Signal::new(libc::SIGHUP)).unwrap(); + sender.send(()).unwrap(); + lp.run(signal.into_future()).ok().unwrap(); + }) + }) + .collect(); + // Wait for them to declare they're ready + for &_ in threads.iter() { + receiver.recv().unwrap(); + } + // Send a signal + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGHUP), 0); + } + // Make sure the threads terminated correctly + for t in threads { + t.join().unwrap(); + } + } +} diff --git a/tests/notify_both.rs b/tests/notify_both.rs new file mode 100644 index 000000000..b80259162 --- /dev/null +++ b/tests/notify_both.rs @@ -0,0 +1,32 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio; +extern crate tokio_core; +extern crate tokio_signal; + +use futures::stream::Stream; +use futures::Future; +use tokio_core::reactor::Core; +use tokio_signal::unix::Signal; + +#[test] +fn notify_both() { + let mut lp = Core::new().unwrap(); + let handle = lp.handle(); + let signal1 = lp.run(Signal::with_handle( + libc::SIGUSR2, + &handle.new_tokio_handle(), + )).unwrap(); + let signal2 = lp.run(Signal::with_handle( + libc::SIGUSR2, + &handle.new_tokio_handle(), + )).unwrap(); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); + } + lp.run(signal1.into_future().join(signal2.into_future())) + .ok() + .unwrap(); +} diff --git a/tests/signal.rs b/tests/signal.rs index 54b6916b2..3a3093bce 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -6,25 +6,10 @@ extern crate tokio; extern crate tokio_core; extern crate tokio_signal; -use std::sync::mpsc::channel; -use std::thread; -use std::time::Duration; - use futures::stream::Stream; use futures::{Future, IntoFuture}; -use tokio_core::reactor::{Core, Timeout}; use tokio_signal::unix::Signal; -#[test] -fn simple() { - let mut lp = Core::new().unwrap(); - let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - lp.run(signal.into_future()).ok().unwrap(); -} - #[test] fn tokio_simple() { tokio::run( @@ -39,87 +24,3 @@ fn tokio_simple() { .map_err(|err| panic!("{}", err)), ) } - -#[test] -fn notify_both() { - let mut lp = Core::new().unwrap(); - let handle = lp.handle(); - let signal1 = lp.run(Signal::with_handle( - libc::SIGUSR2, - &handle.new_tokio_handle(), - )).unwrap(); - let signal2 = lp.run(Signal::with_handle( - libc::SIGUSR2, - &handle.new_tokio_handle(), - )).unwrap(); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); - } - lp.run(signal1.into_future().join(signal2.into_future())) - .ok() - .unwrap(); -} - -#[test] -fn drop_then_get_a_signal() { - let mut lp = Core::new().unwrap(); - let handle = lp.handle(); - let signal = lp.run(Signal::with_handle( - libc::SIGUSR1, - &handle.new_tokio_handle(), - )).unwrap(); - drop(signal); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - let timeout = Timeout::new(Duration::from_millis(1), &lp.handle()).unwrap(); - lp.run(timeout).unwrap(); -} - -#[test] -fn twice() { - let mut lp = Core::new().unwrap(); - let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - let (num, signal) = lp.run(signal.into_future()).ok().unwrap(); - assert_eq!(num, Some(libc::SIGUSR1)); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - lp.run(signal.into_future()).ok().unwrap(); -} - -#[test] -fn multi_loop() { - // An "ordinary" (non-future) channel - let (sender, receiver) = channel(); - // Run multiple times, to make sure there are no race conditions - for _ in 0..10 { - // Run multiple event loops, each one in its own thread - let threads: Vec<_> = (0..4) - .map(|_| { - let sender = sender.clone(); - thread::spawn(move || { - let mut lp = Core::new().unwrap(); - let signal = lp.run(Signal::new(libc::SIGHUP)).unwrap(); - sender.send(()).unwrap(); - lp.run(signal.into_future()).ok().unwrap(); - }) - }) - .collect(); - // Wait for them to declare they're ready - for &_ in threads.iter() { - receiver.recv().unwrap(); - } - // Send a signal - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGHUP), 0); - } - // Make sure the threads terminated correctly - for t in threads { - t.join().unwrap(); - } - } -} diff --git a/tests/simple.rs b/tests/simple.rs new file mode 100644 index 000000000..f24fb019b --- /dev/null +++ b/tests/simple.rs @@ -0,0 +1,20 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio_core; +extern crate tokio_signal; + +use futures::stream::Stream; +use tokio_core::reactor::Core; +use tokio_signal::unix::Signal; + +#[test] +fn simple() { + let mut lp = Core::new().unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + lp.run(signal.into_future()).ok().unwrap(); +} diff --git a/tests/twice.rs b/tests/twice.rs new file mode 100644 index 000000000..4eb5804f0 --- /dev/null +++ b/tests/twice.rs @@ -0,0 +1,26 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio; +extern crate tokio_core; +extern crate tokio_signal; + +use futures::stream::Stream; +use tokio_core::reactor::Core; +use tokio_signal::unix::Signal; + +#[test] +fn twice() { + let mut lp = Core::new().unwrap(); + let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + let (num, signal) = lp.run(signal.into_future()).ok().unwrap(); + assert_eq!(num, Some(libc::SIGUSR1)); + unsafe { + assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); + } + lp.run(signal.into_future()).ok().unwrap(); +} From b6ecfa251c1943abde0be4dcc0b467eef79676f0 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 14 Jul 2018 13:01:39 -0700 Subject: [PATCH 68/86] signal: Add (failing) test case which exibits starvation on drop * Currently, whenever a new signal stream is created we attempt to register a global pipe with the event loop to drive events. * We also (correctly) swallow any descriptor-already-registered errors since the same pipe is always used * However, we currently *deregister* the same global pipe *any time* a Signal stream is dropped. * This means that if 2 or more of Signal instances exist simultaneously (even if listening for different signals) and one of them is dropped, the remainder will starve (until any new signal is created again). --- ...ing_does_not_deregister_other_instances.rs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/dropping_does_not_deregister_other_instances.rs diff --git a/tests/dropping_does_not_deregister_other_instances.rs b/tests/dropping_does_not_deregister_other_instances.rs new file mode 100644 index 000000000..c0df714e6 --- /dev/null +++ b/tests/dropping_does_not_deregister_other_instances.rs @@ -0,0 +1,45 @@ +#![cfg(unix)] + +extern crate futures; +extern crate libc; +extern crate tokio; +extern crate tokio_signal; + +use futures::stream::Stream; +use futures::Future; +use tokio_signal::unix::Signal; +use std::time::{Duration, Instant}; + +#[test] +fn dropping_signal_does_not_deregister_any_other_instances() { + // NB: Deadline requires a timer registration which is provided by + // tokio's `current_thread::Runtime`, but isn't available by just using + // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. + let mut rt = tokio::runtime::current_thread::Runtime::new() + .expect("failed to init runtime"); + + // FIXME(38): there appears to be a bug with the order these are created/destroyed + // If the duplicate signal is created first and dropped, it appears that `signal` + // will starve. This ordering also appears to be OS specific... + let first_signal = rt.block_on(Signal::new(libc::SIGUSR1)) + .expect("failed to register duplicate signal"); + let second_signal = rt.block_on(Signal::new(libc::SIGUSR1)) + .expect("failed to register signal"); + let (duplicate, signal) = if cfg!(target_os = "linux") { + (second_signal, first_signal) + } else { + // macOS + (first_signal, second_signal) + }; + + drop(duplicate); + + unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } + + let signal_future = signal.into_future() + .map_err(|(e, _)| e); + + let deadline_time = Instant::now() + Duration::from_secs(1); + rt.block_on(tokio::timer::Deadline::new(signal_future, deadline_time)) + .expect("failed to get signal"); +} From 2d4bfa1485c7abfbb83518da8928d7070d525b52 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 14 Jul 2018 16:22:05 -0700 Subject: [PATCH 69/86] signal: Fix starvation of signal streams on drop of another instance * We introduce a new global structure which keeps track of how many signal streams have been registered with a given event loop (the event loop is identified by its OS file descriptor) * We only attempt to deregister our global evented pipe from any event loop if and only if we are the last signal that was registered with it --- src/unix.rs | 48 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 85cbc8fe0..b4467e264 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -9,6 +9,7 @@ pub extern crate libc; extern crate mio; extern crate mio_uds; +use std::collections::hash_map::HashMap; use std::cell::UnsafeCell; use std::io; use std::io::prelude::*; @@ -50,6 +51,9 @@ struct Globals { sender: UnixStream, receiver: UnixStream, signals: Vec, + // A count of how many signal streams are registered on the same event loop instance + // so that we can avoid deregistering our receiver stream prematurely + pollfd_register_count: Mutex>, } impl Default for SignalInfo { @@ -76,6 +80,7 @@ fn globals() -> &'static Globals { sender: sender, receiver: receiver, signals: (0..SIGNUM).map(|_| Default::default()).collect(), + pollfd_register_count: Mutex::new(HashMap::new()), }; GLOBALS = Box::into_raw(Box::new(globals)); }); @@ -191,14 +196,30 @@ impl Evented for EventedReceiver { events: Ready, opts: PollOpt, ) -> io::Result<()> { - let fd = globals().receiver.as_raw_fd(); + let globals = globals(); + let fd = globals.receiver.as_raw_fd(); + + // NB: we must try registering with the event loop regardless of our + // reference count. If the event loop descriptor is closed, we won't + // get any notifications to clean up our map, so if a new event loop + // descriptor collides with an entry in our map, we want to make sure + // we don't accidentally skip the registration. match EventedFd(&fd).register(poll, token, events, opts) { - Ok(()) => Ok(()), + Ok(()) => {} // Due to tokio-rs/tokio-core#307 - Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()), - Err(e) => Err(e), + Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {}, + Err(e) => return Err(e), } + + *globals.pollfd_register_count + .lock() + .expect("poisoned") + .entry(poll.as_raw_fd()) + .or_insert(0) += 1; + + Ok(()) } + fn reregister( &self, poll: &MioPoll, @@ -210,8 +231,23 @@ impl Evented for EventedReceiver { EventedFd(&fd).reregister(poll, token, events, opts) } fn deregister(&self, poll: &MioPoll) -> io::Result<()> { - let fd = globals().receiver.as_raw_fd(); - EventedFd(&fd).deregister(poll) + let globals = globals(); + + let mut guard = globals.pollfd_register_count + .lock() + .expect("poisoned"); + let ref_count = guard.entry(poll.as_raw_fd()).or_insert(1); + *ref_count -= 1; + + // Only deregister if we're the last stream listening for this signal + // on this event loop. Otherwise, if multiple streams are opened for + // the same signal and one of them is closed, the remainder will starve. + if *ref_count == 0 { + let fd = globals.receiver.as_raw_fd(); + EventedFd(&fd).deregister(poll) + } else { + Ok(()) + } } } From c9ffd98b1e5a97a12db9deb03d54026501e4a1d3 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 21 Jul 2018 20:36:47 -0700 Subject: [PATCH 70/86] signal: Fix a potential Signal starvation based on creation order * As observed in alexcrichton/tokio-signal#38, Signal instances can starve based on the order they are created in, and this ordering appears to be platform/OS specific * The crux of the issue is that we woud only *attempt* to broadcast any pending signals if we successfully read out at least one byte from the global pipe. * For reasons unclear to me, the affected Signal instance would get woken up after the signal handler writes to the global pipe, but it would immediately hit a WouldBlock error and give up, bypassing the broadcast attempt (even though the pending flag was correctly set). - Maybe this has to do with OS specifics with how the bytes are delivered (or not), or with some complex interaction with tokio and the pipe registration. It seems fishy since strace logs didn't show the signal handler pipe write fail either, but I'm all out of ideas * The fix appears simple: unconditionally attempt to broadcast any pending signals *any* time a Driver instance is woken up. * Since we perform an atomic check for each pending signal, we know that each (coalesced) signal broadcast will happen at most once. If we were supuriously woken up and no signals were pending, then nothing will be yielded to any pollers of Signal * The down side is that since each Signal instance polls a Driver instance, each poll to Signal will essentially perform N atomic operations (N = number of signals we support) in an attempt to broadcast any pending signals. - However, we can revisit optimizing this better in the future Fixes alexcrichton/tokio-signal#38 --- src/unix.rs | 24 ++++++++---------- ...ing_does_not_deregister_other_instances.rs | 25 ++++++++----------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index b4467e264..eb27f572b 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -267,10 +267,10 @@ impl Future for Driver { fn poll(&mut self) -> Poll<(), ()> { // Drain the data from the pipe and maintain interest in getting more - let any_wakeup = self.drain(); - if any_wakeup { - self.broadcast(); - } + self.drain(); + // Broadcast any signals which were received + self.broadcast(); + // This task just lives until the end of the event loop Ok(Async::NotReady) } @@ -283,23 +283,21 @@ impl Driver { }) } - /// Drain all data in the global receiver, returning whether data was to be - /// had. + /// Drain all data in the global receiver, ensuring we'll get woken up when + /// there is a write on the other end. /// - /// If this function returns `true` then some signal has been received since - /// we last checked, otherwise `false` indicates that no signal has been - /// received. - fn drain(&mut self) -> bool { - let mut received = false; + /// We do *NOT* use the existence of any read bytes as evidence a sigal was + /// received since the `pending` flags would have already been set if that + /// was the case. See #38 for more info. + fn drain(&mut self) { loop { match self.wakeup.read(&mut [0; 128]) { Ok(0) => panic!("EOF on self-pipe"), - Ok(_) => received = true, + Ok(_) => {}, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, Err(e) => panic!("Bad read on self-pipe: {}", e), } } - received } /// Go through all the signals and broadcast everything. diff --git a/tests/dropping_does_not_deregister_other_instances.rs b/tests/dropping_does_not_deregister_other_instances.rs index c0df714e6..16cab40ac 100644 --- a/tests/dropping_does_not_deregister_other_instances.rs +++ b/tests/dropping_does_not_deregister_other_instances.rs @@ -10,6 +10,8 @@ use futures::Future; use tokio_signal::unix::Signal; use std::time::{Duration, Instant}; +const TEST_SIGNAL: libc::c_int = libc::SIGUSR1; + #[test] fn dropping_signal_does_not_deregister_any_other_instances() { // NB: Deadline requires a timer registration which is provided by @@ -18,23 +20,18 @@ fn dropping_signal_does_not_deregister_any_other_instances() { let mut rt = tokio::runtime::current_thread::Runtime::new() .expect("failed to init runtime"); - // FIXME(38): there appears to be a bug with the order these are created/destroyed - // If the duplicate signal is created first and dropped, it appears that `signal` - // will starve. This ordering also appears to be OS specific... - let first_signal = rt.block_on(Signal::new(libc::SIGUSR1)) - .expect("failed to register duplicate signal"); - let second_signal = rt.block_on(Signal::new(libc::SIGUSR1)) + // NB: Testing for issue #38: signals should not starve based on ordering + let first_duplicate_signal = rt.block_on(Signal::new(TEST_SIGNAL)) + .expect("failed to register first duplicate signal"); + let signal = rt.block_on(Signal::new(TEST_SIGNAL)) .expect("failed to register signal"); - let (duplicate, signal) = if cfg!(target_os = "linux") { - (second_signal, first_signal) - } else { - // macOS - (first_signal, second_signal) - }; + let second_duplicate_signal = rt.block_on(Signal::new(TEST_SIGNAL)) + .expect("failed to register second duplicate signal"); - drop(duplicate); + drop(first_duplicate_signal); + drop(second_duplicate_signal); - unsafe { assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); } + unsafe { assert_eq!(libc::kill(libc::getpid(), TEST_SIGNAL), 0); } let signal_future = signal.into_future() .map_err(|(e, _)| e); From 266919add653dc7f867ae26a70f2e913bd431ca6 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 28 Jul 2018 12:30:46 -0700 Subject: [PATCH 71/86] signal: Refactor Signal tests * Added timeouts to all tests that were missing them - any issue we have will likely result in deadlocks/starvation so its best if all tests quickly timeout rather than require getting killed or have the CI timeout itself * Added a `support` module and put a bunch of helpers there to DRY the tests --- tests/drop_multi_loop.rs | 39 +++++++++++++++ tests/drop_then_get_a_signal.rs | 30 +++++------ ...ing_does_not_deregister_other_instances.rs | 26 +++------- tests/multi_loop.rs | 15 ++---- tests/notify_both.rs | 29 +++++------ tests/signal.rs | 30 +++++------ tests/simple.rs | 22 ++++---- tests/support.rs | 50 +++++++++++++++++++ tests/twice.rs | 25 ++++------ 9 files changed, 160 insertions(+), 106 deletions(-) create mode 100644 tests/drop_multi_loop.rs create mode 100644 tests/support.rs diff --git a/tests/drop_multi_loop.rs b/tests/drop_multi_loop.rs new file mode 100644 index 000000000..62d8da53f --- /dev/null +++ b/tests/drop_multi_loop.rs @@ -0,0 +1,39 @@ +#![cfg(unix)] + +extern crate libc; + +pub mod support; +use support::*; + +const TEST_SIGNAL: libc::c_int = libc::SIGUSR1; + +#[test] +fn dropping_loops_does_not_cause_starvation() { + let (mut rt, signal) = { + let mut first_rt = CurrentThreadRuntime::new() + .expect("failed to init first runtime"); + + let first_signal = run_with_timeout(&mut first_rt, Signal::new(TEST_SIGNAL)) + .expect("failed to register first signal"); + + let mut second_rt = CurrentThreadRuntime::new() + .expect("failed to init second runtime"); + + let second_signal = run_with_timeout(&mut second_rt, Signal::new(TEST_SIGNAL)) + .expect("failed to register second signal"); + + drop(first_rt); + drop(first_signal); + + (second_rt, second_signal) + }; + + send_signal(TEST_SIGNAL); + + let signal_future = signal.into_future() + .map_err(|(e, _)| e); + + run_with_timeout(&mut rt, signal_future) + .expect("failed to get signal"); +} + diff --git a/tests/drop_then_get_a_signal.rs b/tests/drop_then_get_a_signal.rs index 0d53c5cac..4323e38b7 100644 --- a/tests/drop_then_get_a_signal.rs +++ b/tests/drop_then_get_a_signal.rs @@ -1,28 +1,28 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio; -extern crate tokio_core; -extern crate tokio_signal; -use std::time::Duration; - -use tokio_core::reactor::{Core, Timeout}; -use tokio_signal::unix::Signal; +pub mod support; +use support::*; #[test] fn drop_then_get_a_signal() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal = lp.run(Signal::with_handle( + + let signal = run_core_with_timeout(&mut lp, Signal::with_handle( libc::SIGUSR1, &handle.new_tokio_handle(), - )).unwrap(); + )).expect("failed to create first signal"); drop(signal); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - let timeout = Timeout::new(Duration::from_millis(1), &lp.handle()).unwrap(); - lp.run(timeout).unwrap(); + + send_signal(libc::SIGUSR1); + let signal = lp.run(Signal::with_handle(libc::SIGUSR1, &handle.new_tokio_handle())) + .expect("failed to create signal") + .into_future() + .map(|_| ()) + .map_err(|(e, _)| panic!("{}", e)); + + run_core_with_timeout(&mut lp, signal) + .expect("failed to get signal"); } diff --git a/tests/dropping_does_not_deregister_other_instances.rs b/tests/dropping_does_not_deregister_other_instances.rs index 16cab40ac..d8d3ee1d2 100644 --- a/tests/dropping_does_not_deregister_other_instances.rs +++ b/tests/dropping_does_not_deregister_other_instances.rs @@ -1,14 +1,9 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio; -extern crate tokio_signal; -use futures::stream::Stream; -use futures::Future; -use tokio_signal::unix::Signal; -use std::time::{Duration, Instant}; +pub mod support; +use support::*; const TEST_SIGNAL: libc::c_int = libc::SIGUSR1; @@ -17,26 +12,21 @@ fn dropping_signal_does_not_deregister_any_other_instances() { // NB: Deadline requires a timer registration which is provided by // tokio's `current_thread::Runtime`, but isn't available by just using // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. - let mut rt = tokio::runtime::current_thread::Runtime::new() + let mut rt = CurrentThreadRuntime::new() .expect("failed to init runtime"); // NB: Testing for issue #38: signals should not starve based on ordering - let first_duplicate_signal = rt.block_on(Signal::new(TEST_SIGNAL)) + let first_duplicate_signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) .expect("failed to register first duplicate signal"); - let signal = rt.block_on(Signal::new(TEST_SIGNAL)) + let signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) .expect("failed to register signal"); - let second_duplicate_signal = rt.block_on(Signal::new(TEST_SIGNAL)) + let second_duplicate_signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) .expect("failed to register second duplicate signal"); drop(first_duplicate_signal); drop(second_duplicate_signal); - unsafe { assert_eq!(libc::kill(libc::getpid(), TEST_SIGNAL), 0); } - - let signal_future = signal.into_future() - .map_err(|(e, _)| e); - - let deadline_time = Instant::now() + Duration::from_secs(1); - rt.block_on(tokio::timer::Deadline::new(signal_future, deadline_time)) + send_signal(TEST_SIGNAL); + run_with_timeout(&mut rt, signal.into_future().map_err(|(e, _)| e)) .expect("failed to get signal"); } diff --git a/tests/multi_loop.rs b/tests/multi_loop.rs index e899b2338..40facc191 100644 --- a/tests/multi_loop.rs +++ b/tests/multi_loop.rs @@ -1,17 +1,12 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio; -extern crate tokio_core; -extern crate tokio_signal; use std::sync::mpsc::channel; use std::thread; -use futures::stream::Stream; -use tokio_core::reactor::Core; -use tokio_signal::unix::Signal; +pub mod support; +use support::*; #[test] fn multi_loop() { @@ -27,7 +22,7 @@ fn multi_loop() { let mut lp = Core::new().unwrap(); let signal = lp.run(Signal::new(libc::SIGHUP)).unwrap(); sender.send(()).unwrap(); - lp.run(signal.into_future()).ok().unwrap(); + run_core_with_timeout(&mut lp, signal.into_future()).ok().unwrap(); }) }) .collect(); @@ -36,9 +31,7 @@ fn multi_loop() { receiver.recv().unwrap(); } // Send a signal - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGHUP), 0); - } + send_signal(libc::SIGHUP); // Make sure the threads terminated correctly for t in threads { t.join().unwrap(); diff --git a/tests/notify_both.rs b/tests/notify_both.rs index b80259162..ddcec33b7 100644 --- a/tests/notify_both.rs +++ b/tests/notify_both.rs @@ -1,32 +1,27 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio; -extern crate tokio_core; -extern crate tokio_signal; -use futures::stream::Stream; -use futures::Future; -use tokio_core::reactor::Core; -use tokio_signal::unix::Signal; +pub mod support; +use support::*; #[test] fn notify_both() { let mut lp = Core::new().unwrap(); let handle = lp.handle(); - let signal1 = lp.run(Signal::with_handle( + + let signal1 = run_core_with_timeout(&mut lp, Signal::with_handle( libc::SIGUSR2, &handle.new_tokio_handle(), - )).unwrap(); - let signal2 = lp.run(Signal::with_handle( + )).expect("failed to create signal1"); + + let signal2 = run_core_with_timeout(&mut lp, Signal::with_handle( libc::SIGUSR2, &handle.new_tokio_handle(), - )).unwrap(); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0); - } - lp.run(signal1.into_future().join(signal2.into_future())) + )).expect("failed to create signal2"); + + send_signal(libc::SIGUSR2); + run_core_with_timeout(&mut lp, signal1.into_future().join(signal2.into_future())) .ok() - .unwrap(); + .expect("failed to create signal2"); } diff --git a/tests/signal.rs b/tests/signal.rs index 3a3093bce..05318e415 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -1,26 +1,20 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio; -extern crate tokio_core; -extern crate tokio_signal; -use futures::stream::Stream; -use futures::{Future, IntoFuture}; -use tokio_signal::unix::Signal; +pub mod support; +use support::*; #[test] fn tokio_simple() { - tokio::run( - Signal::new(libc::SIGUSR1) - .into_future() - .and_then(|signal| { - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - signal.into_future().map(|_| ()).map_err(|(err, _)| err) - }) - .map_err(|err| panic!("{}", err)), - ) + let signal_future = Signal::new(libc::SIGUSR1) + .and_then(|signal| { + send_signal(libc::SIGUSR1); + signal.into_future().map(|_| ()).map_err(|(err, _)| err) + }); + + let mut rt = CurrentThreadRuntime::new() + .expect("failed to init runtime"); + run_with_timeout(&mut rt, signal_future) + .expect("failed"); } diff --git a/tests/simple.rs b/tests/simple.rs index f24fb019b..117cdce19 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -1,20 +1,20 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio_core; -extern crate tokio_signal; -use futures::stream::Stream; -use tokio_core::reactor::Core; -use tokio_signal::unix::Signal; +pub mod support; +use support::*; #[test] fn simple() { let mut lp = Core::new().unwrap(); - let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - lp.run(signal.into_future()).ok().unwrap(); + + let signal = run_core_with_timeout(&mut lp, Signal::new(libc::SIGUSR1)) + .expect("failed to create signal"); + + send_signal(libc::SIGUSR1); + + run_core_with_timeout(&mut lp, signal.into_future()) + .ok() + .expect("failed to get signal"); } diff --git a/tests/support.rs b/tests/support.rs new file mode 100644 index 000000000..c9c7fd620 --- /dev/null +++ b/tests/support.rs @@ -0,0 +1,50 @@ +extern crate libc; +extern crate futures; +extern crate tokio; +extern crate tokio_core; +extern crate tokio_signal; + +use self::libc::{c_int, getpid, kill}; +use std::time::{Duration, Instant}; +use self::tokio::timer::Deadline; +use self::tokio_core::reactor::Timeout; + +pub use self::futures::{Future, Stream}; +pub use self::tokio_core::reactor::Core; +pub use self::tokio::runtime::current_thread::Runtime as CurrentThreadRuntime; +pub use self::tokio_signal::unix::Signal; + +pub fn run_core_with_timeout(lp: &mut Core, future: F) -> Result + where F: Future +{ + let timeout = Timeout::new(Duration::from_secs(1), &lp.handle()) + .expect("failed to register timeout") + .map(|()| panic!("timeout exceeded")) + .map_err(|e| panic!("timeout error: {}", e)); + + lp.run(future.select(timeout)) + .map(|(r, _)| r) + .map_err(|(e, _)| e) +} + +pub fn run_with_timeout(rt: &mut CurrentThreadRuntime, future: F) -> Result + where F: Future +{ + let deadline = Deadline::new(future, Instant::now() + Duration::from_secs(1)) + .map_err(|e| if e.is_timer() { + panic!("failed to register timer"); + } else if e.is_elapsed() { + panic!("timed out") + } else { + e.into_inner().expect("missing inner error") + }); + + rt.block_on(deadline) +} + +#[cfg(unix)] +pub fn send_signal(signal: c_int) { + unsafe { + assert_eq!(kill(getpid(), signal), 0); + } +} diff --git a/tests/twice.rs b/tests/twice.rs index 4eb5804f0..3ddefa58a 100644 --- a/tests/twice.rs +++ b/tests/twice.rs @@ -1,26 +1,19 @@ #![cfg(unix)] -extern crate futures; extern crate libc; -extern crate tokio; -extern crate tokio_core; -extern crate tokio_signal; -use futures::stream::Stream; -use tokio_core::reactor::Core; -use tokio_signal::unix::Signal; +pub mod support; +use support::*; #[test] fn twice() { let mut lp = Core::new().unwrap(); - let signal = lp.run(Signal::new(libc::SIGUSR1)).unwrap(); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - let (num, signal) = lp.run(signal.into_future()).ok().unwrap(); + let signal = run_core_with_timeout(&mut lp, Signal::new(libc::SIGUSR1)).unwrap(); + + send_signal(libc::SIGUSR1); + let (num, signal) = run_core_with_timeout(&mut lp, signal.into_future()).ok().unwrap(); assert_eq!(num, Some(libc::SIGUSR1)); - unsafe { - assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0); - } - lp.run(signal.into_future()).ok().unwrap(); + + send_signal(libc::SIGUSR1); + run_core_with_timeout(&mut lp, signal.into_future()).ok().unwrap(); } From 32d3e0e1f91b3c5ba95ae9706e60e950f4f25f20 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sun, 12 Aug 2018 17:17:25 -0700 Subject: [PATCH 72/86] signal: Bump version to 0.2.2 --- CHANGELOG.md | 22 ++++++++++++++++++++-- Cargo.toml | 4 ++-- src/lib.rs | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8266f32e4..600401d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,25 @@ - -## v0.2.0 (2018-05-07) +# Changelog +All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.2.2] - +### Fixes +* Fix starvation of `Signal`s whenever a `Signal` instance is dropped +* Fix starvation of individual `Signal`s based on their creation order + +## [0.2.1] - 2018-05-27 +### Fixes +* Bump minimum supported version of `mio` to 0.6.14 + +## 0.2.0 - 2018-05-07 #### Features * Uses `tokio` instead of `tokio_core` (#24) * Supports all 33 signals on FreeBSD (#27) +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...HEAD +[0.2.2]: https://github.com/alexcrichton/tokio-signal/compare/0.2.1...0.2.2 +[0.2.1]: https://github.com/alexcrichton/tokio-signal/compare/0.2.0...0.2.1 diff --git a/Cargo.toml b/Cargo.toml index 430752c03..3dd61c2ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "tokio-signal" -version = "0.2.0" +version = "0.2.2" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" homepage = "https://github.com/alexcrichton/tokio-signal" -documentation = "https://docs.rs/tokio-signal/0.1" +documentation = "https://docs.rs/tokio-signal/0.2" description = """ An implementation of an asynchronous Unix signal handling backed futures. """ diff --git a/src/lib.rs b/src/lib.rs index df8ad783a..29dc914c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ //! # fn main() {} //! ``` -#![doc(html_root_url = "https://docs.rs/tokio-signal/0.1")] +#![doc(html_root_url = "https://docs.rs/tokio-signal/0.2")] #![deny(missing_docs)] extern crate futures; From b8f8145b623824f825b33caeee03849c8cd01a22 Mon Sep 17 00:00:00 2001 From: Niv Kaminer Date: Thu, 23 Aug 2018 14:29:24 +0300 Subject: [PATCH 73/86] signal: cast SIGINFO to be the same type as sa_flags regardless of platform --- src/unix.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index eb27f572b..6350f1cd6 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -116,11 +116,14 @@ extern "C" fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc: if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { return; } - #[cfg(all(target_os = "android", target_pointer_width = "64"))] - let contains_siginfo = (*slot.prev.get()).sa_flags & libc::SA_SIGINFO as libc::c_uint; - #[cfg(not(all(target_os = "android", target_pointer_width = "64")))] - let contains_siginfo = (*slot.prev.get()).sa_flags & libc::SA_SIGINFO; - if contains_siginfo == 0 { + let mut sa_flags = (*slot.prev.get()).sa_flags; + // android defines SIGINFO with a different type than sa_flags, + // this ensures that the variables are of the same type regardless of platform + #[allow(unused_assignments)] + let mut siginfo = sa_flags; + siginfo = libc::SA_SIGINFO as _; + sa_flags &= siginfo; + if sa_flags == 0 { let action = mem::transmute::(fnptr); action(signum) } else { @@ -141,25 +144,18 @@ fn signal_enable(signal: c_int) -> io::Result<()> { None => return Err(io::Error::new(io::ErrorKind::Other, "signal too large")), }; unsafe { - #[cfg(all(target_os = "android", target_pointer_width = "32"))] - fn flags() -> libc::c_ulong { - (libc::SA_RESTART as libc::c_ulong) | libc::SA_SIGINFO - | (libc::SA_NOCLDSTOP as libc::c_ulong) - } - #[cfg(all(target_os = "android", target_pointer_width = "64"))] - fn flags() -> libc::c_uint { - (libc::SA_RESTART as libc::c_uint) | (libc::SA_SIGINFO as libc::c_uint) - | (libc::SA_NOCLDSTOP as libc::c_uint) - } - #[cfg(not(target_os = "android"))] - fn flags() -> c_int { - libc::SA_RESTART | libc::SA_SIGINFO | libc::SA_NOCLDSTOP - } let mut err = None; siginfo.init.call_once(|| { let mut new: libc::sigaction = mem::zeroed(); new.sa_sigaction = handler as usize; - new.sa_flags = flags(); + let flags = libc::SA_RESTART | libc::SA_NOCLDSTOP;; + // android defines SIGINFO with a different type than sa_flags, + // this ensures that the variables are of the same type regardless of platform + #[allow(unused_assignments)] + let mut sa_siginfo = flags; + sa_siginfo = libc::SA_SIGINFO as _; + let flags = flags | sa_siginfo; + new.sa_flags = flags as _; if libc::sigaction(signal, &new, &mut *siginfo.prev.get()) != 0 { err = Some(io::Error::last_os_error()); } else { From 214722a296dfa7489cb37b5f6998be270c213fa2 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Thu, 23 Aug 2018 17:52:18 -0700 Subject: [PATCH 74/86] signal: Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 600401d40..c34322df2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [0.2.2] - +## [0.2.2] - 2018-08-14 ### Fixes * Fix starvation of `Signal`s whenever a `Signal` instance is dropped * Fix starvation of individual `Signal`s based on their creation order From f0ac62151be424a63cb9dd0e2d834ef5aeb1e248 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 19 Aug 2018 10:57:44 -0600 Subject: [PATCH 75/86] signal: Update tokio dependency tokio::runtime::current_thread was added in 0.1.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3dd61c2ac..161a2853d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ mio-uds = "0.6" [dev-dependencies] tokio-core = "0.1.17" -tokio = "0.1.0" +tokio = "0.1.6" [target.'cfg(windows)'.dependencies.winapi] version = "0.3" From d9edc26e974020ca4d7d4737c282f9093fcd82a7 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 19 Aug 2018 10:58:11 -0600 Subject: [PATCH 76/86] signal: export SIGINFO on supported platforms. --- src/unix.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/unix.rs b/src/unix.rs index 6350f1cd6..5d5f5ee80 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -32,6 +32,9 @@ use tokio_io::IoFuture; pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; +#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos", + target_os = "netbsd", target_os = "openbsd"))] +pub use self::libc::SIGINFO; // Number of different unix signals // (FreeBSD has 33) From 8ad66d296f993b8261c8e2c2662e51c1311d2c5c Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 19 Aug 2018 11:08:02 -0600 Subject: [PATCH 77/86] signal: Add CHANGELOG entry for SIGINFO. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c34322df2..388ca4f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## Features +* Exposes `SIGINFO` on BSD-based operating systems. (#46) ## [0.2.2] - 2018-08-14 ### Fixes From bcd42d11d9c70ffbc2064c0511d662d234a4e58e Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 24 Aug 2018 21:25:14 -0600 Subject: [PATCH 78/86] signal: Move SIGINFO to a BSD-specific submodule --- src/unix.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/unix.rs b/src/unix.rs index 5d5f5ee80..76ca51cc6 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -32,9 +32,14 @@ use tokio_io::IoFuture; pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; -#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos", - target_os = "netbsd", target_os = "openbsd"))] -pub use self::libc::SIGINFO; + +/// BSD-specific definitions +mod bsd { + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", + target_os = "macos", target_os = "netbsd", + target_os = "openbsd"))] + pub use super::libc::SIGINFO; +} // Number of different unix signals // (FreeBSD has 33) From 837c3934d5dc60f095a3e82e5ffbeec0d501c004 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 Aug 2018 09:40:04 -0700 Subject: [PATCH 79/86] signal: Also cfg gate the entire `unix::bsd` module --- src/unix.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/unix.rs b/src/unix.rs index 76ca51cc6..0d8a80a06 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -34,6 +34,13 @@ pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; /// BSD-specific definitions +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", +))] mod bsd { #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos", target_os = "netbsd", From 90ea2f6c5b70c968f1fdf10780c04d1f13f89c22 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 Aug 2018 09:50:00 -0700 Subject: [PATCH 80/86] signal: Bump version to 0.2.3 --- CHANGELOG.md | 7 +++++-- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 388ca4f71..d03e13e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] -## Features + +## [0.2.3] - 2018-08-25 +### Features * Exposes `SIGINFO` on BSD-based operating systems. (#46) ## [0.2.2] - 2018-08-14 @@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * Uses `tokio` instead of `tokio_core` (#24) * Supports all 33 signals on FreeBSD (#27) -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.2...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.3...HEAD +[0.2.3]: https://github.com/alexcrichton/tokio-signal/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/alexcrichton/tokio-signal/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/alexcrichton/tokio-signal/compare/0.2.0...0.2.1 diff --git a/Cargo.toml b/Cargo.toml index 161a2853d..385ac7d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.2.2" +version = "0.2.3" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" diff --git a/README.md b/README.md index 5c01aa618..cb62dfbf4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ First, add this to your `Cargo.toml`: ```toml [dependencies] -tokio-signal = "0.1" +tokio-signal = "0.2" ``` Next you can use this in conjunction with the `tokio` and `futures` crates: From b7f5bc95fe334df81595b52094236adce0e13e9e Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 25 Aug 2018 11:59:49 -0600 Subject: [PATCH 81/86] signal: Actually make unix::bsd public --- src/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unix.rs b/src/unix.rs index 0d8a80a06..37cd6b5f4 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -41,7 +41,7 @@ pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; target_os = "netbsd", target_os = "openbsd", ))] -mod bsd { +pub mod bsd { #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos", target_os = "netbsd", target_os = "openbsd"))] From 98e76d9bc610cda12d2baead476bb8bcf9eff3d0 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Sat, 25 Aug 2018 11:16:54 -0700 Subject: [PATCH 82/86] signal: Bump version to to 0.2.4 --- CHANGELOG.md | 7 ++++++- Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d03e13e01..bd030c49e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [0.2.4] - 2018-08-25 +### Fixes +* Actually make `unix::bsd` public + ## [0.2.3] - 2018-08-25 ### Features * Exposes `SIGINFO` on BSD-based operating systems. (#46) @@ -24,7 +28,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * Uses `tokio` instead of `tokio_core` (#24) * Supports all 33 signals on FreeBSD (#27) -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.3...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.4...HEAD +[0.2.4]: https://github.com/alexcrichton/tokio-signal/compare/0.2.3...0.2.4 [0.2.3]: https://github.com/alexcrichton/tokio-signal/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/alexcrichton/tokio-signal/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/alexcrichton/tokio-signal/compare/0.2.0...0.2.1 diff --git a/Cargo.toml b/Cargo.toml index 385ac7d95..2bad85f69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.2.3" +version = "0.2.4" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From 605708dca6af5ac6a762f3bea235fab1303594a2 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Tue, 28 Aug 2018 16:19:58 -0700 Subject: [PATCH 83/86] signal: Fix a possible starvation with concurrent Signal polls * Originally reported in alexcrichton/tokio-process#42 * The root cause appears to be due to two different PollEvented instances trying to consume readiness events from the same file descriptor. * Previously we would simply swallow any `AlreadyExists` errors when attempting to register the pipe receiver with the event loop. I'm not sure if this means the PollEvented wrapper wasn't fully registered to receive events, or maybe there is a potential race condition with how PollEvented consumes mio readiness events. Using a fresh/duplicate file descriptor appears to mitigate the issue, however. * I was also not able to reproduce the issue as an isolated test case so there is no regression test available within this crate (but we can add one in tokio-process) --- CHANGELOG.md | 3 ++ src/unix.rs | 106 +++++++++------------------------------------------ 2 files changed, 21 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd030c49e..a852177a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixes +* Fix a possible starvation when polling multiple `Signal` instances outside of +a tokio reactor (e.g. by using `Future::wait`) ## [0.2.4] - 2018-08-25 ### Fixes diff --git a/src/unix.rs b/src/unix.rs index 37cd6b5f4..6e8466a6f 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -9,19 +9,14 @@ pub extern crate libc; extern crate mio; extern crate mio_uds; -use std::collections::hash_map::HashMap; use std::cell::UnsafeCell; use std::io; use std::io::prelude::*; use std::mem; -use std::os::unix::prelude::*; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, Once, ONCE_INIT}; use self::libc::c_int; -use self::mio::unix::EventedFd; -use self::mio::Poll as MioPoll; -use self::mio::{Evented, PollOpt, Ready, Token}; use self::mio_uds::UnixStream; use futures::future; use futures::sync::mpsc::{channel, Receiver, Sender}; @@ -66,9 +61,6 @@ struct Globals { sender: UnixStream, receiver: UnixStream, signals: Vec, - // A count of how many signal streams are registered on the same event loop instance - // so that we can avoid deregistering our receiver stream prematurely - pollfd_register_count: Mutex>, } impl Default for SignalInfo { @@ -95,7 +87,6 @@ fn globals() -> &'static Globals { sender: sender, receiver: receiver, signals: (0..SIGNUM).map(|_| Default::default()).collect(), - pollfd_register_count: Mutex::new(HashMap::new()), }; GLOBALS = Box::into_raw(Box::new(globals)); }); @@ -191,85 +182,8 @@ fn signal_enable(signal: c_int) -> io::Result<()> { } } -/// A helper struct to register our global receiving end of the signal pipe on -/// multiple event loops. -/// -/// This structure represents registering the receiving end on all event loops, -/// and uses `EventedFd` in mio to do so. It's stored in each driver task and is -/// used to read data and register interest in new signals coming in. -struct EventedReceiver; - -impl Evented for EventedReceiver { - fn register( - &self, - poll: &MioPoll, - token: Token, - events: Ready, - opts: PollOpt, - ) -> io::Result<()> { - let globals = globals(); - let fd = globals.receiver.as_raw_fd(); - - // NB: we must try registering with the event loop regardless of our - // reference count. If the event loop descriptor is closed, we won't - // get any notifications to clean up our map, so if a new event loop - // descriptor collides with an entry in our map, we want to make sure - // we don't accidentally skip the registration. - match EventedFd(&fd).register(poll, token, events, opts) { - Ok(()) => {} - // Due to tokio-rs/tokio-core#307 - Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {}, - Err(e) => return Err(e), - } - - *globals.pollfd_register_count - .lock() - .expect("poisoned") - .entry(poll.as_raw_fd()) - .or_insert(0) += 1; - - Ok(()) - } - - fn reregister( - &self, - poll: &MioPoll, - token: Token, - events: Ready, - opts: PollOpt, - ) -> io::Result<()> { - let fd = globals().receiver.as_raw_fd(); - EventedFd(&fd).reregister(poll, token, events, opts) - } - fn deregister(&self, poll: &MioPoll) -> io::Result<()> { - let globals = globals(); - - let mut guard = globals.pollfd_register_count - .lock() - .expect("poisoned"); - let ref_count = guard.entry(poll.as_raw_fd()).or_insert(1); - *ref_count -= 1; - - // Only deregister if we're the last stream listening for this signal - // on this event loop. Otherwise, if multiple streams are opened for - // the same signal and one of them is closed, the remainder will starve. - if *ref_count == 0 { - let fd = globals.receiver.as_raw_fd(); - EventedFd(&fd).deregister(poll) - } else { - Ok(()) - } - } -} - -impl Read for EventedReceiver { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - (&globals().receiver).read(buf) - } -} - struct Driver { - wakeup: PollEvented, + wakeup: PollEvented, } impl Future for Driver { @@ -289,8 +203,24 @@ impl Future for Driver { impl Driver { fn new(handle: &Handle) -> io::Result { + // NB: We give each driver a "fresh" reciever file descriptor to avoid + // the issues described in alexcrichton/tokio-process#42. + // + // In the past we would reuse the actual receiver file descriptor and + // swallow any errors around double registration of the same descriptor. + // I'm not sure if the second (failed) registration simply doesn't end up + // receiving wake up notifications, or there could be some race condition + // when consuming readiness events, but having distinct descriptors for + // distinct PollEvented instances appears to mitigate this. + // + // Unfortunately we cannot just use a single global PollEvented instance + // either, since we can't compare Handles or assume they will always + // point to the exact same reactor. + let stream = globals().receiver.try_clone()?; + let wakeup = PollEvented::new_with_handle(stream, handle)?; + Ok(Driver { - wakeup: try!(PollEvented::new_with_handle(EventedReceiver, handle)), + wakeup: wakeup, }) } From b594e240f9bcee0a8022471d9cb69e01f783cfd7 Mon Sep 17 00:00:00 2001 From: Ivan Petkov Date: Wed, 29 Aug 2018 19:34:43 -0700 Subject: [PATCH 84/86] signal: Bump version to 0.2.5 --- CHANGELOG.md | 5 ++++- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a852177a5..046885267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.2.5] - 2018-08-29 ### Fixes * Fix a possible starvation when polling multiple `Signal` instances outside of a tokio reactor (e.g. by using `Future::wait`) @@ -31,7 +33,8 @@ a tokio reactor (e.g. by using `Future::wait`) * Uses `tokio` instead of `tokio_core` (#24) * Supports all 33 signals on FreeBSD (#27) -[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.4...HEAD +[Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.5...HEAD +[0.2.5]: https://github.com/alexcrichton/tokio-signal/compare/0.2.4...0.2.5 [0.2.4]: https://github.com/alexcrichton/tokio-signal/compare/0.2.3...0.2.4 [0.2.3]: https://github.com/alexcrichton/tokio-signal/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/alexcrichton/tokio-signal/compare/0.2.1...0.2.2 diff --git a/Cargo.toml b/Cargo.toml index 2bad85f69..21a929e90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-signal" -version = "0.2.4" +version = "0.2.5" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/tokio-signal" From e7dc3a10916f298915ae40503b33041e53593c86 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Tue, 28 Aug 2018 20:44:43 +0200 Subject: [PATCH 85/86] signal: Use signal-hook for registration of signals This saves some code and gets rid of quite some amount of unsafe code. --- Cargo.toml | 1 + src/unix.rs | 110 ++++++++++++++++++---------------------------------- 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 21a929e90..b3118ef78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ tokio-io = "0.1" [target.'cfg(unix)'.dependencies] libc = "0.2" mio-uds = "0.6" +signal-hook = "0.1" [dev-dependencies] tokio-core = "0.1.17" diff --git a/src/unix.rs b/src/unix.rs index 6e8466a6f..fa2cba543 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -8,11 +8,10 @@ pub extern crate libc; extern crate mio; extern crate mio_uds; +extern crate signal_hook; -use std::cell::UnsafeCell; -use std::io; +use std::io::{self, Error, ErrorKind}; use std::io::prelude::*; -use std::mem; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, Once, ONCE_INIT}; @@ -53,8 +52,7 @@ struct SignalInfo { recipients: Mutex>>>, init: Once, - initialized: UnsafeCell, - prev: UnsafeCell, + initialized: AtomicBool, } struct Globals { @@ -68,9 +66,8 @@ impl Default for SignalInfo { SignalInfo { pending: AtomicBool::new(false), init: ONCE_INIT, - initialized: UnsafeCell::new(false), + initialized: AtomicBool::new(false), recipients: Mutex::new(Vec::new()), - prev: UnsafeCell::new(unsafe { mem::zeroed() }), } } } @@ -101,42 +98,13 @@ fn globals() -> &'static Globals { /// 1. Flag that our specific signal was received (e.g. store an atomic flag) /// 2. Wake up driver tasks by writing a byte to a pipe /// -/// Those two operations shoudl both be async-signal safe. After that's done we -/// just try to call a previous signal handler, if any, to be "good denizens of -/// the internet" -extern "C" fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc::c_void) { - type FnSigaction = extern "C" fn(c_int, *mut libc::siginfo_t, *mut libc::c_void); - type FnHandler = extern "C" fn(c_int); - unsafe { - let slot = match (*GLOBALS).signals.get(signum as usize) { - Some(slot) => slot, - None => return, - }; - slot.pending.store(true, Ordering::SeqCst); +/// Those two operations shoudl both be async-signal safe. +fn action(slot: &SignalInfo, mut sender: &UnixStream) { + slot.pending.store(true, Ordering::SeqCst); - // Send a wakeup, ignore any errors (anything reasonably possible is - // full pipe and then it will wake up anyway). - drop((*GLOBALS).sender.write(&[1])); - - let fnptr = (*slot.prev.get()).sa_sigaction; - if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN { - return; - } - let mut sa_flags = (*slot.prev.get()).sa_flags; - // android defines SIGINFO with a different type than sa_flags, - // this ensures that the variables are of the same type regardless of platform - #[allow(unused_assignments)] - let mut siginfo = sa_flags; - siginfo = libc::SA_SIGINFO as _; - sa_flags &= siginfo; - if sa_flags == 0 { - let action = mem::transmute::(fnptr); - action(signum) - } else { - let action = mem::transmute::(fnptr); - action(signum, info, ptr) - } - } + // Send a wakeup, ignore any errors (anything reasonably possible is + // full pipe and then it will wake up anyway). + drop(sender.write(&[1])); } /// Enable this module to receive signal notifications for the `signal` @@ -145,40 +113,31 @@ extern "C" fn handler(signum: c_int, info: *mut libc::siginfo_t, ptr: *mut libc: /// This will register the signal handler if it hasn't already been registered, /// returning any error along the way if that fails. fn signal_enable(signal: c_int) -> io::Result<()> { - let siginfo = match globals().signals.get(signal as usize) { + if signal_hook::FORBIDDEN.contains(&signal) { + return Err(Error::new(ErrorKind::Other, format!("Refusing to register signal {}", signal))); + } + + let globals = globals(); + let siginfo = match globals.signals.get(signal as usize) { Some(slot) => slot, None => return Err(io::Error::new(io::ErrorKind::Other, "signal too large")), }; - unsafe { - let mut err = None; - siginfo.init.call_once(|| { - let mut new: libc::sigaction = mem::zeroed(); - new.sa_sigaction = handler as usize; - let flags = libc::SA_RESTART | libc::SA_NOCLDSTOP;; - // android defines SIGINFO with a different type than sa_flags, - // this ensures that the variables are of the same type regardless of platform - #[allow(unused_assignments)] - let mut sa_siginfo = flags; - sa_siginfo = libc::SA_SIGINFO as _; - let flags = flags | sa_siginfo; - new.sa_flags = flags as _; - if libc::sigaction(signal, &new, &mut *siginfo.prev.get()) != 0 { - err = Some(io::Error::last_os_error()); - } else { - *siginfo.initialized.get() = true; - } - }); - if let Some(err) = err { - return Err(err); - } - if *siginfo.initialized.get() { - Ok(()) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "failed to register signal handler", - )) + let mut registered = Ok(()); + siginfo.init.call_once(|| { + registered = unsafe { + signal_hook::register(signal, move || action(siginfo, &globals.sender)).map(|_| ()) + }; + if registered.is_ok() { + siginfo.initialized.store(true, Ordering::Relaxed); } + }); + registered?; + // If the call_once failed, it won't be retried on the next attempt to register the signal. In + // such case it is not run, registered is still `Ok(())`, initialized is still false. + if siginfo.initialized.load(Ordering::Relaxed) { + Ok(()) + } else { + Err(Error::new(ErrorKind::Other, "Failed to register signal handler")) } } @@ -347,6 +306,13 @@ impl Signal { /// A `Signal` stream can be created for a particular signal number /// multiple times. When a signal is received then all the associated /// channels will receive the signal notification. + /// + /// # Errors + /// + /// * If the lower-level C functions fail for some reason. + /// * If the previous initialization of this specific signal failed. + /// * If the signal is one of + /// [`signal_hook::FORBIDDEN`](https://docs.rs/signal-hook/*/signal_hook/fn.register.html#panics) pub fn new(signal: c_int) -> IoFuture { Signal::with_handle(signal, &Handle::current()) } From 35687f1d187ec21019906f900e99f064bf55c0eb Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Sat, 1 Sep 2018 10:57:20 +0200 Subject: [PATCH 86/86] signal: Move to tokio-signal subdirectory As a preparation to merge with tokio. --- .travis.yml => tokio-signal/.travis.yml | 0 CHANGELOG.md => tokio-signal/CHANGELOG.md | 0 Cargo.toml => tokio-signal/Cargo.toml | 0 LICENSE-APACHE => tokio-signal/LICENSE-APACHE | 0 LICENSE-MIT => tokio-signal/LICENSE-MIT | 0 README.md => tokio-signal/README.md | 0 appveyor.yml => tokio-signal/appveyor.yml | 0 {examples => tokio-signal/examples}/ctrl-c.rs | 0 {examples => tokio-signal/examples}/multiple.rs | 0 {examples => tokio-signal/examples}/sighup-example.rs | 0 {src => tokio-signal/src}/lib.rs | 0 {src => tokio-signal/src}/unix.rs | 0 {src => tokio-signal/src}/windows.rs | 0 {tests => tokio-signal/tests}/drop_multi_loop.rs | 0 {tests => tokio-signal/tests}/drop_then_get_a_signal.rs | 0 .../tests}/dropping_does_not_deregister_other_instances.rs | 0 {tests => tokio-signal/tests}/multi_loop.rs | 0 {tests => tokio-signal/tests}/notify_both.rs | 0 {tests => tokio-signal/tests}/signal.rs | 0 {tests => tokio-signal/tests}/simple.rs | 0 {tests => tokio-signal/tests}/support.rs | 0 {tests => tokio-signal/tests}/twice.rs | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename .travis.yml => tokio-signal/.travis.yml (100%) rename CHANGELOG.md => tokio-signal/CHANGELOG.md (100%) rename Cargo.toml => tokio-signal/Cargo.toml (100%) rename LICENSE-APACHE => tokio-signal/LICENSE-APACHE (100%) rename LICENSE-MIT => tokio-signal/LICENSE-MIT (100%) rename README.md => tokio-signal/README.md (100%) rename appveyor.yml => tokio-signal/appveyor.yml (100%) rename {examples => tokio-signal/examples}/ctrl-c.rs (100%) rename {examples => tokio-signal/examples}/multiple.rs (100%) rename {examples => tokio-signal/examples}/sighup-example.rs (100%) rename {src => tokio-signal/src}/lib.rs (100%) rename {src => tokio-signal/src}/unix.rs (100%) rename {src => tokio-signal/src}/windows.rs (100%) rename {tests => tokio-signal/tests}/drop_multi_loop.rs (100%) rename {tests => tokio-signal/tests}/drop_then_get_a_signal.rs (100%) rename {tests => tokio-signal/tests}/dropping_does_not_deregister_other_instances.rs (100%) rename {tests => tokio-signal/tests}/multi_loop.rs (100%) rename {tests => tokio-signal/tests}/notify_both.rs (100%) rename {tests => tokio-signal/tests}/signal.rs (100%) rename {tests => tokio-signal/tests}/simple.rs (100%) rename {tests => tokio-signal/tests}/support.rs (100%) rename {tests => tokio-signal/tests}/twice.rs (100%) diff --git a/.travis.yml b/tokio-signal/.travis.yml similarity index 100% rename from .travis.yml rename to tokio-signal/.travis.yml diff --git a/CHANGELOG.md b/tokio-signal/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to tokio-signal/CHANGELOG.md diff --git a/Cargo.toml b/tokio-signal/Cargo.toml similarity index 100% rename from Cargo.toml rename to tokio-signal/Cargo.toml diff --git a/LICENSE-APACHE b/tokio-signal/LICENSE-APACHE similarity index 100% rename from LICENSE-APACHE rename to tokio-signal/LICENSE-APACHE diff --git a/LICENSE-MIT b/tokio-signal/LICENSE-MIT similarity index 100% rename from LICENSE-MIT rename to tokio-signal/LICENSE-MIT diff --git a/README.md b/tokio-signal/README.md similarity index 100% rename from README.md rename to tokio-signal/README.md diff --git a/appveyor.yml b/tokio-signal/appveyor.yml similarity index 100% rename from appveyor.yml rename to tokio-signal/appveyor.yml diff --git a/examples/ctrl-c.rs b/tokio-signal/examples/ctrl-c.rs similarity index 100% rename from examples/ctrl-c.rs rename to tokio-signal/examples/ctrl-c.rs diff --git a/examples/multiple.rs b/tokio-signal/examples/multiple.rs similarity index 100% rename from examples/multiple.rs rename to tokio-signal/examples/multiple.rs diff --git a/examples/sighup-example.rs b/tokio-signal/examples/sighup-example.rs similarity index 100% rename from examples/sighup-example.rs rename to tokio-signal/examples/sighup-example.rs diff --git a/src/lib.rs b/tokio-signal/src/lib.rs similarity index 100% rename from src/lib.rs rename to tokio-signal/src/lib.rs diff --git a/src/unix.rs b/tokio-signal/src/unix.rs similarity index 100% rename from src/unix.rs rename to tokio-signal/src/unix.rs diff --git a/src/windows.rs b/tokio-signal/src/windows.rs similarity index 100% rename from src/windows.rs rename to tokio-signal/src/windows.rs diff --git a/tests/drop_multi_loop.rs b/tokio-signal/tests/drop_multi_loop.rs similarity index 100% rename from tests/drop_multi_loop.rs rename to tokio-signal/tests/drop_multi_loop.rs diff --git a/tests/drop_then_get_a_signal.rs b/tokio-signal/tests/drop_then_get_a_signal.rs similarity index 100% rename from tests/drop_then_get_a_signal.rs rename to tokio-signal/tests/drop_then_get_a_signal.rs diff --git a/tests/dropping_does_not_deregister_other_instances.rs b/tokio-signal/tests/dropping_does_not_deregister_other_instances.rs similarity index 100% rename from tests/dropping_does_not_deregister_other_instances.rs rename to tokio-signal/tests/dropping_does_not_deregister_other_instances.rs diff --git a/tests/multi_loop.rs b/tokio-signal/tests/multi_loop.rs similarity index 100% rename from tests/multi_loop.rs rename to tokio-signal/tests/multi_loop.rs diff --git a/tests/notify_both.rs b/tokio-signal/tests/notify_both.rs similarity index 100% rename from tests/notify_both.rs rename to tokio-signal/tests/notify_both.rs diff --git a/tests/signal.rs b/tokio-signal/tests/signal.rs similarity index 100% rename from tests/signal.rs rename to tokio-signal/tests/signal.rs diff --git a/tests/simple.rs b/tokio-signal/tests/simple.rs similarity index 100% rename from tests/simple.rs rename to tokio-signal/tests/simple.rs diff --git a/tests/support.rs b/tokio-signal/tests/support.rs similarity index 100% rename from tests/support.rs rename to tokio-signal/tests/support.rs diff --git a/tests/twice.rs b/tokio-signal/tests/twice.rs similarity index 100% rename from tests/twice.rs rename to tokio-signal/tests/twice.rs