├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── appveyor.yml ├── examples ├── ctrl-c.rs ├── multiple.rs └── sighup-example.rs ├── src ├── lib.rs ├── unix.rs └── windows.rs └── tests ├── drop_multi_loop.rs ├── drop_then_get_a_signal.rs ├── dropping_does_not_deregister_other_instances.rs ├── multi_loop.rs ├── notify_both.rs ├── signal.rs ├── simple.rs ├── support.rs └── twice.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - rust: stable 7 | - os: osx 8 | - rust: beta 9 | - rust: nightly 10 | 11 | - rust: nightly 12 | before_script: 13 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 14 | script: 15 | - cargo doc --no-deps --all-features 16 | after_success: 17 | - travis-cargo --only nightly doc-upload 18 | 19 | script: 20 | - cargo test --no-fail-fast 21 | - rustdoc --test README.md -L target/debug/deps 22 | 23 | env: 24 | global: 25 | - secure: "SXXK7Znvm1s5WWQ94l9IP25mXA0uGIQ7ghBuumZz3nSAfxhhJQnYi5hCAbl2/cOfSbpgEtE137dgk6Nd9UDx7rIkLCSe8TWyYjzraX/vvX3xNtLh/fjsayYYRK9a6qU2HIJegZdxPgyF5h2DeBgeLks0Ue8drrFQ1s9bYZVUO0yeuZ3aLkL1FkIG6RXGItUFpb6srEYL1NLizYLxXFEG3cL+kKoFIWc2qPx3EwOqv/eii134nQsuObhWZvPqfTo7zfNP8W/6TnoiggpRH1nrZc3DI3CynTICIOJ2Ogn9gFX9LftYKuJysSwUNVN3WF5aOuLP/XjRSBLYc+PW3v0iqiGzMX3n1VpcyhcbsSNA7ZckGn1HZsWYwspAxkN3idSuVie9Mezm7IV4005juiYKEWEr6hlkv1lzd49QZkWOvLCFCMRiwOOGp4NyzilG1Q1Zs3G1wrcvstmasNpK+QUFNdOFvT2sm34rI4x2rQUvjC/OyqbAK+PjYmTHL47YKON5ymfUL3mAcwgUfBUSd4Wpx8G3VKg3gMcmQm27ah1knOGJWH6XulYTnfGfx6bLo5t2NGx+vZk0naqajD3auWnseobMDsFjhUIRrt6GlnfPqeFoJSm0unu3riAX+RDF/iqZdDfjhX4evETIw3SaTl8EQtVLwz7kJTnxSbTU4XTi+0M=" 26 | 27 | notifications: 28 | email: 29 | on_success: never 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.5] - 2018-08-29 10 | ### Fixes 11 | * Fix a possible starvation when polling multiple `Signal` instances outside of 12 | a tokio reactor (e.g. by using `Future::wait`) 13 | 14 | ## [0.2.4] - 2018-08-25 15 | ### Fixes 16 | * Actually make `unix::bsd` public 17 | 18 | ## [0.2.3] - 2018-08-25 19 | ### Features 20 | * Exposes `SIGINFO` on BSD-based operating systems. (#46) 21 | 22 | ## [0.2.2] - 2018-08-14 23 | ### Fixes 24 | * Fix starvation of `Signal`s whenever a `Signal` instance is dropped 25 | * Fix starvation of individual `Signal`s based on their creation order 26 | 27 | ## [0.2.1] - 2018-05-27 28 | ### Fixes 29 | * Bump minimum supported version of `mio` to 0.6.14 30 | 31 | ## 0.2.0 - 2018-05-07 32 | #### Features 33 | * Uses `tokio` instead of `tokio_core` (#24) 34 | * Supports all 33 signals on FreeBSD (#27) 35 | 36 | [Unreleased]: https://github.com/alexcrichton/tokio-process/compare/0.2.5...HEAD 37 | [0.2.5]: https://github.com/alexcrichton/tokio-signal/compare/0.2.4...0.2.5 38 | [0.2.4]: https://github.com/alexcrichton/tokio-signal/compare/0.2.3...0.2.4 39 | [0.2.3]: https://github.com/alexcrichton/tokio-signal/compare/0.2.2...0.2.3 40 | [0.2.2]: https://github.com/alexcrichton/tokio-signal/compare/0.2.1...0.2.2 41 | [0.2.1]: https://github.com/alexcrichton/tokio-signal/compare/0.2.0...0.2.1 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-signal" 3 | version = "0.2.5" 4 | authors = ["Alex Crichton "] 5 | license = "MIT/Apache-2.0" 6 | repository = "https://github.com/alexcrichton/tokio-signal" 7 | homepage = "https://github.com/alexcrichton/tokio-signal" 8 | documentation = "https://docs.rs/tokio-signal/0.2" 9 | description = """ 10 | An implementation of an asynchronous Unix signal handling backed futures. 11 | """ 12 | categories = ["asynchronous"] 13 | 14 | [badges] 15 | travis-ci = { repository = "alexcrichton/tokio-signal" } 16 | appveyor = { repository = "alexcrichton/tokio-signal" } 17 | 18 | [dependencies] 19 | futures = "0.1.11" 20 | mio = "0.6.14" 21 | tokio-reactor = "0.1.0" 22 | tokio-executor = "0.1.0" 23 | tokio-io = "0.1" 24 | 25 | [target.'cfg(unix)'.dependencies] 26 | libc = "0.2" 27 | mio-uds = "0.6" 28 | signal-hook = "0.1" 29 | 30 | [dev-dependencies] 31 | tokio-core = "0.1.17" 32 | tokio = "0.1.6" 33 | 34 | [target.'cfg(windows)'.dependencies.winapi] 35 | version = "0.3" 36 | features = ["minwindef", "wincon"] 37 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Alex Crichton 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The tokio-signal crate has been moved into the [tokio](https://github.com/tokio-rs/tokio) Git repository. 2 | 3 | Please do not use this repo anymore. 4 | 5 | # tokio-signal 6 | 7 | An implementation of Unix signal handling for Tokio 8 | 9 | [![Build Status](https://travis-ci.org/alexcrichton/tokio-signal.svg?branch=master)](https://travis-ci.org/alexcrichton/tokio-signal) 10 | 11 | [Documentation](https://docs.rs/tokio-signal) 12 | 13 | ## Usage 14 | 15 | First, add this to your `Cargo.toml`: 16 | 17 | ```toml 18 | [dependencies] 19 | tokio-signal = "0.2" 20 | ``` 21 | 22 | Next you can use this in conjunction with the `tokio` and `futures` crates: 23 | 24 | ```rust,no_run 25 | extern crate futures; 26 | extern crate tokio; 27 | extern crate tokio_signal; 28 | 29 | use futures::{Future, Stream}; 30 | 31 | fn main() { 32 | 33 | // Create an infinite stream of "Ctrl+C" notifications. Each item received 34 | // on this stream may represent multiple ctrl-c signals. 35 | let ctrl_c = tokio_signal::ctrl_c().flatten_stream(); 36 | 37 | // Process each ctrl-c as it comes in 38 | let prog = ctrl_c.for_each(|()| { 39 | println!("ctrl-c received!"); 40 | Ok(()) 41 | }); 42 | 43 | tokio::run(prog.map_err(|err| panic!("{}", err))); 44 | } 45 | ``` 46 | 47 | # License 48 | 49 | This project is licensed under either of 50 | 51 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 52 | http://www.apache.org/licenses/LICENSE-2.0) 53 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 54 | http://opensource.org/licenses/MIT) 55 | 56 | at your option. 57 | 58 | ### Contribution 59 | 60 | Unless you explicitly state otherwise, any contribution intentionally submitted 61 | for inclusion in tokio-signal by you, as defined in the Apache-2.0 license, shall be 62 | dual licensed as above, without any additional terms or conditions. 63 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | # Stable channel 4 | - TARGET: x86_64-pc-windows-gnu 5 | CHANNEL: stable 6 | - TARGET: x86_64-pc-windows-msvc 7 | CHANNEL: stable 8 | # Beta channel 9 | - TARGET: x86_64-pc-windows-msvc 10 | CHANNEL: beta 11 | # Nightly channel 12 | - TARGET: x86_64-pc-windows-msvc 13 | CHANNEL: nightly 14 | 15 | # Install Rust and Cargo 16 | # (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) 17 | install: 18 | - curl -sSf -o rustup-init.exe https://win.rustup.rs 19 | - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y 20 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 21 | - rustc -Vv 22 | - cargo -V 23 | 24 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 25 | # the "directory does not contain a project or solution file" error. 26 | # source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113 27 | build: false 28 | 29 | test_script: 30 | - cargo build --example ctrl-c 31 | - rustdoc --test README.md -L target/debug/deps 32 | 33 | branches: 34 | only: 35 | - master 36 | -------------------------------------------------------------------------------- /examples/ctrl-c.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate tokio_core; 3 | extern crate tokio_signal; 4 | 5 | use futures::{Future, Stream}; 6 | use tokio_core::reactor::Core; 7 | 8 | /// how many signals to handle before exiting 9 | const STOP_AFTER: u64 = 10; 10 | 11 | fn main() { 12 | // set up a Tokio event loop 13 | let mut core = Core::new().unwrap(); 14 | 15 | // tokio_signal provides a convenience builder for Ctrl+C 16 | // this even works cross-platform: linux and windows! 17 | // 18 | // `fn ctrl_c()` produces a `Future` of the actual stream-initialisation 19 | // the `flatten_stream()` convenience method lazily defers that 20 | // initialisation, allowing us to use it 'as if' it is already the 21 | // stream we want, reducing boilerplate Future-handling. 22 | let endless_stream = tokio_signal::ctrl_c().flatten_stream(); 23 | // don't keep going forever: convert the endless stream to a bounded one. 24 | let limited_stream = endless_stream.take(STOP_AFTER); 25 | 26 | // how many Ctrl+C have we received so far? 27 | let mut counter = 0; 28 | 29 | println!( 30 | "This program is now waiting for you to press Ctrl+C {0} times. 31 | * If running via `cargo run --example ctrl-c`, Ctrl+C also kills it, \ 32 | due to https://github.com/rust-lang-nursery/rustup.rs/issues/806 33 | * If running the binary directly, the Ctrl+C is properly trapped. 34 | Terminate by repeating Ctrl+C {0} times, or ahead of time by \ 35 | opening a second terminal and issuing `pkill -sigkil ctrl-c`", 36 | STOP_AFTER 37 | ); 38 | 39 | // Stream::for_each is a powerful primitive provided by the Futures crate. 40 | // It turns a Stream into a Future that completes after all stream-items 41 | // have been completed, or the first time the closure returns an error 42 | let future = limited_stream.for_each(|()| { 43 | // Note how we manipulate the counter without any fancy synchronisation. 44 | // The borrowchecker realises there can't be any conflicts, so the closure 45 | // can just capture it. 46 | counter += 1; 47 | println!( 48 | "Ctrl+C received {} times! {} more before exit", 49 | counter, 50 | STOP_AFTER - counter 51 | ); 52 | 53 | // return Ok-result to continue handling the stream 54 | Ok(()) 55 | }); 56 | 57 | // Up until now, we haven't really DONE anything, just prepared 58 | // now it's time to actually schedule, and thus execute, the stream 59 | // on our event loop 60 | core.run(future).unwrap(); 61 | 62 | println!("Stream ended, quiting the program."); 63 | } 64 | -------------------------------------------------------------------------------- /examples/multiple.rs: -------------------------------------------------------------------------------- 1 | //! A small example of how to listen for two signals at the same time 2 | 3 | extern crate futures; 4 | extern crate tokio_core; 5 | extern crate tokio_signal; 6 | 7 | use futures::{Future, Stream}; 8 | use tokio_core::reactor::Core; 9 | use tokio_signal::unix::{Signal, SIGINT, SIGTERM}; 10 | 11 | fn main() { 12 | let mut core = Core::new().unwrap(); 13 | 14 | // Create a stream for each of the signals we'd like to handle. 15 | let sigint = Signal::new(SIGINT).flatten_stream(); 16 | let sigterm = Signal::new(SIGTERM).flatten_stream(); 17 | 18 | // Use the `select` combinator to merge these two streams into one 19 | let stream = sigint.select(sigterm); 20 | 21 | // Wait for a signal to arrive 22 | println!("Waiting for SIGINT or SIGTERM"); 23 | println!( 24 | " TIP: use `pkill -sigint multiple` from a second terminal \ 25 | to send a SIGINT to all processes named 'multiple' \ 26 | (i.e. this binary)" 27 | ); 28 | let (item, _rest) = core.run(stream.into_future()).ok().unwrap(); 29 | 30 | // Figure out which signal we received 31 | let item = item.unwrap(); 32 | if item == SIGINT { 33 | println!("received SIGINT"); 34 | } else { 35 | assert_eq!(item, SIGTERM); 36 | println!("received SIGTERM"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/sighup-example.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate tokio_core; 3 | extern crate tokio_signal; 4 | 5 | use futures::{Future, Stream}; 6 | use tokio_core::reactor::Core; 7 | use tokio_signal::unix::{Signal, SIGHUP}; 8 | 9 | fn main() { 10 | // set up a Tokio event loop 11 | let mut core = Core::new().unwrap(); 12 | 13 | // on Unix, we can listen to whatever signal we want, in this case: SIGHUP 14 | let stream = Signal::new(SIGHUP).flatten_stream(); 15 | 16 | println!("Waiting for SIGHUPS (Ctrl+C to quit)"); 17 | println!( 18 | " TIP: use `pkill -sighup sighup-example` from a second terminal \ 19 | to send a SIGHUP to all processes named 'sighup-example' \ 20 | (i.e. this binary)" 21 | ); 22 | 23 | // for_each is a powerful primitive provided by the Futures crate 24 | // it turns a Stream into a Future that completes after all stream-items 25 | // have been completed. 26 | let future = stream.for_each(|the_signal| { 27 | println!( 28 | "*Got signal {:#x}* I should probably reload my config \ 29 | or something", 30 | the_signal 31 | ); 32 | Ok(()) 33 | }); 34 | 35 | // Up until now, we haven't really DONE anything, just prepared 36 | // now it's time to actually schedule, and thus execute, the stream 37 | // on our event loop, and loop forever 38 | core.run(future).unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous signal handling for Tokio 2 | //! 3 | //! This crate implements asynchronous signal handling for Tokio, an 4 | //! asynchronous I/O framework in Rust. The primary type exported from this 5 | //! crate, `unix::Signal`, allows listening for arbitrary signals on Unix 6 | //! platforms, receiving them in an asynchronous fashion. 7 | //! 8 | //! Note that signal handling is in general a very tricky topic and should be 9 | //! used with great care. This crate attempts to implement 'best practice' for 10 | //! signal handling, but it should be evaluated for your own applications' needs 11 | //! to see if it's suitable. 12 | //! 13 | //! The are some fundamental limitations of this crate documented on the 14 | //! `Signal` structure as well. 15 | //! 16 | //! # Examples 17 | //! 18 | //! Print out all ctrl-C notifications received 19 | //! 20 | //! ```rust,no_run 21 | //! extern crate futures; 22 | //! extern crate tokio_core; 23 | //! extern crate tokio_signal; 24 | //! 25 | //! use tokio_core::reactor::Core; 26 | //! use futures::{Future, Stream}; 27 | //! 28 | //! fn main() { 29 | //! let mut core = Core::new().unwrap(); 30 | //! 31 | //! // Create an infinite stream of "Ctrl+C" notifications. Each item received 32 | //! // on this stream may represent multiple ctrl-c signals. 33 | //! let ctrl_c = tokio_signal::ctrl_c().flatten_stream(); 34 | //! 35 | //! // Process each ctrl-c as it comes in 36 | //! let prog = ctrl_c.for_each(|()| { 37 | //! println!("ctrl-c received!"); 38 | //! Ok(()) 39 | //! }); 40 | //! 41 | //! core.run(prog).unwrap(); 42 | //! } 43 | //! ``` 44 | //! 45 | //! Wait for SIGHUP on Unix 46 | //! 47 | //! ```rust,no_run 48 | //! # extern crate futures; 49 | //! # extern crate tokio_core; 50 | //! # extern crate tokio_signal; 51 | //! # #[cfg(unix)] 52 | //! # mod foo { 53 | //! # 54 | //! extern crate futures; 55 | //! extern crate tokio_core; 56 | //! extern crate tokio_signal; 57 | //! 58 | //! use tokio_core::reactor::Core; 59 | //! use futures::{Future, Stream}; 60 | //! use tokio_signal::unix::{Signal, SIGHUP}; 61 | //! 62 | //! fn main() { 63 | //! let mut core = Core::new().unwrap(); 64 | //! 65 | //! // Like the previous example, this is an infinite stream of signals 66 | //! // being received, and signals may be coalesced while pending. 67 | //! let stream = Signal::new(SIGHUP).flatten_stream(); 68 | //! 69 | //! // Convert out stream into a future and block the program 70 | //! core.run(stream.into_future()).ok().unwrap(); 71 | //! } 72 | //! # } 73 | //! # fn main() {} 74 | //! ``` 75 | 76 | #![doc(html_root_url = "https://docs.rs/tokio-signal/0.2")] 77 | #![deny(missing_docs)] 78 | 79 | extern crate futures; 80 | extern crate mio; 81 | extern crate tokio_executor; 82 | extern crate tokio_io; 83 | extern crate tokio_reactor; 84 | 85 | use std::io; 86 | 87 | use futures::stream::Stream; 88 | use futures::{future, Future}; 89 | use tokio_reactor::Handle; 90 | 91 | pub mod unix; 92 | pub mod windows; 93 | 94 | /// A future whose error is `io::Error` 95 | pub type IoFuture = Box + Send>; 96 | /// A stream whose error is `io::Error` 97 | pub type IoStream = Box + Send>; 98 | 99 | /// Creates a stream which receives "ctrl-c" notifications sent to a process. 100 | /// 101 | /// In general signals are handled very differently across Unix and Windows, but 102 | /// this is somewhat cross platform in terms of how it can be handled. A ctrl-c 103 | /// event to a console process can be represented as a stream for both Windows 104 | /// and Unix. 105 | /// 106 | /// This function binds to the default event loop. Note that 107 | /// there are a number of caveats listening for signals, and you may wish to 108 | /// read up on the documentation in the `unix` or `windows` module to take a 109 | /// peek. 110 | pub fn ctrl_c() -> IoFuture> { 111 | ctrl_c_handle(&Handle::current()) 112 | } 113 | 114 | /// Creates a stream which receives "ctrl-c" notifications sent to a process. 115 | /// 116 | /// In general signals are handled very differently across Unix and Windows, but 117 | /// this is somewhat cross platform in terms of how it can be handled. A ctrl-c 118 | /// event to a console process can be represented as a stream for both Windows 119 | /// and Unix. 120 | /// 121 | /// This function receives a `Handle` to an event loop and returns a future 122 | /// which when resolves yields a stream receiving all signal events. Note that 123 | /// there are a number of caveats listening for signals, and you may wish to 124 | /// read up on the documentation in the `unix` or `windows` module to take a 125 | /// peek. 126 | pub fn ctrl_c_handle(handle: &Handle) -> IoFuture> { 127 | return ctrl_c_imp(handle); 128 | 129 | #[cfg(unix)] 130 | fn ctrl_c_imp(handle: &Handle) -> IoFuture> { 131 | let handle = handle.clone(); 132 | Box::new(future::lazy(move || { 133 | unix::Signal::with_handle(unix::libc::SIGINT, &handle) 134 | .map(|x| Box::new(x.map(|_| ())) as Box + Send>) 135 | })) 136 | } 137 | 138 | #[cfg(windows)] 139 | fn ctrl_c_imp(handle: &Handle) -> IoFuture> { 140 | let handle = handle.clone(); 141 | // Use lazy to ensure that `ctrl_c` gets called while on an event loop 142 | Box::new(future::lazy(move || { 143 | windows::Event::ctrl_c_handle(&handle) 144 | .map(|x| Box::new(x) as Box + Send>) 145 | })) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | //! Unix-specific types for signal handling. 2 | //! 3 | //! This module is only defined on Unix platforms and contains the primary 4 | //! `Signal` type for receiving notifications of signals. 5 | 6 | #![cfg(unix)] 7 | 8 | pub extern crate libc; 9 | extern crate mio; 10 | extern crate mio_uds; 11 | extern crate signal_hook; 12 | 13 | use std::io::{self, Error, ErrorKind}; 14 | use std::io::prelude::*; 15 | use std::sync::atomic::{AtomicBool, Ordering}; 16 | use std::sync::{Mutex, Once, ONCE_INIT}; 17 | 18 | use self::libc::c_int; 19 | use self::mio_uds::UnixStream; 20 | use futures::future; 21 | use futures::sync::mpsc::{channel, Receiver, Sender}; 22 | use futures::{Async, AsyncSink, Future}; 23 | use futures::{Poll, Sink, Stream}; 24 | use tokio_reactor::{Handle, PollEvented}; 25 | use tokio_io::IoFuture; 26 | 27 | pub use self::libc::{SIGUSR1, SIGUSR2, SIGINT, SIGTERM}; 28 | pub use self::libc::{SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTRAP}; 29 | 30 | /// BSD-specific definitions 31 | #[cfg(any( 32 | target_os = "dragonfly", 33 | target_os = "freebsd", 34 | target_os = "macos", 35 | target_os = "netbsd", 36 | target_os = "openbsd", 37 | ))] 38 | pub mod bsd { 39 | #[cfg(any(target_os = "dragonfly", target_os = "freebsd", 40 | target_os = "macos", target_os = "netbsd", 41 | target_os = "openbsd"))] 42 | pub use super::libc::SIGINFO; 43 | } 44 | 45 | // Number of different unix signals 46 | // (FreeBSD has 33) 47 | const SIGNUM: usize = 33; 48 | 49 | struct SignalInfo { 50 | pending: AtomicBool, 51 | // The ones interested in this signal 52 | recipients: Mutex>>>, 53 | 54 | init: Once, 55 | initialized: AtomicBool, 56 | } 57 | 58 | struct Globals { 59 | sender: UnixStream, 60 | receiver: UnixStream, 61 | signals: Vec, 62 | } 63 | 64 | impl Default for SignalInfo { 65 | fn default() -> SignalInfo { 66 | SignalInfo { 67 | pending: AtomicBool::new(false), 68 | init: ONCE_INIT, 69 | initialized: AtomicBool::new(false), 70 | recipients: Mutex::new(Vec::new()), 71 | } 72 | } 73 | } 74 | 75 | static mut GLOBALS: *mut Globals = 0 as *mut Globals; 76 | 77 | fn globals() -> &'static Globals { 78 | static INIT: Once = ONCE_INIT; 79 | 80 | unsafe { 81 | INIT.call_once(|| { 82 | let (receiver, sender) = UnixStream::pair().unwrap(); 83 | let globals = Globals { 84 | sender: sender, 85 | receiver: receiver, 86 | signals: (0..SIGNUM).map(|_| Default::default()).collect(), 87 | }; 88 | GLOBALS = Box::into_raw(Box::new(globals)); 89 | }); 90 | &*GLOBALS 91 | } 92 | } 93 | 94 | /// Our global signal handler for all signals registered by this module. 95 | /// 96 | /// The purpose of this signal handler is to primarily: 97 | /// 98 | /// 1. Flag that our specific signal was received (e.g. store an atomic flag) 99 | /// 2. Wake up driver tasks by writing a byte to a pipe 100 | /// 101 | /// Those two operations shoudl both be async-signal safe. 102 | fn action(slot: &SignalInfo, mut sender: &UnixStream) { 103 | slot.pending.store(true, Ordering::SeqCst); 104 | 105 | // Send a wakeup, ignore any errors (anything reasonably possible is 106 | // full pipe and then it will wake up anyway). 107 | drop(sender.write(&[1])); 108 | } 109 | 110 | /// Enable this module to receive signal notifications for the `signal` 111 | /// provided. 112 | /// 113 | /// This will register the signal handler if it hasn't already been registered, 114 | /// returning any error along the way if that fails. 115 | fn signal_enable(signal: c_int) -> io::Result<()> { 116 | if signal_hook::FORBIDDEN.contains(&signal) { 117 | return Err(Error::new(ErrorKind::Other, format!("Refusing to register signal {}", signal))); 118 | } 119 | 120 | let globals = globals(); 121 | let siginfo = match globals.signals.get(signal as usize) { 122 | Some(slot) => slot, 123 | None => return Err(io::Error::new(io::ErrorKind::Other, "signal too large")), 124 | }; 125 | let mut registered = Ok(()); 126 | siginfo.init.call_once(|| { 127 | registered = unsafe { 128 | signal_hook::register(signal, move || action(siginfo, &globals.sender)).map(|_| ()) 129 | }; 130 | if registered.is_ok() { 131 | siginfo.initialized.store(true, Ordering::Relaxed); 132 | } 133 | }); 134 | registered?; 135 | // If the call_once failed, it won't be retried on the next attempt to register the signal. In 136 | // such case it is not run, registered is still `Ok(())`, initialized is still false. 137 | if siginfo.initialized.load(Ordering::Relaxed) { 138 | Ok(()) 139 | } else { 140 | Err(Error::new(ErrorKind::Other, "Failed to register signal handler")) 141 | } 142 | } 143 | 144 | struct Driver { 145 | wakeup: PollEvented, 146 | } 147 | 148 | impl Future for Driver { 149 | type Item = (); 150 | type Error = (); 151 | 152 | fn poll(&mut self) -> Poll<(), ()> { 153 | // Drain the data from the pipe and maintain interest in getting more 154 | self.drain(); 155 | // Broadcast any signals which were received 156 | self.broadcast(); 157 | 158 | // This task just lives until the end of the event loop 159 | Ok(Async::NotReady) 160 | } 161 | } 162 | 163 | impl Driver { 164 | fn new(handle: &Handle) -> io::Result { 165 | // NB: We give each driver a "fresh" reciever file descriptor to avoid 166 | // the issues described in alexcrichton/tokio-process#42. 167 | // 168 | // In the past we would reuse the actual receiver file descriptor and 169 | // swallow any errors around double registration of the same descriptor. 170 | // I'm not sure if the second (failed) registration simply doesn't end up 171 | // receiving wake up notifications, or there could be some race condition 172 | // when consuming readiness events, but having distinct descriptors for 173 | // distinct PollEvented instances appears to mitigate this. 174 | // 175 | // Unfortunately we cannot just use a single global PollEvented instance 176 | // either, since we can't compare Handles or assume they will always 177 | // point to the exact same reactor. 178 | let stream = globals().receiver.try_clone()?; 179 | let wakeup = PollEvented::new_with_handle(stream, handle)?; 180 | 181 | Ok(Driver { 182 | wakeup: wakeup, 183 | }) 184 | } 185 | 186 | /// Drain all data in the global receiver, ensuring we'll get woken up when 187 | /// there is a write on the other end. 188 | /// 189 | /// We do *NOT* use the existence of any read bytes as evidence a sigal was 190 | /// received since the `pending` flags would have already been set if that 191 | /// was the case. See #38 for more info. 192 | fn drain(&mut self) { 193 | loop { 194 | match self.wakeup.read(&mut [0; 128]) { 195 | Ok(0) => panic!("EOF on self-pipe"), 196 | Ok(_) => {}, 197 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, 198 | Err(e) => panic!("Bad read on self-pipe: {}", e), 199 | } 200 | } 201 | } 202 | 203 | /// Go through all the signals and broadcast everything. 204 | /// 205 | /// Driver tasks wake up for *any* signal and simply process all globally 206 | /// registered signal streams, so each task is sort of cooperatively working 207 | /// for all the rest as well. 208 | fn broadcast(&self) { 209 | for (sig, slot) in globals().signals.iter().enumerate() { 210 | // Any signal of this kind arrived since we checked last? 211 | if !slot.pending.swap(false, Ordering::SeqCst) { 212 | continue; 213 | } 214 | 215 | let signum = sig as c_int; 216 | let mut recipients = slot.recipients.lock().unwrap(); 217 | 218 | // Notify all waiters on this signal that the signal has been 219 | // received. If we can't push a message into the queue then we don't 220 | // worry about it as everything is coalesced anyway. If the channel 221 | // has gone away then we can remove that slot. 222 | for i in (0..recipients.len()).rev() { 223 | // TODO: This thing probably generates unnecessary wakups of 224 | // this task when `NotReady` is received because we don't 225 | // actually want to get woken up to continue sending a 226 | // message. Let's optimise it later on though, as we know 227 | // this works. 228 | match recipients[i].start_send(signum) { 229 | Ok(AsyncSink::Ready) => {} 230 | Ok(AsyncSink::NotReady(_)) => {} 231 | Err(_) => { 232 | recipients.swap_remove(i); 233 | } 234 | } 235 | } 236 | } 237 | } 238 | } 239 | 240 | /// An implementation of `Stream` for receiving a particular type of signal. 241 | /// 242 | /// This structure implements the `Stream` trait and represents notifications 243 | /// of the current process receiving a particular signal. The signal being 244 | /// listened for is passed to `Signal::new`, and the same signal number is then 245 | /// yielded as each element for the stream. 246 | /// 247 | /// In general signal handling on Unix is a pretty tricky topic, and this 248 | /// structure is no exception! There are some important limitations to keep in 249 | /// mind when using `Signal` streams: 250 | /// 251 | /// * Signals handling in Unix already necessitates coalescing signals 252 | /// together sometimes. This `Signal` stream is also no exception here in 253 | /// that it will also coalesce signals. That is, even if the signal handler 254 | /// for this process runs multiple times, the `Signal` stream may only return 255 | /// one signal notification. Specifically, before `poll` is called, all 256 | /// signal notifications are coalesced into one item returned from `poll`. 257 | /// Once `poll` has been called, however, a further signal is guaranteed to 258 | /// be yielded as an item. 259 | /// 260 | /// Put another way, any element pulled off the returned stream corresponds to 261 | /// *at least one* signal, but possibly more. 262 | /// 263 | /// * Signal handling in general is relatively inefficient. Although some 264 | /// improvements are possible in this crate, it's recommended to not plan on 265 | /// having millions of signal channels open. 266 | /// 267 | /// * Currently the "driver task" to process incoming signals never exits. This 268 | /// driver task runs in the background of the event loop provided, and 269 | /// in general you shouldn't need to worry about it. 270 | /// 271 | /// If you've got any questions about this feel free to open an issue on the 272 | /// repo, though, as I'd love to chat about this! In other words, I'd love to 273 | /// alleviate some of these limitations if possible! 274 | pub struct Signal { 275 | driver: Driver, 276 | signal: c_int, 277 | // Used only as an identifier. We place the real sender into a Box, so it 278 | // stays on the same address forever. That gives us a unique pointer, so we 279 | // can use this to identify the sender in a Vec and delete it when we are 280 | // dropped. 281 | id: *const Sender, 282 | rx: Receiver, 283 | } 284 | 285 | // The raw pointer prevents the compiler from determining it as Send 286 | // automatically. But the only thing we use the raw pointer for is to identify 287 | // the correct Box to delete, not manipulate any data through that. 288 | unsafe impl Send for Signal {} 289 | 290 | impl Signal { 291 | /// Creates a new stream which will receive notifications when the current 292 | /// process receives the signal `signal`. 293 | /// 294 | /// This function will create a new stream which binds to the default event 295 | /// loop. This function returns a future which will 296 | /// then resolve to the signal stream, if successful. 297 | /// 298 | /// The `Signal` stream is an infinite stream which will receive 299 | /// notifications whenever a signal is received. More documentation can be 300 | /// found on `Signal` itself, but to reiterate: 301 | /// 302 | /// * Signals may be coalesced beyond what the kernel already does. 303 | /// * Once a signal handler is registered with the process the underlying 304 | /// libc signal handler is never unregistered. 305 | /// 306 | /// A `Signal` stream can be created for a particular signal number 307 | /// multiple times. When a signal is received then all the associated 308 | /// channels will receive the signal notification. 309 | /// 310 | /// # Errors 311 | /// 312 | /// * If the lower-level C functions fail for some reason. 313 | /// * If the previous initialization of this specific signal failed. 314 | /// * If the signal is one of 315 | /// [`signal_hook::FORBIDDEN`](https://docs.rs/signal-hook/*/signal_hook/fn.register.html#panics) 316 | pub fn new(signal: c_int) -> IoFuture { 317 | Signal::with_handle(signal, &Handle::current()) 318 | } 319 | 320 | /// Creates a new stream which will receive notifications when the current 321 | /// process receives the signal `signal`. 322 | /// 323 | /// This function will create a new stream which may be based on the 324 | /// event loop handle provided. This function returns a future which will 325 | /// then resolve to the signal stream, if successful. 326 | /// 327 | /// The `Signal` stream is an infinite stream which will receive 328 | /// notifications whenever a signal is received. More documentation can be 329 | /// found on `Signal` itself, but to reiterate: 330 | /// 331 | /// * Signals may be coalesced beyond what the kernel already does. 332 | /// * Once a signal handler is registered with the process the underlying 333 | /// libc signal handler is never unregistered. 334 | /// 335 | /// A `Signal` stream can be created for a particular signal number 336 | /// multiple times. When a signal is received then all the associated 337 | /// channels will receive the signal notification. 338 | pub fn with_handle(signal: c_int, handle: &Handle) -> IoFuture { 339 | let handle = handle.clone(); 340 | Box::new(future::lazy(move || { 341 | let result = (|| { 342 | // Turn the signal delivery on once we are ready for it 343 | try!(signal_enable(signal)); 344 | 345 | // Ensure there's a driver for our associated event loop processing 346 | // signals. 347 | let driver = try!(Driver::new(&handle)); 348 | 349 | // One wakeup in a queue is enough, no need for us to buffer up any 350 | // more. 351 | let (tx, rx) = channel(1); 352 | let tx = Box::new(tx); 353 | let id: *const _ = &*tx; 354 | let idx = signal as usize; 355 | globals().signals[idx].recipients.lock().unwrap().push(tx); 356 | Ok(Signal { 357 | driver: driver, 358 | rx: rx, 359 | id: id, 360 | signal: signal, 361 | }) 362 | })(); 363 | future::result(result) 364 | })) 365 | } 366 | } 367 | 368 | impl Stream for Signal { 369 | type Item = c_int; 370 | type Error = io::Error; 371 | 372 | fn poll(&mut self) -> Poll, io::Error> { 373 | self.driver.poll().unwrap(); 374 | // receivers don't generate errors 375 | self.rx.poll().map_err(|_| panic!()) 376 | } 377 | } 378 | 379 | impl Drop for Signal { 380 | fn drop(&mut self) { 381 | let idx = self.signal as usize; 382 | let mut list = globals().signals[idx].recipients.lock().unwrap(); 383 | list.retain(|sender| &**sender as *const _ != self.id); 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | //! Windows-specific types for signal handling. 2 | //! 3 | //! This module is only defined on Windows and contains the primary `Event` type 4 | //! for receiving notifications of events. These events are listened for via the 5 | //! `SetConsoleCtrlHandler` function which receives events of the type 6 | //! `CTRL_C_EVENT` and `CTRL_BREAK_EVENT` 7 | 8 | #![cfg(windows)] 9 | 10 | extern crate mio; 11 | extern crate winapi; 12 | 13 | use std::cell::RefCell; 14 | use std::io; 15 | use std::sync::atomic::{AtomicBool, Ordering}; 16 | use std::sync::{Once, ONCE_INIT}; 17 | 18 | use futures::future; 19 | use futures::stream::Fuse; 20 | use futures::sync::mpsc; 21 | use futures::sync::oneshot; 22 | use futures::{Async, Future, IntoFuture, Poll, Stream}; 23 | use tokio_reactor::{Handle, PollEvented}; 24 | use mio::Ready; 25 | use self::winapi::shared::minwindef::*; 26 | use self::winapi::um::wincon::*; 27 | 28 | use IoFuture; 29 | 30 | extern "system" { 31 | fn SetConsoleCtrlHandler(HandlerRoutine: usize, Add: BOOL) -> BOOL; 32 | } 33 | 34 | static INIT: Once = ONCE_INIT; 35 | static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _; 36 | 37 | /// Stream of events discovered via `SetConsoleCtrlHandler`. 38 | /// 39 | /// This structure can be used to listen for events of the type `CTRL_C_EVENT` 40 | /// and `CTRL_BREAK_EVENT`. The `Stream` trait is implemented for this struct 41 | /// and will resolve for each notification received by the process. Note that 42 | /// there are few limitations with this as well: 43 | /// 44 | /// * A notification to this process notifies *all* `Event` streams for that 45 | /// event type. 46 | /// * Notifications to an `Event` stream **are coalesced** if they aren't 47 | /// processed quickly enough. This means that if two notifications are 48 | /// received back-to-back, then the stream may only receive one item about the 49 | /// two notifications. 50 | pub struct Event { 51 | reg: PollEvented, 52 | _finished: oneshot::Sender<()>, 53 | } 54 | 55 | struct GlobalState { 56 | ready: mio::SetReadiness, 57 | tx: mpsc::UnboundedSender, 58 | ctrl_c: GlobalEventState, 59 | ctrl_break: GlobalEventState, 60 | } 61 | 62 | struct GlobalEventState { 63 | ready: AtomicBool, 64 | } 65 | 66 | enum Message { 67 | NewEvent(DWORD, oneshot::Sender>), 68 | } 69 | 70 | struct DriverTask { 71 | handle: Handle, 72 | reg: PollEvented, 73 | rx: Fuse>, 74 | ctrl_c: EventState, 75 | ctrl_break: EventState, 76 | } 77 | 78 | struct EventState { 79 | tasks: Vec<(RefCell>, mio::SetReadiness)>, 80 | } 81 | 82 | impl Event { 83 | /// Creates a new stream listening for the `CTRL_C_EVENT` events. 84 | /// 85 | /// This function will register a handler via `SetConsoleCtrlHandler` and 86 | /// deliver notifications to the returned stream. 87 | pub fn ctrl_c() -> IoFuture { 88 | Event::ctrl_c_handle(&Handle::current()) 89 | } 90 | 91 | /// Creates a new stream listening for the `CTRL_C_EVENT` events. 92 | /// 93 | /// This function will register a handler via `SetConsoleCtrlHandler` and 94 | /// deliver notifications to the returned stream. 95 | pub fn ctrl_c_handle(handle: &Handle) -> IoFuture { 96 | Event::new(CTRL_C_EVENT, handle) 97 | } 98 | 99 | /// Creates a new stream listening for the `CTRL_BREAK_EVENT` events. 100 | /// 101 | /// This function will register a handler via `SetConsoleCtrlHandler` and 102 | /// deliver notifications to the returned stream. 103 | pub fn ctrl_break() -> IoFuture { 104 | Event::ctrl_break_handle(&Handle::current()) 105 | } 106 | 107 | /// Creates a new stream listening for the `CTRL_BREAK_EVENT` events. 108 | /// 109 | /// This function will register a handler via `SetConsoleCtrlHandler` and 110 | /// deliver notifications to the returned stream. 111 | pub fn ctrl_break_handle(handle: &Handle) -> IoFuture { 112 | Event::new(CTRL_BREAK_EVENT, handle) 113 | } 114 | 115 | fn new(signum: DWORD, handle: &Handle) -> IoFuture { 116 | let mut init = None; 117 | INIT.call_once(|| { 118 | init = Some(global_init(handle)); 119 | }); 120 | let new_signal = future::lazy(move || { 121 | let (tx, rx) = oneshot::channel(); 122 | let msg = Message::NewEvent(signum, tx); 123 | let res = unsafe { (*GLOBAL_STATE).tx.clone().unbounded_send(msg) }; 124 | res.expect( 125 | "failed to request a new signal stream, did the \ 126 | first event loop go away?", 127 | ); 128 | rx.then(|r| r.unwrap()) 129 | }); 130 | match init { 131 | Some(init) => Box::new(init.into_future().and_then(|()| new_signal)), 132 | None => Box::new(new_signal), 133 | } 134 | } 135 | } 136 | 137 | impl Stream for Event { 138 | type Item = (); 139 | type Error = io::Error; 140 | 141 | fn poll(&mut self) -> Poll, io::Error> { 142 | if !self.reg.poll_read_ready(Ready::readable())?.is_ready() { 143 | return Ok(Async::NotReady); 144 | } 145 | self.reg.clear_read_ready(Ready::readable())?; 146 | self.reg 147 | .get_ref() 148 | .inner 149 | .borrow() 150 | .as_ref() 151 | .unwrap() 152 | .1 153 | .set_readiness(mio::Ready::empty()) 154 | .expect("failed to set readiness"); 155 | Ok(Async::Ready(Some(()))) 156 | } 157 | } 158 | 159 | fn global_init(handle: &Handle) -> io::Result<()> { 160 | let (tx, rx) = mpsc::unbounded(); 161 | let reg = MyRegistration { 162 | inner: RefCell::new(None), 163 | }; 164 | let reg = try!(PollEvented::new_with_handle(reg, handle)); 165 | let ready = reg.get_ref().inner.borrow().as_ref().unwrap().1.clone(); 166 | unsafe { 167 | let state = Box::new(GlobalState { 168 | ready: ready, 169 | ctrl_c: GlobalEventState { 170 | ready: AtomicBool::new(false), 171 | }, 172 | ctrl_break: GlobalEventState { 173 | ready: AtomicBool::new(false), 174 | }, 175 | tx: tx, 176 | }); 177 | GLOBAL_STATE = Box::into_raw(state); 178 | 179 | let rc = SetConsoleCtrlHandler(handler as usize, TRUE); 180 | if rc == 0 { 181 | Box::from_raw(GLOBAL_STATE); 182 | GLOBAL_STATE = 0 as *mut _; 183 | return Err(io::Error::last_os_error()); 184 | } 185 | 186 | ::tokio_executor::spawn(Box::new(DriverTask { 187 | handle: handle.clone(), 188 | rx: rx.fuse(), 189 | reg: reg, 190 | ctrl_c: EventState { tasks: Vec::new() }, 191 | ctrl_break: EventState { tasks: Vec::new() }, 192 | })); 193 | 194 | Ok(()) 195 | } 196 | } 197 | 198 | impl Future for DriverTask { 199 | type Item = (); 200 | type Error = (); 201 | 202 | fn poll(&mut self) -> Poll<(), ()> { 203 | self.check_event_drops(); 204 | self.check_messages(); 205 | self.check_events().unwrap(); 206 | 207 | // TODO: when to finish this task? 208 | Ok(Async::NotReady) 209 | } 210 | } 211 | 212 | impl DriverTask { 213 | fn check_event_drops(&mut self) { 214 | self.ctrl_c 215 | .tasks 216 | .retain(|task| !task.0.borrow_mut().poll().is_err()); 217 | self.ctrl_break 218 | .tasks 219 | .retain(|task| !task.0.borrow_mut().poll().is_err()); 220 | } 221 | 222 | fn check_messages(&mut self) { 223 | loop { 224 | // Acquire the next message 225 | let message = match self.rx.poll().unwrap() { 226 | Async::Ready(Some(e)) => e, 227 | Async::Ready(None) | Async::NotReady => break, 228 | }; 229 | let (sig, complete) = match message { 230 | Message::NewEvent(sig, complete) => (sig, complete), 231 | }; 232 | 233 | let event = if sig == CTRL_C_EVENT { 234 | &mut self.ctrl_c 235 | } else { 236 | &mut self.ctrl_break 237 | }; 238 | 239 | // Acquire the (registration, set_readiness) pair by... assuming 240 | // we're on the event loop (true because of the spawn above). 241 | let reg = MyRegistration { 242 | inner: RefCell::new(None), 243 | }; 244 | let reg = match PollEvented::new_with_handle(reg, &self.handle) { 245 | Ok(reg) => reg, 246 | Err(e) => { 247 | drop(complete.send(Err(e))); 248 | continue; 249 | } 250 | }; 251 | 252 | // Create the `Event` to pass back and then also keep a handle to 253 | // the `SetReadiness` for ourselves internally. 254 | let (tx, rx) = oneshot::channel(); 255 | let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone(); 256 | drop(complete.send(Ok(Event { 257 | reg: reg, 258 | _finished: tx, 259 | }))); 260 | event.tasks.push((RefCell::new(rx), ready)); 261 | } 262 | } 263 | 264 | fn check_events(&mut self) -> io::Result<()> { 265 | if self.reg.poll_read_ready(Ready::readable())?.is_not_ready() { 266 | return Ok(()); 267 | } 268 | self.reg.clear_read_ready(Ready::readable())?; 269 | self.reg 270 | .get_ref() 271 | .inner 272 | .borrow() 273 | .as_ref() 274 | .unwrap() 275 | .1 276 | .set_readiness(mio::Ready::empty()) 277 | .unwrap(); 278 | 279 | if unsafe { (*GLOBAL_STATE).ctrl_c.ready.swap(false, Ordering::SeqCst) } { 280 | for task in self.ctrl_c.tasks.iter() { 281 | task.1.set_readiness(mio::Ready::readable()).unwrap(); 282 | } 283 | } 284 | if unsafe { 285 | (*GLOBAL_STATE) 286 | .ctrl_break 287 | .ready 288 | .swap(false, Ordering::SeqCst) 289 | } { 290 | for task in self.ctrl_break.tasks.iter() { 291 | task.1.set_readiness(mio::Ready::readable()).unwrap(); 292 | } 293 | } 294 | Ok(()) 295 | } 296 | } 297 | 298 | unsafe extern "system" fn handler(ty: DWORD) -> BOOL { 299 | let event = match ty { 300 | CTRL_C_EVENT => &(*GLOBAL_STATE).ctrl_c, 301 | CTRL_BREAK_EVENT => &(*GLOBAL_STATE).ctrl_break, 302 | _ => return FALSE, 303 | }; 304 | if event.ready.swap(true, Ordering::SeqCst) { 305 | FALSE 306 | } else { 307 | drop((*GLOBAL_STATE).ready.set_readiness(mio::Ready::readable())); 308 | // TODO: this will report that we handled a CTRL_BREAK_EVENT when in 309 | // fact we may not have any streams actually created for that 310 | // event. 311 | TRUE 312 | } 313 | } 314 | 315 | struct MyRegistration { 316 | inner: RefCell>, 317 | } 318 | 319 | impl mio::Evented for MyRegistration { 320 | fn register( 321 | &self, 322 | poll: &mio::Poll, 323 | token: mio::Token, 324 | events: mio::Ready, 325 | opts: mio::PollOpt, 326 | ) -> io::Result<()> { 327 | let reg = mio::Registration::new2(); 328 | reg.0.register(poll, token, events, opts)?; 329 | *self.inner.borrow_mut() = Some(reg); 330 | Ok(()) 331 | } 332 | 333 | fn reregister( 334 | &self, 335 | _poll: &mio::Poll, 336 | _token: mio::Token, 337 | _events: mio::Ready, 338 | _opts: mio::PollOpt, 339 | ) -> io::Result<()> { 340 | Ok(()) 341 | } 342 | 343 | fn deregister(&self, _poll: &mio::Poll) -> io::Result<()> { 344 | Ok(()) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /tests/drop_multi_loop.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | const TEST_SIGNAL: libc::c_int = libc::SIGUSR1; 9 | 10 | #[test] 11 | fn dropping_loops_does_not_cause_starvation() { 12 | let (mut rt, signal) = { 13 | let mut first_rt = CurrentThreadRuntime::new() 14 | .expect("failed to init first runtime"); 15 | 16 | let first_signal = run_with_timeout(&mut first_rt, Signal::new(TEST_SIGNAL)) 17 | .expect("failed to register first signal"); 18 | 19 | let mut second_rt = CurrentThreadRuntime::new() 20 | .expect("failed to init second runtime"); 21 | 22 | let second_signal = run_with_timeout(&mut second_rt, Signal::new(TEST_SIGNAL)) 23 | .expect("failed to register second signal"); 24 | 25 | drop(first_rt); 26 | drop(first_signal); 27 | 28 | (second_rt, second_signal) 29 | }; 30 | 31 | send_signal(TEST_SIGNAL); 32 | 33 | let signal_future = signal.into_future() 34 | .map_err(|(e, _)| e); 35 | 36 | run_with_timeout(&mut rt, signal_future) 37 | .expect("failed to get signal"); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tests/drop_then_get_a_signal.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | #[test] 9 | fn drop_then_get_a_signal() { 10 | let mut lp = Core::new().unwrap(); 11 | let handle = lp.handle(); 12 | 13 | let signal = run_core_with_timeout(&mut lp, Signal::with_handle( 14 | libc::SIGUSR1, 15 | &handle.new_tokio_handle(), 16 | )).expect("failed to create first signal"); 17 | drop(signal); 18 | 19 | send_signal(libc::SIGUSR1); 20 | let signal = lp.run(Signal::with_handle(libc::SIGUSR1, &handle.new_tokio_handle())) 21 | .expect("failed to create signal") 22 | .into_future() 23 | .map(|_| ()) 24 | .map_err(|(e, _)| panic!("{}", e)); 25 | 26 | run_core_with_timeout(&mut lp, signal) 27 | .expect("failed to get signal"); 28 | } 29 | -------------------------------------------------------------------------------- /tests/dropping_does_not_deregister_other_instances.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | const TEST_SIGNAL: libc::c_int = libc::SIGUSR1; 9 | 10 | #[test] 11 | fn dropping_signal_does_not_deregister_any_other_instances() { 12 | // NB: Deadline requires a timer registration which is provided by 13 | // tokio's `current_thread::Runtime`, but isn't available by just using 14 | // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. 15 | let mut rt = CurrentThreadRuntime::new() 16 | .expect("failed to init runtime"); 17 | 18 | // NB: Testing for issue #38: signals should not starve based on ordering 19 | let first_duplicate_signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) 20 | .expect("failed to register first duplicate signal"); 21 | let signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) 22 | .expect("failed to register signal"); 23 | let second_duplicate_signal = run_with_timeout(&mut rt, Signal::new(TEST_SIGNAL)) 24 | .expect("failed to register second duplicate signal"); 25 | 26 | drop(first_duplicate_signal); 27 | drop(second_duplicate_signal); 28 | 29 | send_signal(TEST_SIGNAL); 30 | run_with_timeout(&mut rt, signal.into_future().map_err(|(e, _)| e)) 31 | .expect("failed to get signal"); 32 | } 33 | -------------------------------------------------------------------------------- /tests/multi_loop.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | use std::sync::mpsc::channel; 6 | use std::thread; 7 | 8 | pub mod support; 9 | use support::*; 10 | 11 | #[test] 12 | fn multi_loop() { 13 | // An "ordinary" (non-future) channel 14 | let (sender, receiver) = channel(); 15 | // Run multiple times, to make sure there are no race conditions 16 | for _ in 0..10 { 17 | // Run multiple event loops, each one in its own thread 18 | let threads: Vec<_> = (0..4) 19 | .map(|_| { 20 | let sender = sender.clone(); 21 | thread::spawn(move || { 22 | let mut lp = Core::new().unwrap(); 23 | let signal = lp.run(Signal::new(libc::SIGHUP)).unwrap(); 24 | sender.send(()).unwrap(); 25 | run_core_with_timeout(&mut lp, signal.into_future()).ok().unwrap(); 26 | }) 27 | }) 28 | .collect(); 29 | // Wait for them to declare they're ready 30 | for &_ in threads.iter() { 31 | receiver.recv().unwrap(); 32 | } 33 | // Send a signal 34 | send_signal(libc::SIGHUP); 35 | // Make sure the threads terminated correctly 36 | for t in threads { 37 | t.join().unwrap(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/notify_both.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | #[test] 9 | fn notify_both() { 10 | let mut lp = Core::new().unwrap(); 11 | let handle = lp.handle(); 12 | 13 | let signal1 = run_core_with_timeout(&mut lp, Signal::with_handle( 14 | libc::SIGUSR2, 15 | &handle.new_tokio_handle(), 16 | )).expect("failed to create signal1"); 17 | 18 | let signal2 = run_core_with_timeout(&mut lp, Signal::with_handle( 19 | libc::SIGUSR2, 20 | &handle.new_tokio_handle(), 21 | )).expect("failed to create signal2"); 22 | 23 | send_signal(libc::SIGUSR2); 24 | run_core_with_timeout(&mut lp, signal1.into_future().join(signal2.into_future())) 25 | .ok() 26 | .expect("failed to create signal2"); 27 | } 28 | -------------------------------------------------------------------------------- /tests/signal.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | #[test] 9 | fn tokio_simple() { 10 | let signal_future = Signal::new(libc::SIGUSR1) 11 | .and_then(|signal| { 12 | send_signal(libc::SIGUSR1); 13 | signal.into_future().map(|_| ()).map_err(|(err, _)| err) 14 | }); 15 | 16 | let mut rt = CurrentThreadRuntime::new() 17 | .expect("failed to init runtime"); 18 | run_with_timeout(&mut rt, signal_future) 19 | .expect("failed"); 20 | } 21 | -------------------------------------------------------------------------------- /tests/simple.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | #[test] 9 | fn simple() { 10 | let mut lp = Core::new().unwrap(); 11 | 12 | let signal = run_core_with_timeout(&mut lp, Signal::new(libc::SIGUSR1)) 13 | .expect("failed to create signal"); 14 | 15 | send_signal(libc::SIGUSR1); 16 | 17 | run_core_with_timeout(&mut lp, signal.into_future()) 18 | .ok() 19 | .expect("failed to get signal"); 20 | } 21 | -------------------------------------------------------------------------------- /tests/support.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate futures; 3 | extern crate tokio; 4 | extern crate tokio_core; 5 | extern crate tokio_signal; 6 | 7 | use self::libc::{c_int, getpid, kill}; 8 | use std::time::{Duration, Instant}; 9 | use self::tokio::timer::Deadline; 10 | use self::tokio_core::reactor::Timeout; 11 | 12 | pub use self::futures::{Future, Stream}; 13 | pub use self::tokio_core::reactor::Core; 14 | pub use self::tokio::runtime::current_thread::Runtime as CurrentThreadRuntime; 15 | pub use self::tokio_signal::unix::Signal; 16 | 17 | pub fn run_core_with_timeout(lp: &mut Core, future: F) -> Result 18 | where F: Future 19 | { 20 | let timeout = Timeout::new(Duration::from_secs(1), &lp.handle()) 21 | .expect("failed to register timeout") 22 | .map(|()| panic!("timeout exceeded")) 23 | .map_err(|e| panic!("timeout error: {}", e)); 24 | 25 | lp.run(future.select(timeout)) 26 | .map(|(r, _)| r) 27 | .map_err(|(e, _)| e) 28 | } 29 | 30 | pub fn run_with_timeout(rt: &mut CurrentThreadRuntime, future: F) -> Result 31 | where F: Future 32 | { 33 | let deadline = Deadline::new(future, Instant::now() + Duration::from_secs(1)) 34 | .map_err(|e| if e.is_timer() { 35 | panic!("failed to register timer"); 36 | } else if e.is_elapsed() { 37 | panic!("timed out") 38 | } else { 39 | e.into_inner().expect("missing inner error") 40 | }); 41 | 42 | rt.block_on(deadline) 43 | } 44 | 45 | #[cfg(unix)] 46 | pub fn send_signal(signal: c_int) { 47 | unsafe { 48 | assert_eq!(kill(getpid(), signal), 0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/twice.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | extern crate libc; 4 | 5 | pub mod support; 6 | use support::*; 7 | 8 | #[test] 9 | fn twice() { 10 | let mut lp = Core::new().unwrap(); 11 | let signal = run_core_with_timeout(&mut lp, Signal::new(libc::SIGUSR1)).unwrap(); 12 | 13 | send_signal(libc::SIGUSR1); 14 | let (num, signal) = run_core_with_timeout(&mut lp, signal.into_future()).ok().unwrap(); 15 | assert_eq!(num, Some(libc::SIGUSR1)); 16 | 17 | send_signal(libc::SIGUSR1); 18 | run_core_with_timeout(&mut lp, signal.into_future()).ok().unwrap(); 19 | } 20 | --------------------------------------------------------------------------------