├── .github └── workflows │ ├── clippy.yml │ ├── rustfmt.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── examples ├── args.rs ├── bad-serialization.rs ├── custom-serialization.rs ├── join.rs ├── kill.rs ├── macro.rs ├── panic.rs ├── pool.rs ├── simple.rs ├── stdout.rs └── timeout.rs ├── src ├── core.rs ├── error.rs ├── json.rs ├── lib.rs ├── macros.rs ├── panic.rs ├── pool.rs ├── proc.rs ├── serde.rs └── testsupport.rs └── tests ├── test_basic.rs ├── test_macros.rs └── test_pool.rs /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | profile: minimal 15 | components: clippy, rustfmt 16 | override: true 17 | - name: Run clippy 18 | run: make lint 19 | -------------------------------------------------------------------------------- /.github/workflows/rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | profile: minimal 16 | components: clippy, rustfmt 17 | override: true 18 | - name: Run rustfmt 19 | run: make style 20 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | profile: minimal 16 | override: true 17 | - name: Test 18 | run: make test 19 | 20 | build-stable: 21 | name: Test on 1.70.0 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v1 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: 1.70.0 29 | profile: minimal 30 | override: true 31 | - name: Test 32 | run: make test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.1 4 | 5 | * Removed winapi dependency 6 | * Upgraded backtrace to newer minimal version 7 | * Bump MSRV to 1.70 8 | * Update ipc-channel dependency 9 | 10 | ## 1.0.0 11 | 12 | * Changes the `join` and `join_timeout` API to no longer consume the handle. 13 | 14 | ## 0.10.3 15 | 16 | * Update ipc-channel to 0.16.1. 17 | 18 | ## 0.10.2 19 | 20 | * Update ipc-channel to 0.16. 21 | 22 | ## 0.10.1 23 | 24 | * Fixed some clippy warnings. 25 | * Fixed a test that timed out in CI. 26 | 27 | ## 0.10.0 28 | 29 | * Upgraded ctor 30 | * Name the pool monitoring threads 31 | 32 | ## 0.9.0 33 | 34 | * Removed experimental async support. 35 | 36 | ## 0.8.4 37 | 38 | * Added pool support for macros. 39 | 40 | ## 0.8.3 41 | 42 | * Resolved a deadlock when large args are sent over the 43 | IPC boundary. 44 | ([#31](https://github.com/mitsuhiko/procspawn/pull/31)) 45 | 46 | ## 0.8.2 47 | 48 | * Detect path to test module in case enable test mode is in a 49 | submodule. 50 | ([#28](https://github.com/mitsuhiko/procspawn/pull/28)) 51 | * Fixed zombies being left behind. 52 | ([#27](https://github.com/mitsuhiko/procspawn/pull/27)) 53 | 54 | ## 0.8.1 55 | 56 | * Fixed test support not working correctly for other crates. 57 | ([#26](https://github.com/mitsuhiko/procspawn/pull/26)) 58 | 59 | ## 0.8.0 60 | 61 | * Added support for `spawn!` and `spawn_async!` macros 62 | ([#25](https://github.com/mitsuhiko/procspawn/pull/25)) 63 | 64 | ## 0.7.0 65 | 66 | * Added basic support for using this crate with async/await 67 | ([#24](https://github.com/mitsuhiko/procspawn/pull/24)) 68 | 69 | ## 0.6.0 70 | 71 | * Calls from the test support not have stdout disabled by default 72 | ([#22](https://github.com/mitsuhiko/procspawn/pull/22)) 73 | * Spurious "Unknown Mach error: 44e" on the remote side is now 74 | silenced on macOS when the caller disconnects. 75 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "procspawn" 3 | version = "1.0.1" 4 | authors = [ 5 | "Armin Ronacher ", 6 | "Manish Goregaokar ", 7 | ] 8 | edition = "2018" 9 | license = "MIT OR Apache-2.0" 10 | description = "thread::spawn just with processes" 11 | homepage = "https://github.com/mitsuhiko/procspawn" 12 | repository = "https://github.com/mitsuhiko/procspawn" 13 | keywords = ["proc", "spawn", "subprocess"] 14 | readme = "README.md" 15 | autoexamples = true 16 | autotests = true 17 | rust-version = "1.70.0" 18 | 19 | [package.metadata.docs.rs] 20 | all-features = true 21 | 22 | [features] 23 | default = ["backtrace", "safe-shared-libraries"] 24 | test-support = ["small_ctor"] 25 | json = ["serde_json"] 26 | safe-shared-libraries = ["findshlibs"] 27 | 28 | [dependencies] 29 | ipc-channel = "0.18.2" 30 | serde = { version = "1.0.104", features = ["derive"] } 31 | backtrace = { version = "0.3.73", optional = true, features = ["serde"] } 32 | libc = "0.2.66" 33 | serde_json = { version = "1.0.47", optional = true } 34 | findshlibs = { version = "0.10.2", optional = true } 35 | small_ctor = { version = "0.1.2", optional = true } 36 | 37 | [target."cfg(windows)".dependencies] 38 | windows-sys = { version = "0.48.0", features = ["Win32_System_Threading"] } 39 | 40 | [[example]] 41 | name = "panic" 42 | required-features = ["backtrace"] 43 | 44 | [[example]] 45 | name = "bad-serialization" 46 | required-features = ["backtrace", "json"] 47 | 48 | [[test]] 49 | name = "test_basic" 50 | required-features = ["test-support"] 51 | 52 | [[test]] 53 | name = "test_pool" 54 | required-features = ["test-support"] 55 | 56 | [[test]] 57 | name = "test_macros" 58 | required-features = ["test-support"] 59 | -------------------------------------------------------------------------------- /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 | MIT License 2 | 3 | Copyright (c) 2020 Armin Ronacher 4 | Copyright (c) 2019 Manish Goregaokar 5 | 6 | Permission is hereby granted, free of charge, to any 7 | person obtaining a copy of this software and associated 8 | documentation files (the "Software"), to deal in the 9 | Software without restriction, including without 10 | limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software 13 | is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice 17 | shall be included in all copies or substantial portions 18 | of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 21 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 22 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 23 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 24 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 27 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 | DEALINGS IN THE SOFTWARE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test style lint 2 | .PHONY: all 3 | 4 | clean: 5 | @cargo clean 6 | .PHONY: clean 7 | 8 | build: 9 | @cargo build 10 | .PHONY: build 11 | 12 | doc: 13 | @cargo doc --all-features 14 | .PHONY: doc 15 | 16 | style: 17 | @rustup component add rustfmt --toolchain stable 2> /dev/null 18 | cargo fmt -- --check 19 | .PHONY: style 20 | 21 | format: 22 | @rustup component add rustfmt --toolchain stable 2> /dev/null 23 | cargo fmt 24 | .PHONY: format 25 | 26 | lint: 27 | @rustup component add clippy --toolchain stable 2> /dev/null 28 | cargo clippy --all-features --tests --all --examples -- -D clippy::all 29 | .PHONY: lint 30 | 31 | test: testall 32 | .PHONY: test 33 | 34 | checkall: 35 | cargo check --all-features 36 | cargo check --no-default-features 37 | .PHONY: checkall 38 | 39 | testall: 40 | cargo check --no-default-features --all 41 | cargo check --no-default-features --features test-support --all 42 | cargo test --all-features --all -- 43 | cargo run --all-features --example join 44 | cargo run --all-features --example kill 45 | cargo run --all-features --example panic 46 | cargo run --all-features --example pool 47 | cargo run --all-features --example simple 48 | cargo run --all-features --example stdout 49 | cargo run --all-features --example timeout 50 | cargo run --all-features --example macro 51 | cargo run --all-features --example bad-serialization 52 | cargo run --all-features --example custom-serialization 53 | cargo run --all-features --example args -- 1 2 3 54 | .PHONY: testall 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # procspawn 2 | 3 | [![Build Status](https://github.com/mitsuhiko/procspawn/workflows/Tests/badge.svg?branch=master)](https://github.com/mitsuhiko/procspawn/actions?query=workflow%3ATests) 4 | [![Crates.io](https://img.shields.io/crates/d/procspawn.svg)](https://crates.io/crates/procspawn) 5 | [![Documentation](https://docs.rs/procspawn/badge.svg)](https://docs.rs/procspawn) 6 | [![rustc 1.65.0](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://img.shields.io/badge/rust-1.70%2B-orange.svg) 7 | 8 | This crate provides the ability to spawn processes with a function similar 9 | to `thread::spawn`. Instead of closures it passes [`serde`](https://serde.rs/) 10 | serializable objects. The return value from the spawned closure also must be 11 | serializable and can then be retrieved from the returned join handle. 12 | 13 | If the spawned function causes a panic it will also be serialized across 14 | the process boundaries. 15 | 16 | ## Example 17 | 18 | Step 1: invoke `procspawn::init` at a point early in your program (somewhere at 19 | the beginning of the main function). Whatever happens before that point also 20 | happens in your spawned functions. 21 | 22 | ```rust 23 | procspawn::init(); 24 | ``` 25 | 26 | Step 2: now you can start spawning functions: 27 | 28 | ```rust 29 | let data = vec![1, 2, 3, 4]; 30 | let handle = procspawn::spawn(data, |data| { 31 | println!("Received data {:?}", &data); 32 | data.into_iter().sum::() 33 | }); 34 | let result = handle.join().unwrap(); 35 | ``` 36 | 37 | ## License and Links 38 | 39 | - [Documentation](https://docs.rs/procspawn/) 40 | - [Issue Tracker](https://github.com/mitsuhiko/procspawn/issues) 41 | - [Examples](https://github.com/mitsuhiko/procspawn/tree/master/examples) 42 | - License: [Apache-2.0](https://github.com/mitsuhiko/procspawn/blob/master/LICENSE-APACHE) 43 | -------------------------------------------------------------------------------- /examples/args.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | fn main() { 4 | procspawn::init(); 5 | 6 | let handle = spawn((), |()| std::env::args().collect::>()); 7 | 8 | let args = handle.join().unwrap(); 9 | 10 | println!("args in subprocess: {:?}", args); 11 | } 12 | -------------------------------------------------------------------------------- /examples/bad-serialization.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, serde::Json, spawn}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Serialize, Deserialize, Debug)] 5 | struct InnerStruct { 6 | value: u64, 7 | } 8 | 9 | #[derive(Serialize, Deserialize, Debug)] 10 | struct BadStruct { 11 | #[serde(flatten)] 12 | inner: InnerStruct, 13 | } 14 | 15 | fn main() { 16 | procspawn::init(); 17 | 18 | // json works: 19 | println!("JSON lets you send a flattened object through:"); 20 | let handle = spawn((), |()| { 21 | Json(BadStruct { 22 | inner: InnerStruct { value: 42 }, 23 | }) 24 | }); 25 | println!("result with JSON: {:?}", handle.join()); 26 | 27 | println!("raw bincode currently does not permit this:"); 28 | // bincode fails: 29 | let handle = spawn((), |()| BadStruct { 30 | inner: InnerStruct { value: 42 }, 31 | }); 32 | println!("result with bincode: {:?}", handle.join()); 33 | } 34 | -------------------------------------------------------------------------------- /examples/custom-serialization.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fs; 3 | use std::io; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use serde::{de::Deserializer, de::Error, ser::Serializer}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Serialize, Deserialize)] 10 | struct MyBytesHelper<'a> { 11 | path: Cow<'a, Path>, 12 | bytes: Cow<'a, [u8]>, 13 | } 14 | 15 | #[derive(Clone, PartialEq, Debug)] 16 | struct MyBytes { 17 | path: PathBuf, 18 | bytes: Vec, 19 | } 20 | 21 | impl MyBytes { 22 | pub fn open>(p: P) -> io::Result { 23 | println!("opening file in {}", std::process::id()); 24 | let path = p.as_ref().to_path_buf(); 25 | Ok(MyBytes { 26 | bytes: fs::read(&path)?, 27 | path, 28 | }) 29 | } 30 | } 31 | 32 | impl Serialize for MyBytes { 33 | fn serialize(&self, serializer: S) -> Result 34 | where 35 | S: Serializer, 36 | { 37 | if procspawn::serde::in_ipc_mode() { 38 | println!("serialize in ipc mode"); 39 | self.path.serialize(serializer) 40 | } else { 41 | println!("serialize in normal mode"); 42 | MyBytesHelper { 43 | path: Cow::Borrowed(&self.path), 44 | bytes: Cow::Borrowed(&self.bytes), 45 | } 46 | .serialize(serializer) 47 | } 48 | } 49 | } 50 | 51 | impl<'de> Deserialize<'de> for MyBytes { 52 | fn deserialize(deserializer: D) -> Result 53 | where 54 | D: Deserializer<'de>, 55 | { 56 | if procspawn::serde::in_ipc_mode() { 57 | println!("deserialize in ipc mode"); 58 | let path = PathBuf::deserialize(deserializer)?; 59 | MyBytes::open(path).map_err(D::Error::custom) 60 | } else { 61 | println!("deserialize in normal mode"); 62 | let helper = MyBytesHelper::deserialize(deserializer)?; 63 | Ok(MyBytes { 64 | path: helper.path.into_owned(), 65 | bytes: helper.bytes.into_owned(), 66 | }) 67 | } 68 | } 69 | } 70 | 71 | fn main() { 72 | procspawn::init(); 73 | 74 | let bytes = MyBytes::open("Cargo.toml").unwrap(); 75 | 76 | let bytes_two = procspawn::spawn!((bytes.clone() => bytes) || { 77 | println!("length: {}", bytes.bytes.len()); 78 | bytes 79 | }) 80 | .join() 81 | .unwrap(); 82 | 83 | assert_eq!(bytes, bytes_two); 84 | } 85 | -------------------------------------------------------------------------------- /examples/join.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | fn main() { 4 | procspawn::init(); 5 | 6 | let five = spawn(5, fibonacci); 7 | let ten = spawn(10, fibonacci); 8 | let thirty = spawn(30, fibonacci); 9 | assert_eq!(five.join().unwrap(), 5); 10 | assert_eq!(ten.join().unwrap(), 55); 11 | assert_eq!(thirty.join().unwrap(), 832_040); 12 | println!("Successfully calculated fibonacci values!"); 13 | } 14 | 15 | fn fibonacci(n: u32) -> u32 { 16 | if n <= 2 { 17 | return 1; 18 | } 19 | fibonacci(n - 1) + fibonacci(n - 2) 20 | } 21 | -------------------------------------------------------------------------------- /examples/kill.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | #[allow(clippy::empty_loop)] 4 | fn main() { 5 | procspawn::init(); 6 | let mut handle = spawn((), |()| loop {}); 7 | handle.kill().unwrap(); 8 | } 9 | -------------------------------------------------------------------------------- /examples/macro.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | fn main() { 4 | procspawn::init(); 5 | 6 | let a = 42u32; 7 | let b = 23u32; 8 | let c = 1; 9 | let handle = spawn!((a => new_name1, b, mut c) || -> Result<_, ()> { 10 | c += 1; 11 | Ok(new_name1 + b + c) 12 | }); 13 | let value = handle.join().unwrap(); 14 | 15 | println!("{:?}", value); 16 | } 17 | -------------------------------------------------------------------------------- /examples/panic.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | fn main() { 4 | procspawn::init(); 5 | 6 | let handle = spawn((), |()| { 7 | panic!("Whatever!"); 8 | }); 9 | 10 | match handle.join() { 11 | Ok(()) => unreachable!(), 12 | Err(err) => { 13 | let panic = err.panic_info().expect("got a non panic error"); 14 | println!("process panicked with {}", panic.message()); 15 | println!("{:#?}", panic); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/pool.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, Pool}; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | procspawn::init(); 7 | 8 | let pool = Pool::new(4).unwrap(); 9 | let mut handles = vec![]; 10 | 11 | for counter in 0..8 { 12 | handles.push(procspawn::spawn!(in pool, (counter) || { 13 | thread::sleep(Duration::from_millis(500)); 14 | counter 15 | })); 16 | } 17 | 18 | for handle in handles { 19 | match handle.join() { 20 | Ok(val) => println!("got result: {}", val), 21 | Err(err) => { 22 | let panic = err.panic_info().expect("got a non panic error"); 23 | println!("process panicked with {}", panic.message()); 24 | println!("{:#?}", panic); 25 | } 26 | } 27 | } 28 | 29 | pool.shutdown(); 30 | } 31 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | fn main() { 4 | procspawn::init(); 5 | 6 | let handle = spawn((1, 2), |(a, b)| { 7 | println!("in process: {:?} {:?}", a, b); 8 | a + b 9 | }); 10 | 11 | println!("result: {}", handle.join().unwrap()); 12 | } 13 | -------------------------------------------------------------------------------- /examples/stdout.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::process::Stdio; 3 | 4 | fn main() { 5 | procspawn::init(); 6 | 7 | let mut builder = procspawn::Builder::new(); 8 | builder.stdout(Stdio::piped()); 9 | 10 | let mut handle = builder.spawn((1, 2), |(a, b)| { 11 | println!("{:?} {:?}", a, b); 12 | }); 13 | 14 | let mut s = String::new(); 15 | handle.stdout().unwrap().read_to_string(&mut s).unwrap(); 16 | assert_eq!(s, "1 2\n"); 17 | } 18 | -------------------------------------------------------------------------------- /examples/timeout.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | procspawn::init(); 7 | 8 | let mut handle = spawn((), |()| { 9 | thread::sleep(Duration::from_secs(10)); 10 | }); 11 | 12 | println!("result: {:?}", handle.join_timeout(Duration::from_secs(1))); 13 | } 14 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::{OsStr, OsString}; 3 | use std::io; 4 | use std::mem; 5 | use std::panic; 6 | use std::process; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | 9 | #[cfg(feature = "safe-shared-libraries")] 10 | use findshlibs::{Avma, IterationControl, Segment, SharedLibrary}; 11 | 12 | use ipc_channel::ipc::{self, IpcReceiver, IpcSender, OpaqueIpcReceiver, OpaqueIpcSender}; 13 | use ipc_channel::ErrorKind as IpcErrorKind; 14 | use serde::{Deserialize, Serialize}; 15 | 16 | use crate::error::PanicInfo; 17 | use crate::panic::{init_panic_hook, reset_panic_info, take_panic, BacktraceCapture}; 18 | use crate::serde::with_ipc_mode; 19 | 20 | pub const ENV_NAME: &str = "__PROCSPAWN_CONTENT_PROCESS_ID"; 21 | static INITIALIZED: AtomicBool = AtomicBool::new(false); 22 | static PASS_ARGS: AtomicBool = AtomicBool::new(false); 23 | 24 | #[cfg(not(feature = "safe-shared-libraries"))] 25 | static ALLOW_UNSAFE_SPAWN: AtomicBool = AtomicBool::new(false); 26 | 27 | /// Asserts no shared libraries are used for functions spawned. 28 | /// 29 | /// If the `safe-shared-libraries` feature is disabled this function must be 30 | /// called once to validate that the application does not spawn functions 31 | /// from a shared library. 32 | /// 33 | /// This must be called once before the first call to a spawn function as 34 | /// otherwise they will panic. 35 | /// 36 | /// # Safety 37 | /// 38 | /// You must only call this function if you can guarantee that none of your 39 | /// `spawn` calls cross a shared library boundary. 40 | pub unsafe fn assert_spawn_is_safe() { 41 | #[cfg(not(feature = "safe-shared-libraries"))] 42 | { 43 | ALLOW_UNSAFE_SPAWN.store(true, Ordering::SeqCst); 44 | } 45 | } 46 | 47 | /// Can be used to configure the process. 48 | pub struct ProcConfig { 49 | callback: Option>, 50 | panic_handling: bool, 51 | pass_args: bool, 52 | #[cfg(feature = "backtrace")] 53 | capture_backtraces: bool, 54 | #[cfg(feature = "backtrace")] 55 | resolve_backtraces: bool, 56 | } 57 | 58 | impl Default for ProcConfig { 59 | fn default() -> ProcConfig { 60 | ProcConfig { 61 | callback: None, 62 | panic_handling: true, 63 | pass_args: true, 64 | #[cfg(feature = "backtrace")] 65 | capture_backtraces: true, 66 | #[cfg(feature = "backtrace")] 67 | resolve_backtraces: true, 68 | } 69 | } 70 | } 71 | 72 | pub fn mark_initialized() { 73 | INITIALIZED.store(true, Ordering::SeqCst); 74 | } 75 | 76 | pub fn should_pass_args() -> bool { 77 | PASS_ARGS.load(Ordering::SeqCst) 78 | } 79 | 80 | fn find_shared_library_offset_by_name(name: &OsStr) -> isize { 81 | #[cfg(feature = "safe-shared-libraries")] 82 | { 83 | let mut result = None; 84 | findshlibs::TargetSharedLibrary::each(|shlib| { 85 | if shlib.name() == name { 86 | result = Some( 87 | shlib 88 | .segments() 89 | .next() 90 | .map_or(0, |x| x.actual_virtual_memory_address(shlib).0 as isize), 91 | ); 92 | return IterationControl::Break; 93 | } 94 | IterationControl::Continue 95 | }); 96 | match result { 97 | Some(rv) => rv, 98 | None => panic!("Unable to locate shared library {:?} in subprocess", name), 99 | } 100 | } 101 | #[cfg(not(feature = "safe-shared-libraries"))] 102 | { 103 | let _ = name; 104 | init as *const () as isize 105 | } 106 | } 107 | 108 | fn find_library_name_and_offset(f: *const u8) -> (OsString, isize) { 109 | #[cfg(feature = "safe-shared-libraries")] 110 | { 111 | let mut result = None; 112 | findshlibs::TargetSharedLibrary::each(|shlib| { 113 | let start = shlib 114 | .segments() 115 | .next() 116 | .map_or(0, |x| x.actual_virtual_memory_address(shlib).0 as isize); 117 | for seg in shlib.segments() { 118 | if seg.contains_avma(shlib, Avma(f as usize)) { 119 | result = Some((shlib.name().to_owned(), start)); 120 | return IterationControl::Break; 121 | } 122 | } 123 | IterationControl::Continue 124 | }); 125 | result.expect("Unable to locate function pointer in loaded image") 126 | } 127 | #[cfg(not(feature = "safe-shared-libraries"))] 128 | { 129 | let _ = f; 130 | (OsString::new(), init as *const () as isize) 131 | } 132 | } 133 | 134 | impl ProcConfig { 135 | /// Creates a default proc config. 136 | pub fn new() -> ProcConfig { 137 | ProcConfig::default() 138 | } 139 | 140 | /// Attaches a callback that is used to initializes all processes. 141 | pub fn config_callback(&mut self, f: F) -> &mut Self { 142 | self.callback = Some(Box::new(f)); 143 | self 144 | } 145 | 146 | /// Enables or disables argument passing. 147 | /// 148 | /// By default all arguments are forwarded to the spawned process. 149 | pub fn pass_args(&mut self, enabled: bool) -> &mut Self { 150 | self.pass_args = enabled; 151 | self 152 | } 153 | 154 | /// Configure the automatic panic handling. 155 | /// 156 | /// The default behavior is that panics are caught and that a panic handler 157 | /// is installed. 158 | pub fn panic_handling(&mut self, enabled: bool) -> &mut Self { 159 | self.panic_handling = enabled; 160 | self 161 | } 162 | 163 | /// Configures if backtraces should be captured. 164 | /// 165 | /// The default behavior is that if panic handling is enabled backtraces 166 | /// will be captured. 167 | /// 168 | /// This requires the `backtrace` feature. 169 | #[cfg(feature = "backtrace")] 170 | pub fn capture_backtraces(&mut self, enabled: bool) -> &mut Self { 171 | self.capture_backtraces = enabled; 172 | self 173 | } 174 | 175 | /// Controls whether backtraces should be resolved. 176 | #[cfg(feature = "backtrace")] 177 | pub fn resolve_backtraces(&mut self, enabled: bool) -> &mut Self { 178 | self.resolve_backtraces = enabled; 179 | self 180 | } 181 | 182 | /// Consumes the config and initializes the process. 183 | pub fn init(&mut self) { 184 | mark_initialized(); 185 | PASS_ARGS.store(self.pass_args, Ordering::SeqCst); 186 | 187 | if let Ok(token) = env::var(ENV_NAME) { 188 | // permit nested invocations 189 | std::env::remove_var(ENV_NAME); 190 | if let Some(callback) = self.callback.take() { 191 | callback(); 192 | } 193 | bootstrap_ipc(token, self); 194 | } 195 | } 196 | 197 | fn backtrace_capture(&self) -> BacktraceCapture { 198 | #[cfg(feature = "backtrace")] 199 | { 200 | match (self.capture_backtraces, self.resolve_backtraces) { 201 | (false, _) => BacktraceCapture::No, 202 | (true, true) => BacktraceCapture::Resolved, 203 | (true, false) => BacktraceCapture::Unresolved, 204 | } 205 | } 206 | #[cfg(not(feature = "backtrace"))] 207 | { 208 | BacktraceCapture::No 209 | } 210 | } 211 | } 212 | 213 | /// Initializes procspawn. 214 | /// 215 | /// This function must be called at the beginning of `main`. Whatever comes 216 | /// before it is also executed for all processes spawned through the `spawn` 217 | /// function. 218 | /// 219 | /// For more complex initializations see [`ProcConfig`](struct.ProcConfig.html). 220 | pub fn init() { 221 | ProcConfig::default().init() 222 | } 223 | 224 | #[inline] 225 | pub fn assert_spawn_okay() { 226 | if !INITIALIZED.load(Ordering::SeqCst) { 227 | panic!("procspawn was not initialized"); 228 | } 229 | #[cfg(not(feature = "safe-shared-libraries"))] 230 | { 231 | if !ALLOW_UNSAFE_SPAWN.load(Ordering::SeqCst) { 232 | panic!( 233 | "spawn() prevented because safe-shared-library feature was \ 234 | disabled and assert_no_shared_libraries was not invoked." 235 | ); 236 | } 237 | } 238 | } 239 | 240 | fn is_benign_bootstrap_error(err: &io::Error) -> bool { 241 | // on macos we will observe an unknown mach error 242 | err.kind() == io::ErrorKind::Other && err.to_string() == "Unknown Mach error: 44e" 243 | } 244 | 245 | fn bootstrap_ipc(token: String, config: &ProcConfig) { 246 | if config.panic_handling { 247 | init_panic_hook(config.backtrace_capture()); 248 | } 249 | 250 | { 251 | let connection_bootstrap: IpcSender> = 252 | match IpcSender::connect(token) { 253 | Ok(sender) => sender, 254 | Err(err) => { 255 | if !is_benign_bootstrap_error(&err) { 256 | panic!("could not bootstrap ipc connection: {:?}", err); 257 | } 258 | process::exit(1); 259 | } 260 | }; 261 | let (tx, rx) = ipc::channel().unwrap(); 262 | connection_bootstrap.send(tx).unwrap(); 263 | let marshalled_call = rx.recv().unwrap(); 264 | marshalled_call.call(config.panic_handling); 265 | } 266 | process::exit(0); 267 | } 268 | 269 | /// Marshals a call across process boundaries. 270 | #[derive(Serialize, Deserialize, Debug)] 271 | pub struct MarshalledCall { 272 | pub lib_name: OsString, 273 | pub fn_offset: isize, 274 | pub wrapper_offset: isize, 275 | pub args_receiver: OpaqueIpcReceiver, 276 | pub return_sender: OpaqueIpcSender, 277 | } 278 | 279 | impl MarshalledCall { 280 | /// Marshalls the call. 281 | pub fn marshal( 282 | f: fn(A) -> R, 283 | args_receiver: IpcReceiver, 284 | return_sender: IpcSender>, 285 | ) -> MarshalledCall 286 | where 287 | A: Serialize + for<'de> Deserialize<'de>, 288 | R: Serialize + for<'de> Deserialize<'de>, 289 | { 290 | let (lib_name, offset) = find_library_name_and_offset(f as *const () as *const u8); 291 | let init_loc = init as *const () as isize; 292 | let fn_offset = f as *const () as isize - offset; 293 | let wrapper_offset = run_func:: as *const () as isize - init_loc; 294 | MarshalledCall { 295 | lib_name, 296 | fn_offset, 297 | wrapper_offset, 298 | args_receiver: args_receiver.to_opaque(), 299 | return_sender: return_sender.to_opaque(), 300 | } 301 | } 302 | 303 | /// Unmarshals and performs the call. 304 | pub fn call(self, panic_handling: bool) { 305 | unsafe { 306 | let ptr = self.wrapper_offset + init as *const () as isize; 307 | let func: fn(&OsStr, isize, OpaqueIpcReceiver, OpaqueIpcSender, bool) = 308 | mem::transmute(ptr); 309 | func( 310 | &self.lib_name, 311 | self.fn_offset, 312 | self.args_receiver, 313 | self.return_sender, 314 | panic_handling, 315 | ); 316 | } 317 | } 318 | } 319 | 320 | unsafe fn run_func( 321 | lib_name: &OsStr, 322 | fn_offset: isize, 323 | args_recv: OpaqueIpcReceiver, 324 | sender: OpaqueIpcSender, 325 | panic_handling: bool, 326 | ) where 327 | A: Serialize + for<'de> Deserialize<'de>, 328 | R: Serialize + for<'de> Deserialize<'de>, 329 | { 330 | let lib_offset = find_shared_library_offset_by_name(lib_name); 331 | let function: fn(A) -> R = mem::transmute(fn_offset + lib_offset as *const () as isize); 332 | let args = with_ipc_mode(|| args_recv.to().recv().unwrap()); 333 | let rv = if panic_handling { 334 | reset_panic_info(); 335 | match panic::catch_unwind(panic::AssertUnwindSafe(|| function(args))) { 336 | Ok(rv) => Ok(rv), 337 | Err(panic) => Err(take_panic(&*panic)), 338 | } 339 | } else { 340 | Ok(function(args)) 341 | }; 342 | 343 | // sending can fail easily because of bincode limitations. If you see 344 | // this in your tracebacks consider using the `Json` wrapper. 345 | if let Err(err) = with_ipc_mode(|| sender.to().send(rv)) { 346 | if let IpcErrorKind::Io(ref io) = *err { 347 | if io.kind() == io::ErrorKind::NotFound || io.kind() == io::ErrorKind::ConnectionReset { 348 | // this error is okay. this means nobody actually 349 | // waited for the call, so we just ignore it. 350 | } 351 | } else { 352 | panic!("could not send event over ipc channel: {:?}", err); 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | 4 | use ipc_channel::ipc::{IpcError, TryRecvError}; 5 | use ipc_channel::{Error as BincodeError, ErrorKind as BincodeErrorKind}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | /// Represents a panic caugh across processes. 9 | /// 10 | /// This contains the marshalled panic information so that it can be used 11 | /// for other purposes. 12 | /// 13 | /// This is similar to `std::panic::PanicInfo` but can cross process boundaries. 14 | #[derive(Serialize, Deserialize)] 15 | pub struct PanicInfo { 16 | msg: String, 17 | pub(crate) location: Option, 18 | #[cfg(feature = "backtrace")] 19 | pub(crate) backtrace: Option, 20 | } 21 | 22 | /// Location of a panic. 23 | /// 24 | /// This is similar to `std::panic::Location` but can cross process boundaries. 25 | #[derive(Serialize, Deserialize, Debug)] 26 | pub struct Location { 27 | file: String, 28 | line: u32, 29 | column: u32, 30 | } 31 | 32 | impl Location { 33 | pub(crate) fn from_std(loc: &std::panic::Location) -> Location { 34 | Location { 35 | file: loc.file().into(), 36 | line: loc.line(), 37 | column: loc.column(), 38 | } 39 | } 40 | 41 | /// Returns the name of the source file from which the panic originated. 42 | pub fn file(&self) -> &str { 43 | &self.file 44 | } 45 | 46 | /// Returns the line number from which the panic originated. 47 | pub fn line(&self) -> u32 { 48 | self.line 49 | } 50 | 51 | /// Returns the column from which the panic originated. 52 | pub fn column(&self) -> u32 { 53 | self.column 54 | } 55 | } 56 | 57 | impl PanicInfo { 58 | /// Creates a new panic object. 59 | pub(crate) fn new(s: &str) -> PanicInfo { 60 | PanicInfo { 61 | msg: s.into(), 62 | location: None, 63 | #[cfg(feature = "backtrace")] 64 | backtrace: None, 65 | } 66 | } 67 | 68 | /// Returns the message of the panic. 69 | pub fn message(&self) -> &str { 70 | self.msg.as_str() 71 | } 72 | 73 | /// Returns the panic location. 74 | pub fn location(&self) -> Option<&Location> { 75 | self.location.as_ref() 76 | } 77 | 78 | /// Returns a reference to the backtrace. 79 | /// 80 | /// Typically this backtrace is already resolved because it's currently 81 | /// not possible to cross the process boundaries with unresolved backtraces. 82 | #[cfg(feature = "backtrace")] 83 | pub fn backtrace(&self) -> Option<&backtrace::Backtrace> { 84 | self.backtrace.as_ref() 85 | } 86 | } 87 | 88 | impl fmt::Debug for PanicInfo { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | f.debug_struct("PanicInfo") 91 | .field("message", &self.message()) 92 | .field("location", &self.location()) 93 | .field("backtrace", &{ 94 | #[cfg(feature = "backtrace")] 95 | { 96 | self.backtrace() 97 | } 98 | #[cfg(not(feature = "backtrace"))] 99 | { 100 | None::<()> 101 | } 102 | }) 103 | .finish() 104 | } 105 | } 106 | 107 | impl fmt::Display for PanicInfo { 108 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 109 | write!(f, "{}", self.msg) 110 | } 111 | } 112 | 113 | /// Encapsulates errors of the procspawn crate. 114 | /// 115 | /// In particular it gives access to remotely captured panics. 116 | #[derive(Debug)] 117 | pub struct SpawnError { 118 | kind: SpawnErrorKind, 119 | } 120 | 121 | #[derive(Debug)] 122 | enum SpawnErrorKind { 123 | Bincode(BincodeError), 124 | Io(io::Error), 125 | Panic(PanicInfo), 126 | IpcChannelClosed(io::Error), 127 | Cancelled, 128 | TimedOut, 129 | Consumed, 130 | } 131 | 132 | impl SpawnError { 133 | /// If a panic ocurred this returns the captured panic info. 134 | pub fn panic_info(&self) -> Option<&PanicInfo> { 135 | if let SpawnErrorKind::Panic(ref info) = self.kind { 136 | Some(info) 137 | } else { 138 | None 139 | } 140 | } 141 | 142 | /// True if this error comes from a panic. 143 | pub fn is_panic(&self) -> bool { 144 | self.panic_info().is_some() 145 | } 146 | 147 | /// True if this error indicates a cancellation. 148 | pub fn is_cancellation(&self) -> bool { 149 | matches!(self.kind, SpawnErrorKind::Cancelled) 150 | } 151 | 152 | /// True if this error indicates a timeout. 153 | pub fn is_timeout(&self) -> bool { 154 | matches!(self.kind, SpawnErrorKind::TimedOut) 155 | } 156 | 157 | /// True if this means the remote side closed. 158 | pub fn is_remote_close(&self) -> bool { 159 | matches!(self.kind, SpawnErrorKind::IpcChannelClosed(..)) 160 | } 161 | 162 | pub(crate) fn new_remote_close() -> SpawnError { 163 | SpawnError { 164 | kind: SpawnErrorKind::IpcChannelClosed(io::Error::new( 165 | io::ErrorKind::ConnectionReset, 166 | "remote closed", 167 | )), 168 | } 169 | } 170 | 171 | pub(crate) fn new_cancelled() -> SpawnError { 172 | SpawnError { 173 | kind: SpawnErrorKind::Cancelled, 174 | } 175 | } 176 | 177 | pub(crate) fn new_timeout() -> SpawnError { 178 | SpawnError { 179 | kind: SpawnErrorKind::TimedOut, 180 | } 181 | } 182 | 183 | pub(crate) fn new_consumed() -> SpawnError { 184 | SpawnError { 185 | kind: SpawnErrorKind::Consumed, 186 | } 187 | } 188 | } 189 | 190 | impl std::error::Error for SpawnError { 191 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 192 | match self.kind { 193 | SpawnErrorKind::Bincode(ref err) => Some(err), 194 | SpawnErrorKind::Io(ref err) => Some(err), 195 | SpawnErrorKind::Panic(_) => None, 196 | SpawnErrorKind::Cancelled => None, 197 | SpawnErrorKind::TimedOut => None, 198 | SpawnErrorKind::Consumed => None, 199 | SpawnErrorKind::IpcChannelClosed(ref err) => Some(err), 200 | } 201 | } 202 | } 203 | 204 | impl fmt::Display for SpawnError { 205 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 206 | match self.kind { 207 | SpawnErrorKind::Bincode(_) => write!(f, "process spawn error: bincode error"), 208 | SpawnErrorKind::Io(_) => write!(f, "process spawn error: i/o error"), 209 | SpawnErrorKind::Panic(ref p) => write!(f, "process spawn error: panic: {}", p), 210 | SpawnErrorKind::Cancelled => write!(f, "process spawn error: call cancelled"), 211 | SpawnErrorKind::TimedOut => write!(f, "process spawn error: timed out"), 212 | SpawnErrorKind::Consumed => write!(f, "process spawn error: result already consumed"), 213 | SpawnErrorKind::IpcChannelClosed(_) => write!( 214 | f, 215 | "process spawn error: remote side closed (might have panicked on serialization)" 216 | ), 217 | } 218 | } 219 | } 220 | 221 | impl From for SpawnError { 222 | fn from(err: BincodeError) -> SpawnError { 223 | // unwrap nested IO errors 224 | if let BincodeErrorKind::Io(io_err) = *err { 225 | return SpawnError::from(io_err); 226 | } 227 | SpawnError { 228 | kind: SpawnErrorKind::Bincode(err), 229 | } 230 | } 231 | } 232 | 233 | impl From for SpawnError { 234 | fn from(err: TryRecvError) -> SpawnError { 235 | match err { 236 | TryRecvError::Empty => SpawnError::new_remote_close(), 237 | TryRecvError::IpcError(err) => SpawnError::from(err), 238 | } 239 | } 240 | } 241 | 242 | impl From for SpawnError { 243 | fn from(err: IpcError) -> SpawnError { 244 | // unwrap nested IO errors 245 | match err { 246 | IpcError::Io(err) => SpawnError::from(err), 247 | IpcError::Bincode(err) => SpawnError::from(err), 248 | IpcError::Disconnected => SpawnError::new_remote_close(), 249 | } 250 | } 251 | } 252 | 253 | impl From for SpawnError { 254 | fn from(err: io::Error) -> SpawnError { 255 | if let io::ErrorKind::ConnectionReset = err.kind() { 256 | return SpawnError { 257 | kind: SpawnErrorKind::IpcChannelClosed(err), 258 | }; 259 | } 260 | SpawnError { 261 | kind: SpawnErrorKind::Io(err), 262 | } 263 | } 264 | } 265 | 266 | impl From for SpawnError { 267 | fn from(panic: PanicInfo) -> SpawnError { 268 | SpawnError { 269 | kind: SpawnErrorKind::Panic(panic), 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/json.rs: -------------------------------------------------------------------------------- 1 | use serde::de::{self, Deserialize, DeserializeOwned, Deserializer}; 2 | use serde::ser::{self, Serialize, Serializer}; 3 | 4 | /// Utility wrapper to force values through JSON serialization. 5 | /// 6 | /// By default `procspawn` will use [`bincode`](https://github.com/servo/bincode) to serialize 7 | /// data across process boundaries. This has some limitations which can cause serialization 8 | /// or deserialization to fail for some types. 9 | /// 10 | /// Since JSON is generally better supported in the serde ecosystem this lets you work 11 | /// around some known bugs. 12 | /// 13 | /// * serde flatten not being supported: [bincode#245](https://github.com/servo/bincode/issues/245) 14 | /// * vectors with unknown length not supported: [bincode#167](https://github.com/servo/bincode/issues/167) 15 | /// 16 | /// Examples: 17 | /// 18 | /// ```rust,no_run 19 | /// use procspawn::{spawn, serde::Json}; 20 | /// use serde::{Deserialize, Serialize}; 21 | /// 22 | /// #[derive(Serialize, Deserialize, Debug)] 23 | /// struct InnerStruct { 24 | /// value: u64, 25 | /// } 26 | /// 27 | /// #[derive(Serialize, Deserialize, Debug)] 28 | /// struct BadStruct { 29 | /// #[serde(flatten)] 30 | /// inner: InnerStruct, 31 | /// } 32 | /// 33 | /// let handle = spawn((), |()| { 34 | /// Json(BadStruct { 35 | /// inner: InnerStruct { value: 42 }, 36 | /// }) 37 | /// }); 38 | /// let value = handle.join().unwrap().0; 39 | /// ``` 40 | /// 41 | /// This requires the `json` feature. 42 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 43 | pub struct Json(pub T); 44 | 45 | impl Serialize for Json { 46 | fn serialize(&self, serializer: S) -> Result 47 | where 48 | S: Serializer, 49 | { 50 | let json = serde_json::to_string(&self.0).map_err(|e| ser::Error::custom(e.to_string()))?; 51 | serializer.serialize_str(&json) 52 | } 53 | } 54 | 55 | impl<'de, T: DeserializeOwned> Deserialize<'de> for Json { 56 | fn deserialize(deserializer: D) -> Result, D::Error> 57 | where 58 | D: Deserializer<'de>, 59 | { 60 | let json = 61 | String::deserialize(deserializer).map_err(|e| de::Error::custom(e.to_string()))?; 62 | Ok(Json( 63 | serde_json::from_str(&json).map_err(|e| de::Error::custom(e.to_string()))?, 64 | )) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides the ability to spawn processes with a function similar 2 | //! to `thread::spawn`. 3 | //! 4 | //! Unlike `thread::spawn` data cannot be passed by the use of closures. Instead 5 | //! if must be explicitly passed as serializable object (specifically it must be 6 | //! [`serde`](https://serde.rs/) serializable). The return value from the 7 | //! spawned closure also must be serializable and can then be retrieved from 8 | //! the returned join handle. 9 | //! 10 | //! If the spawned functiom causes a panic it will also be serialized across 11 | //! the process boundaries. 12 | //! 13 | //! # Example 14 | //! 15 | //! First for all of this to work you need to invoke `procspawn::init` at a 16 | //! point early in your program (somewhere at the beginning of the main function). 17 | //! Whatever happens before that point also happens in your spawned functions. 18 | //! 19 | //! Subprocesses are by default invoked with the same arguments and environment 20 | //! variables as the parent process. 21 | //! 22 | //! ```rust,no_run 23 | //! procspawn::init(); 24 | //! ``` 25 | //! 26 | //! Now you can start spawning functions: 27 | //! 28 | //! ```rust,no_run 29 | //! let data = vec![1, 2, 3, 4]; 30 | //! let handle = procspawn::spawn(data, |data| { 31 | //! println!("Received data {:?}", &data); 32 | //! data.into_iter().sum::() 33 | //! }); 34 | //! let result = handle.join().unwrap(); 35 | //!``` 36 | //! 37 | //! Because `procspawn` will invoke a subprocess and there is currently no 38 | //! reliable way to intercept `main` in Rust it's necessary for you to call 39 | //! [`procspawn::init`](fn.init.html) explicitly an early time in the program. 40 | //! 41 | //! Alternatively you can use the [`ProcConfig`](struct.ProcConfig.html) 42 | //! builder object to initialize the process which gives you some extra 43 | //! abilities to customize the processes spawned. This for instance lets you 44 | //! disable the default panic handling. 45 | //! 46 | //! [`spawn`](fn.spawn.html) can pass arbitrary serializable data, including 47 | //! IPC senders and receivers from the [`ipc-channel`](https://crates.io/crates/ipc-channel) 48 | //! crate, down to the new process. 49 | //! 50 | //! # Pools 51 | //! 52 | //! The default way to spawn processes will start and stop processes constantly. 53 | //! For more uses it's a better idea to spawn a [`Pool`](struct.Pool.html) 54 | //! which will keep processes around for reuse. Between calls the processes 55 | //! will stay around which also means the can keep state between calls if 56 | //! needed. 57 | //! 58 | //! # Panics 59 | //! 60 | //! By default panics are captured and serialized across process boundaries. 61 | //! This requires that the `backtrace` crate is used with serialization support. 62 | //! If you do not need this feature you can disable the `backtrace` crate and 63 | //! disable panic handling through the [`ProcConfig`](struct.ProcConfig.html) 64 | //! object. 65 | //! 66 | //! # Feature Flags 67 | //! 68 | //! The following feature flags exist: 69 | //! 70 | //! * `safe-shared-libraries`: this feature is enabled by default. When this 71 | //! feature is disable then no validation about shared library load status 72 | //! is performed around IPC calls. This is highly unsafe if shared libraries 73 | //! are being used and a function from a shared library is spawned. 74 | //! * `backtrace`: this feature is enabled by default. When in use then 75 | //! backtraces are captured with the `backtrace-rs` crate and serialized 76 | //! across process boundaries. 77 | //! * `test-support`: when this feature is enabled procspawn can be used 78 | //! with rusttest. See [`testing`](#testing) for more information. 79 | //! * `json`: enables optional JSON serialization. For more information see 80 | //! [Bincode Limitations](#bincode-limitations). 81 | //! 82 | //! # Bincode Limitations 83 | //! 84 | //! This crate uses [`bincode`](https://github.com/servo/bincode) internally 85 | //! for inter process communication. Bincode currently has some limitations 86 | //! which make some serde features incompatible with it. Most notably if you 87 | //! use `#[serde(flatten)]` data cannot be sent across the processes. To 88 | //! work around this you can enable the `json` feature and wrap affected objects 89 | //! in the [`Json`](serde/struct.Json.html) wrapper to force JSON serialization. 90 | //! 91 | //! # Testing 92 | //! 93 | //! Due to limitations of the rusttest testing system there are some 94 | //! restrictions to how this crate operates. First of all you need to enable 95 | //! the `test-support` feature for `procspawn` to work with rusttest at all. 96 | //! Secondly your tests need to invoke the 97 | //! [`enable_test_support!`](macro.enable_test_support.html) macro once 98 | //! top-level. 99 | //! 100 | //! With this done the following behavior applies: 101 | //! 102 | //! * Tests behave as if `procspawn::init` was called (that means with the 103 | //! default arguments). Other configuration is not supported. 104 | //! * procspawn will register a dummy test (named `procspawn_test_helper`) 105 | //! which doesn't do anything when called directly, but acts as the spawning 106 | //! helper for all `spawn` calls. 107 | //! * stdout is silenced by default unless `--show-output` or `--nocapture` 108 | //! is passed to tests. 109 | //! * when trying to spawn with intercepted `stdout` be aware that there is 110 | //! extra noise that will be emitted by rusttest. 111 | //! 112 | //! ```rust,no_run 113 | //! procspawn::enable_test_support!(); 114 | //! 115 | //! #[test] 116 | //! fn test_basic() { 117 | //! let handle = procspawn::spawn((1, 2), |(a, b)| a + b); 118 | //! let value = handle.join().unwrap(); 119 | //! assert_eq!(value, 3); 120 | //! } 121 | //! ``` 122 | //! 123 | //! # Shared Libraries 124 | //! 125 | //! `procspawn` uses the [`findshlibs`](https://github.com/gimli-rs/findshlibs) 126 | //! crate to determine where a function is located in memory in both processes. 127 | //! If a shared library is not loaded in the subprocess (because for instance it 128 | //! is loaded at runtime) then the call will fail. Because this adds quite 129 | //! some overhead over every call you can also disable the `safe-shared-libraries` 130 | //! feature (which is on by default) in which case you are not allowed to 131 | //! invoke functions from shared libraries and no validation is performed. 132 | //! 133 | //! This in normal circumstances should be okay but you need to validate this. 134 | //! Spawning processes will be disabled if the feature is not enabled until 135 | //! you call the [`assert_spawn_is_safe`](fn.assert_spawn_is_safe.html) function. 136 | //! 137 | //! # Macros 138 | //! 139 | //! Alternatively the [`spawn!`](macro.spawn.html) macro can be used which can 140 | //! make passing more than one argument easier: 141 | //! 142 | //! ```rust,no_run 143 | //! let a = 42u32; 144 | //! let b = 23u32; 145 | //! let c = 1; 146 | //! let handle = procspawn::spawn!((a => base, b, mut c) || -> Result<_, ()> { 147 | //! c += 1; 148 | //! Ok(base + b + c) 149 | //! }); 150 | //! ``` 151 | //! 152 | //! # Platform Support 153 | //! 154 | //! Currently this crate only supports macOS and Linux because ipc-channel 155 | //! itself does not support Windows yet. Additionally the findshlibs which is 156 | //! used for the `safe-shared-libraries` feature also does not yet support 157 | //! Windows. 158 | //! 159 | //! # More Examples 160 | //! 161 | //! Here are some examples of `procspawn` in action: 162 | //! 163 | //! * [simple.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/simple.rs): 164 | //! a very simple example showing the basics. 165 | //! * [args.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/args.rs): 166 | //! shows how arguments are available to the subprocess as well. 167 | //! * [timeout.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/timeout.rs): 168 | //! shows how you can wait on a process with timeouts. 169 | //! * [bad-serialization.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/bad-serialization.rs): 170 | //! shows JSON based workarounds for bincode limitations. 171 | //! * [macro.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/macro.rs): 172 | //! demonstrates macro usage. 173 | //! 174 | //! More examples can be found in the example folder: [examples](https://github.com/mitsuhiko/procspawn/tree/master/examples) 175 | 176 | #[macro_use] 177 | mod proc; 178 | 179 | mod core; 180 | mod error; 181 | mod panic; 182 | mod pool; 183 | 184 | #[cfg(feature = "json")] 185 | mod json; 186 | 187 | #[doc(hidden)] 188 | pub mod testsupport; 189 | 190 | pub mod serde; 191 | 192 | mod macros; 193 | 194 | pub use self::core::{assert_spawn_is_safe, init, ProcConfig}; 195 | pub use self::error::{Location, PanicInfo, SpawnError}; 196 | pub use self::pool::{Pool, PoolBuilder}; 197 | pub use self::proc::{spawn, Builder, JoinHandle}; 198 | 199 | #[cfg(feature = "async")] 200 | pub use self::asyncsupport::{spawn_async, AsyncJoinHandle}; 201 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Utility macro to spawn functions. 2 | /// 3 | /// Since a very common pattern is to pass multiple values to a spawned 4 | /// function by using tuples it can be cumbersome to repeat the arguments. 5 | /// This macro makes it possible to not repeat oneself. The first argument 6 | /// to the macro is a tuple of the arguments to pass, the second argument 7 | /// is the closure that should be invoked. 8 | /// 9 | /// For each argument the following expressions are possible: 10 | /// 11 | /// * `ident`: uses the local variable `ident` unchanged. 12 | /// * `ident => other`: serializes `ident` and deserializes into `other` 13 | /// * `*ident`: alias for `*ident => ident`. 14 | /// * `mut ident`: alias for `ident => mut ident`. 15 | /// 16 | /// Example: 17 | /// 18 | /// ```rust,no_run 19 | /// let a = 42u32; 20 | /// let b = 23u32; 21 | /// let handle = procspawn::spawn!((a, mut b) || { 22 | /// b += 1; 23 | /// a + b 24 | /// }); 25 | /// ``` 26 | /// 27 | /// To spawn in a pool you need to pass the pool to it (prefixed with `in`): 28 | /// 29 | /// ```rust,no_run 30 | /// # let pool = procspawn::Pool::new(4).unwrap(); 31 | /// let a = 42u32; 32 | /// let b = 23u32; 33 | /// let handle = procspawn::spawn!(in pool, (a, mut b) || { 34 | /// b += 1; 35 | /// a + b 36 | /// }); 37 | /// ``` 38 | /// 39 | #[macro_export] 40 | macro_rules! spawn { 41 | (in $pool:expr, $($args:tt)*) => { $crate::_spawn_impl!(pool $pool, $($args)*) }; 42 | ($($args:tt)*) => { $crate::_spawn_impl!(func $crate::spawn, $($args)*) } 43 | } 44 | 45 | #[macro_export] 46 | #[doc(hidden)] 47 | macro_rules! _spawn_impl { 48 | (pool $pool:expr, () || $($body:tt)*) => { 49 | $pool.spawn( 50 | (), 51 | |()| 52 | $($body)* 53 | ) 54 | }; 55 | (pool $pool:expr, ($($param:tt)*) || $($body:tt)*) => { 56 | $pool.spawn( 57 | $crate::_spawn_call_arg!($($param)*), 58 | |($crate::_spawn_decl_arg!($($param)*))| 59 | $($body)* 60 | ) 61 | }; 62 | (func $func:path, () || $($body:tt)*) => { 63 | $func( 64 | (), 65 | |()| 66 | $($body)* 67 | ) 68 | }; 69 | (func $func:path, ($($param:tt)*) || $($body:tt)*) => { 70 | $func( 71 | $crate::_spawn_call_arg!($($param)*), 72 | |($crate::_spawn_decl_arg!($($param)*))| 73 | $($body)* 74 | ) 75 | }; 76 | } 77 | 78 | #[macro_export] 79 | #[doc(hidden)] 80 | macro_rules! _spawn_call_arg { 81 | ($expr:expr => mut $x:ident, $($tt:tt)*) => {( 82 | $expr, $crate::_spawn_call_arg!($($tt)*) 83 | )}; 84 | (*$expr:expr => $x:ident, $($tt:tt)*) => {( 85 | *$expr, $crate::_spawn_call_arg!($($tt)*) 86 | )}; 87 | ($expr:expr => $x:ident, $($tt:tt)*) => {( 88 | $expr, $crate::_spawn_call_arg!($($tt)*) 89 | )}; 90 | ($expr:expr => mut $x:ident) => {( 91 | $expr, 92 | )}; 93 | ($expr:expr => $x:ident) => {( 94 | $expr, 95 | )}; 96 | (mut $x:ident, $($tt:tt)*) => {( 97 | $x, $crate::_spawn_call_arg!($($tt)*) 98 | )}; 99 | (mut $x:ident) => {( 100 | $x, 101 | )}; 102 | (*$x:ident, $($tt:tt)*) => {( 103 | *$x, $crate::_spawn_call_arg!($($tt)*) 104 | )}; 105 | ($x:ident, $($tt:tt)*) => {( 106 | $x, $crate::_spawn_call_arg!($($tt)*) 107 | )}; 108 | (*$x:ident) => {( 109 | *$x, 110 | )}; 111 | ($x:ident) => {( 112 | $x, 113 | )}; 114 | 115 | ($unexpected:tt) => { 116 | $crate::_spawn_unexpected($unexpected); 117 | }; 118 | () => (()) 119 | } 120 | 121 | #[macro_export] 122 | #[doc(hidden)] 123 | macro_rules! _spawn_decl_arg { 124 | ($expr:expr => mut $x:ident, $($tt:tt)*) => {( 125 | mut $x, $crate::_spawn_decl_arg!($($tt)*) 126 | )}; 127 | (*$expr:expr => $x:ident, $($tt:tt)*) => {( 128 | $x, $crate::_spawn_decl_arg!($($tt)*) 129 | )}; 130 | ($expr:expr => $x:ident, $($tt:tt)*) => {( 131 | $x, $crate::_spawn_decl_arg!($($tt)*) 132 | )}; 133 | ($expr:expr => mut $x:ident) => {( 134 | mut $x, 135 | )}; 136 | (*$expr:expr => $x:ident) => {( 137 | $x, 138 | )}; 139 | ($expr:expr => $x:ident) => {( 140 | $x, 141 | )}; 142 | (mut $x:ident, $($tt:tt)*) => {( 143 | mut $x, $crate::_spawn_decl_arg!($($tt)*) 144 | )}; 145 | (mut $x:ident) => {( 146 | mut $x, 147 | )}; 148 | (*$x:ident, $($tt:tt)*) => {( 149 | $x, $crate::_spawn_decl_arg!($($tt)*) 150 | )}; 151 | ($x:ident, $($tt:tt)*) => {( 152 | $x, $crate::_spawn_decl_arg!($($tt)*) 153 | )}; 154 | (*$x:ident) => {( 155 | $x, 156 | )}; 157 | ($x:ident) => {( 158 | $x, 159 | )}; 160 | 161 | ($unexpected:tt) => { 162 | $crate::_spawn_unexpected($unexpected); 163 | }; 164 | () => () 165 | } 166 | 167 | #[macro_export] 168 | #[doc(hidden)] 169 | macro_rules! _spawn_unexpected { 170 | () => {}; 171 | } 172 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::panic; 4 | 5 | use crate::error::{Location, PanicInfo}; 6 | 7 | thread_local! { 8 | static PANIC_INFO: RefCell> = const { RefCell::new(None) }; 9 | } 10 | 11 | #[derive(Copy, Clone)] 12 | pub enum BacktraceCapture { 13 | No, 14 | #[cfg(feature = "backtrace")] 15 | Resolved, 16 | #[cfg(feature = "backtrace")] 17 | Unresolved, 18 | } 19 | 20 | pub fn reset_panic_info() { 21 | PANIC_INFO.with(|pi| { 22 | *pi.borrow_mut() = None; 23 | }); 24 | } 25 | 26 | pub fn take_panic(panic: &dyn Any) -> PanicInfo { 27 | PANIC_INFO 28 | .with(|pi| pi.borrow_mut().take()) 29 | .unwrap_or_else(move || serialize_panic(panic)) 30 | } 31 | 32 | pub fn panic_handler(info: &panic::PanicInfo<'_>, capture_backtraces: BacktraceCapture) { 33 | PANIC_INFO.with(|pi| { 34 | #[allow(unused_mut)] 35 | let mut panic = serialize_panic(info.payload()); 36 | match capture_backtraces { 37 | BacktraceCapture::No => {} 38 | #[cfg(feature = "backtrace")] 39 | BacktraceCapture::Resolved => { 40 | panic.backtrace = Some(backtrace::Backtrace::new()); 41 | } 42 | #[cfg(feature = "backtrace")] 43 | BacktraceCapture::Unresolved => { 44 | panic.backtrace = Some(backtrace::Backtrace::new_unresolved()); 45 | } 46 | } 47 | panic.location = info.location().map(Location::from_std); 48 | *pi.borrow_mut() = Some(panic); 49 | }); 50 | } 51 | 52 | pub fn init_panic_hook(capture_backtraces: BacktraceCapture) { 53 | let next = panic::take_hook(); 54 | panic::set_hook(Box::new(move |info| { 55 | panic_handler(info, capture_backtraces); 56 | next(info); 57 | })); 58 | } 59 | 60 | fn serialize_panic(panic: &dyn Any) -> PanicInfo { 61 | PanicInfo::new(match panic.downcast_ref::<&'static str>() { 62 | Some(s) => s, 63 | None => match panic.downcast_ref::() { 64 | Some(s) => &s[..], 65 | None => "Box", 66 | }, 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/pool.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::fmt; 3 | use std::io; 4 | use std::process; 5 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 6 | use std::sync::{mpsc, Arc, Condvar, Mutex}; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | use ipc_channel::ipc; 11 | use serde::{de::DeserializeOwned, Serialize}; 12 | 13 | use crate::core::MarshalledCall; 14 | use crate::error::SpawnError; 15 | use crate::proc::{Builder, JoinHandle, JoinHandleInner, ProcCommon, ProcessHandleState}; 16 | use crate::serde::with_ipc_mode; 17 | 18 | type WaitFunc = Box bool + Send>; 19 | type NotifyErrorFunc = Box; 20 | 21 | #[derive(Debug)] 22 | pub struct PooledHandleState { 23 | pub cancelled: AtomicBool, 24 | pub process_handle_state: Mutex>>, 25 | } 26 | 27 | impl PooledHandleState { 28 | pub fn kill(&self) { 29 | self.cancelled.store(true, Ordering::SeqCst); 30 | if let Some(ref process_handle_state) = *self.process_handle_state.lock().unwrap() { 31 | process_handle_state.kill(); 32 | } 33 | } 34 | } 35 | 36 | pub struct PooledHandle { 37 | waiter_rx: mpsc::Receiver>, 38 | shared: Arc, 39 | } 40 | 41 | impl PooledHandle { 42 | pub fn process_handle_state(&self) -> Option> { 43 | self.shared.process_handle_state.lock().unwrap().clone() 44 | } 45 | 46 | pub fn kill(&mut self) -> Result<(), SpawnError> { 47 | self.shared.kill(); 48 | Ok(()) 49 | } 50 | } 51 | 52 | impl PooledHandle { 53 | pub fn join(&mut self) -> Result { 54 | match self.waiter_rx.recv() { 55 | Ok(Ok(rv)) => Ok(rv), 56 | Ok(Err(err)) => Err(err), 57 | Err(..) => Err(SpawnError::new_remote_close()), 58 | } 59 | } 60 | 61 | pub fn join_timeout(&mut self, timeout: Duration) -> Result { 62 | match self.waiter_rx.recv_timeout(timeout) { 63 | Ok(Ok(rv)) => Ok(rv), 64 | Ok(Err(err)) => Err(err), 65 | Err(mpsc::RecvTimeoutError::Timeout) => { 66 | self.kill().ok(); 67 | Err(SpawnError::new_timeout()) 68 | } 69 | Err(mpsc::RecvTimeoutError::Disconnected) => Err(SpawnError::new_remote_close()), 70 | } 71 | } 72 | } 73 | 74 | type PoolSender = mpsc::Sender<( 75 | MarshalledCall, 76 | Arc, 77 | WaitFunc, 78 | NotifyErrorFunc, 79 | )>; 80 | 81 | /// A process pool. 82 | /// 83 | /// This works similar to `spawn` but lets you retain a pool of processes. Since 84 | /// procspawn is intended to isolate potentially crashing code the pool will 85 | /// automatically restart broken processes. 86 | /// 87 | /// Note that it's not possible to intercept streams of processes spawned 88 | /// through the pool. 89 | /// 90 | /// When the process pool is dropped all processes are killed. 91 | /// 92 | /// This requires the `pool` feature. 93 | pub struct Pool { 94 | sender: Mutex, 95 | shared: Arc, 96 | } 97 | 98 | impl fmt::Debug for Pool { 99 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 100 | f.debug_struct("Pool") 101 | .field("size", &self.size()) 102 | .field("active_count", &self.active_count()) 103 | .field("queued_count", &self.queued_count()) 104 | .finish() 105 | } 106 | } 107 | 108 | impl Pool { 109 | /// Creates the default pool. 110 | pub fn new(size: usize) -> Result { 111 | Pool::builder(size).build() 112 | } 113 | 114 | /// Creates a builder to customize pool creation. 115 | pub fn builder(size: usize) -> PoolBuilder { 116 | PoolBuilder::new(size) 117 | } 118 | 119 | /// Returns the size of the pool. 120 | pub fn size(&self) -> usize { 121 | self.shared.monitors.lock().unwrap().len() 122 | } 123 | 124 | /// Returns the number of jobs waiting to executed in the pool. 125 | pub fn queued_count(&self) -> usize { 126 | self.shared.queued_count.load(Ordering::Relaxed) 127 | } 128 | 129 | /// Returns the number of currently active threads. 130 | pub fn active_count(&self) -> usize { 131 | self.shared.active_count.load(Ordering::SeqCst) 132 | } 133 | 134 | /// Spawns a closure into a process of the pool. 135 | /// 136 | /// This works exactly like [`procspawn::spawn`](fn.spawn.html) but instead 137 | /// of spawning a new process, it reuses a process from the pool. 138 | pub fn spawn< 139 | A: Serialize + DeserializeOwned, 140 | R: Serialize + DeserializeOwned + Send + 'static, 141 | >( 142 | &self, 143 | args: A, 144 | func: fn(A) -> R, 145 | ) -> JoinHandle { 146 | self.assert_alive(); 147 | let (args_tx, args_rx) = ipc::channel().unwrap(); 148 | let (return_tx, return_rx) = ipc::channel().unwrap(); 149 | 150 | let call = MarshalledCall::marshal::(func, args_rx, return_tx); 151 | let (waiter_tx, waiter_rx) = mpsc::sync_channel(0); 152 | let error_waiter_tx = waiter_tx.clone(); 153 | self.shared.queued_count.fetch_add(1, Ordering::SeqCst); 154 | 155 | let shared = Arc::new(PooledHandleState { 156 | cancelled: AtomicBool::new(false), 157 | process_handle_state: Mutex::new(None), 158 | }); 159 | 160 | self.sender 161 | .lock() 162 | .expect("pool sender poisoned") 163 | .send(( 164 | call, 165 | shared.clone(), 166 | Box::new(move || { 167 | with_ipc_mode(|| { 168 | if let Ok(rv) = return_rx.recv() { 169 | waiter_tx.send(rv.map_err(Into::into)).is_ok() 170 | } else { 171 | false 172 | } 173 | }) 174 | }), 175 | Box::new(move |error| { 176 | error_waiter_tx.send(Err(error)).ok(); 177 | }), 178 | )) 179 | .ok(); 180 | 181 | args_tx.send(args).unwrap(); 182 | 183 | JoinHandle { 184 | inner: Ok(JoinHandleInner::Pooled(PooledHandle { waiter_rx, shared })), 185 | } 186 | } 187 | 188 | /// Joins the process pool. 189 | pub fn join(&self) { 190 | self.assert_alive(); 191 | 192 | // fast path requires no mutex 193 | if !self.shared.has_work() { 194 | return; 195 | } 196 | 197 | let generation = self.shared.join_generation.load(Ordering::SeqCst); 198 | let mut lock = self.shared.empty_trigger.lock().unwrap(); 199 | 200 | while generation == self.shared.join_generation.load(Ordering::Relaxed) 201 | && self.shared.has_work() 202 | { 203 | lock = self.shared.empty_condvar.wait(lock).unwrap(); 204 | } 205 | 206 | // increase generation if we are the first thread to come out of the loop 207 | self.shared 208 | .join_generation 209 | .compare_exchange( 210 | generation, 211 | generation.wrapping_add(1), 212 | Ordering::SeqCst, 213 | Ordering::SeqCst, 214 | ) 215 | .ok(); 216 | } 217 | 218 | /// Joins and shuts down. 219 | pub fn shutdown(&self) { 220 | if !self.shared.dead.load(Ordering::SeqCst) { 221 | self.join(); 222 | self.kill(); 223 | } 224 | } 225 | 226 | /// Hard kills all processes in the pool. 227 | /// 228 | /// After calling this the pool cannot be used any more. 229 | pub fn kill(&self) { 230 | if self.shared.dead.load(Ordering::SeqCst) { 231 | return; 232 | } 233 | self.shared.dead.store(true, Ordering::SeqCst); 234 | for monitor in self.shared.monitors.lock().unwrap().iter_mut() { 235 | if let Some(mut join_handle) = monitor.join_handle.lock().unwrap().take() { 236 | join_handle.kill().ok(); 237 | } 238 | } 239 | } 240 | 241 | fn assert_alive(&self) { 242 | if self.shared.dead.load(Ordering::SeqCst) { 243 | panic!("The process pool is dead"); 244 | } 245 | } 246 | } 247 | 248 | /// Utility to configure a pool. 249 | /// 250 | /// This requires the `pool` feature. 251 | #[derive(Debug)] 252 | pub struct PoolBuilder { 253 | size: usize, 254 | disable_stdin: bool, 255 | disable_stdout: bool, 256 | disable_stderr: bool, 257 | common: ProcCommon, 258 | } 259 | 260 | impl PoolBuilder { 261 | /// Create a new pool builder. 262 | /// 263 | /// The size of the process pool is mandatory. If you want to 264 | /// make it depending on the processor count you can use the 265 | /// `num_cpus` crate. 266 | fn new(size: usize) -> PoolBuilder { 267 | PoolBuilder { 268 | size, 269 | disable_stdin: false, 270 | disable_stdout: false, 271 | disable_stderr: false, 272 | common: ProcCommon::default(), 273 | } 274 | } 275 | 276 | define_common_methods!(); 277 | 278 | /// Redirects stdin to `/dev/null`. 279 | pub fn disable_stdin(&mut self) -> &mut Self { 280 | self.disable_stdin = true; 281 | self 282 | } 283 | 284 | /// Redirects stdout to `/dev/null`. 285 | pub fn disable_stdout(&mut self) -> &mut Self { 286 | self.disable_stdout = true; 287 | self 288 | } 289 | 290 | /// Redirects stderr to `/dev/null`. 291 | pub fn disable_stderr(&mut self) -> &mut Self { 292 | self.disable_stderr = true; 293 | self 294 | } 295 | 296 | /// Creates the pool. 297 | pub fn build(&mut self) -> Result { 298 | let (tx, rx) = mpsc::channel(); 299 | 300 | let shared = Arc::new(PoolShared { 301 | call_receiver: Mutex::new(rx), 302 | empty_trigger: Mutex::new(()), 303 | empty_condvar: Condvar::new(), 304 | join_generation: AtomicUsize::new(0), 305 | monitors: Mutex::new(Vec::with_capacity(self.size)), 306 | queued_count: AtomicUsize::new(0), 307 | active_count: AtomicUsize::new(0), 308 | dead: AtomicBool::new(false), 309 | }); 310 | 311 | { 312 | let mut monitors = shared.monitors.lock().unwrap(); 313 | for _ in 0..self.size { 314 | monitors.push(spawn_worker(shared.clone(), self)?); 315 | } 316 | } 317 | 318 | Ok(Pool { 319 | sender: Mutex::new(tx), 320 | shared, 321 | }) 322 | } 323 | } 324 | 325 | impl Drop for Pool { 326 | fn drop(&mut self) { 327 | self.kill(); 328 | } 329 | } 330 | 331 | struct PoolShared { 332 | #[allow(clippy::type_complexity)] 333 | call_receiver: Mutex< 334 | mpsc::Receiver<( 335 | MarshalledCall, 336 | Arc, 337 | WaitFunc, 338 | NotifyErrorFunc, 339 | )>, 340 | >, 341 | empty_trigger: Mutex<()>, 342 | empty_condvar: Condvar, 343 | join_generation: AtomicUsize, 344 | monitors: Mutex>, 345 | queued_count: AtomicUsize, 346 | active_count: AtomicUsize, 347 | dead: AtomicBool, 348 | } 349 | 350 | impl PoolShared { 351 | fn has_work(&self) -> bool { 352 | self.queued_count.load(Ordering::SeqCst) > 0 || self.active_count.load(Ordering::SeqCst) > 0 353 | } 354 | 355 | fn no_work_notify_all(&self) { 356 | if !self.has_work() { 357 | drop( 358 | self.empty_trigger 359 | .lock() 360 | .expect("Unable to notify all joining threads"), 361 | ); 362 | self.empty_condvar.notify_all(); 363 | } 364 | } 365 | } 366 | 367 | struct WorkerMonitor { 368 | join_handle: Arc>>>, 369 | } 370 | 371 | fn spawn_worker( 372 | shared: Arc, 373 | builder: &PoolBuilder, 374 | ) -> Result { 375 | let join_handle = Arc::new(Mutex::new(None::>)); 376 | let current_call_tx = Arc::new(Mutex::new(None::>)); 377 | 378 | let spawn = Arc::new(Mutex::new({ 379 | let disable_stdin = builder.disable_stdin; 380 | let disable_stdout = builder.disable_stdout; 381 | let disable_stderr = builder.disable_stderr; 382 | let common = builder.common.clone(); 383 | let join_handle = join_handle.clone(); 384 | let current_call_tx = current_call_tx.clone(); 385 | move || { 386 | let (call_tx, call_rx) = ipc::channel::().unwrap(); 387 | let mut builder = Builder::new(); 388 | builder.common(common.clone()); 389 | if disable_stdin { 390 | builder.stdin(process::Stdio::null()); 391 | } 392 | if disable_stdout { 393 | builder.stdout(process::Stdio::null()); 394 | } 395 | if disable_stderr { 396 | builder.stderr(process::Stdio::null()); 397 | } 398 | *join_handle.lock().unwrap() = Some(builder.spawn(call_rx, |rx| { 399 | while let Ok(call) = rx.recv() { 400 | // we never want panic handling here as we're going to 401 | // defer this to the process'. 402 | call.call(false); 403 | } 404 | })); 405 | *current_call_tx.lock().unwrap() = Some(call_tx); 406 | } 407 | })); 408 | 409 | let check_for_restart = { 410 | let spawn = spawn.clone(); 411 | let join_handle = join_handle.clone(); 412 | let shared = shared.clone(); 413 | move |f: &mut NotifyErrorFunc| { 414 | // something went wrong so we're expecting the join handle to 415 | // indicate an error. 416 | if let Some(join_handle) = join_handle.lock().unwrap().take() { 417 | match join_handle.join() { 418 | Ok(()) => f(SpawnError::from(io::Error::new( 419 | io::ErrorKind::BrokenPipe, 420 | "client process died", 421 | ))), 422 | Err(err) => f(err), 423 | } 424 | } 425 | 426 | // next step is respawning the client. 427 | if !shared.dead.load(Ordering::SeqCst) { 428 | (*spawn.lock().unwrap())(); 429 | } 430 | } 431 | }; 432 | 433 | // for each worker we spawn a monitoring thread 434 | { 435 | let join_handle = join_handle.clone(); 436 | thread::Builder::new() 437 | .name("procspawn-monitor".into()) 438 | .spawn(move || { 439 | loop { 440 | if shared.dead.load(Ordering::SeqCst) { 441 | break; 442 | } 443 | 444 | let (call, state, wait_func, mut err_func) = { 445 | // Only lock jobs for the time it takes 446 | // to get a job, not run it. 447 | let lock = shared 448 | .call_receiver 449 | .lock() 450 | .expect("Monitor thread unable to lock call receiver"); 451 | match lock.recv() { 452 | Ok(rv) => rv, 453 | Err(_) => break, 454 | } 455 | }; 456 | 457 | shared.active_count.fetch_add(1, Ordering::SeqCst); 458 | shared.queued_count.fetch_sub(1, Ordering::SeqCst); 459 | 460 | // this task was already cancelled, no need to execute it 461 | if state.cancelled.load(Ordering::SeqCst) { 462 | err_func(SpawnError::new_cancelled()); 463 | } else { 464 | if let Some(ref mut handle) = *join_handle.lock().unwrap() { 465 | *state.process_handle_state.lock().unwrap() = 466 | handle.process_handle_state(); 467 | } 468 | 469 | let mut restart = false; 470 | { 471 | let mut call_tx = current_call_tx.lock().unwrap(); 472 | if let Some(ref mut call_tx) = *call_tx { 473 | match with_ipc_mode(|| call_tx.send(call)) { 474 | Ok(()) => {} 475 | Err(..) => { 476 | restart = true; 477 | } 478 | } 479 | } else { 480 | restart = true; 481 | } 482 | } 483 | 484 | if !restart && !wait_func() { 485 | restart = true; 486 | } 487 | 488 | *state.process_handle_state.lock().unwrap() = None; 489 | 490 | if restart { 491 | check_for_restart(&mut err_func); 492 | } 493 | } 494 | 495 | shared.active_count.fetch_sub(1, Ordering::SeqCst); 496 | shared.no_work_notify_all(); 497 | } 498 | }) 499 | .unwrap(); 500 | } 501 | 502 | (*spawn.lock().unwrap())(); 503 | 504 | Ok(WorkerMonitor { join_handle }) 505 | } 506 | -------------------------------------------------------------------------------- /src/proc.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::{OsStr, OsString}; 3 | use std::fmt; 4 | use std::path::PathBuf; 5 | use std::process::Stdio; 6 | use std::process::{ChildStderr, ChildStdin, ChildStdout}; 7 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 8 | use std::sync::Arc; 9 | use std::time::{Duration, Instant}; 10 | use std::{env, mem, process}; 11 | use std::{io, thread}; 12 | 13 | use ipc_channel::ipc::{self, IpcOneShotServer, IpcReceiver, IpcSender}; 14 | use serde::{de::DeserializeOwned, Serialize}; 15 | 16 | use crate::core::{assert_spawn_okay, should_pass_args, MarshalledCall, ENV_NAME}; 17 | use crate::error::{PanicInfo, SpawnError}; 18 | use crate::pool::PooledHandle; 19 | use crate::serde::with_ipc_mode; 20 | 21 | #[cfg(unix)] 22 | type PreExecFunc = dyn FnMut() -> io::Result<()> + Send + Sync + 'static; 23 | 24 | #[derive(Clone)] 25 | pub struct ProcCommon { 26 | pub vars: HashMap, 27 | #[cfg(unix)] 28 | pub uid: Option, 29 | #[cfg(unix)] 30 | pub gid: Option, 31 | #[cfg(unix)] 32 | pub pre_exec: Option>>>, 33 | } 34 | 35 | impl fmt::Debug for ProcCommon { 36 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 37 | f.debug_struct("ProcCommon") 38 | .field("vars", &self.vars) 39 | .finish() 40 | } 41 | } 42 | 43 | impl Default for ProcCommon { 44 | fn default() -> ProcCommon { 45 | ProcCommon { 46 | vars: std::env::vars_os().collect(), 47 | #[cfg(unix)] 48 | uid: None, 49 | #[cfg(unix)] 50 | gid: None, 51 | #[cfg(unix)] 52 | pre_exec: None, 53 | } 54 | } 55 | } 56 | 57 | /// Process factory, which can be used in order to configure the properties 58 | /// of a process being created. 59 | /// 60 | /// Methods can be chained on it in order to configure it. 61 | #[derive(Debug, Default)] 62 | pub struct Builder { 63 | stdin: Option, 64 | stdout: Option, 65 | stderr: Option, 66 | common: ProcCommon, 67 | } 68 | 69 | macro_rules! define_common_methods { 70 | () => { 71 | /// Set an environment variable in the spawned process. 72 | /// 73 | /// Equivalent to `Command::env` 74 | pub fn env(&mut self, key: K, val: V) -> &mut Self 75 | where 76 | K: AsRef, 77 | V: AsRef, 78 | { 79 | self.common 80 | .vars 81 | .insert(key.as_ref().to_owned(), val.as_ref().to_owned()); 82 | self 83 | } 84 | 85 | /// Set environment variables in the spawned process. 86 | /// 87 | /// Equivalent to `Command::envs` 88 | pub fn envs(&mut self, vars: I) -> &mut Self 89 | where 90 | I: IntoIterator, 91 | K: AsRef, 92 | V: AsRef, 93 | { 94 | self.common.vars.extend( 95 | vars.into_iter() 96 | .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())), 97 | ); 98 | self 99 | } 100 | 101 | /// Removes an environment variable in the spawned process. 102 | /// 103 | /// Equivalent to `Command::env_remove` 104 | pub fn env_remove>(&mut self, key: K) -> &mut Self { 105 | self.common.vars.remove(key.as_ref()); 106 | self 107 | } 108 | 109 | /// Clears all environment variables in the spawned process. 110 | /// 111 | /// Equivalent to `Command::env_clear` 112 | pub fn env_clear(&mut self) -> &mut Self { 113 | self.common.vars.clear(); 114 | self 115 | } 116 | 117 | /// Sets the child process's user ID. This translates to a 118 | /// `setuid` call in the child process. Failure in the `setuid` 119 | /// call will cause the spawn to fail. 120 | /// 121 | /// Unix-specific extension only available on unix. 122 | /// 123 | /// Equivalent to `std::os::unix::process::CommandExt::uid` 124 | #[cfg(unix)] 125 | pub fn uid(&mut self, id: u32) -> &mut Self { 126 | self.common.uid = Some(id); 127 | self 128 | } 129 | 130 | /// Similar to `uid`, but sets the group ID of the child process. This has 131 | /// the same semantics as the `uid` field. 132 | /// 133 | /// Unix-specific extension only available on unix. 134 | /// 135 | /// Equivalent to `std::os::unix::process::CommandExt::gid` 136 | #[cfg(unix)] 137 | pub fn gid(&mut self, id: u32) -> &mut Self { 138 | self.common.gid = Some(id); 139 | self 140 | } 141 | 142 | /// Schedules a closure to be run just before the `exec` function is 143 | /// invoked. 144 | /// 145 | /// # Safety 146 | /// 147 | /// This method is inherently unsafe. See the notes of the unix command 148 | /// ext for more information. 149 | /// 150 | /// Equivalent to `std::os::unix::process::CommandExt::pre_exec` 151 | #[cfg(unix)] 152 | pub unsafe fn pre_exec(&mut self, f: F) -> &mut Self 153 | where 154 | F: FnMut() -> io::Result<()> + Send + Sync + 'static, 155 | { 156 | self.common.pre_exec = Some(Arc::new(std::sync::Mutex::new(Box::new(f)))); 157 | self 158 | } 159 | }; 160 | } 161 | 162 | impl Builder { 163 | /// Generates the base configuration for spawning a thread, from which 164 | /// configuration methods can be chained. 165 | pub fn new() -> Self { 166 | Self { 167 | stdin: None, 168 | stdout: None, 169 | stderr: None, 170 | common: ProcCommon::default(), 171 | } 172 | } 173 | 174 | pub(crate) fn common(&mut self, common: ProcCommon) -> &mut Self { 175 | self.common = common; 176 | self 177 | } 178 | 179 | define_common_methods!(); 180 | 181 | /// Captures the `stdin` of the spawned process, allowing you to manually 182 | /// send data via `JoinHandle::stdin` 183 | pub fn stdin>(&mut self, cfg: T) -> &mut Self { 184 | self.stdin = Some(cfg.into()); 185 | self 186 | } 187 | 188 | /// Captures the `stdout` of the spawned process, allowing you to manually 189 | /// receive data via `JoinHandle::stdout` 190 | pub fn stdout>(&mut self, cfg: T) -> &mut Self { 191 | self.stdout = Some(cfg.into()); 192 | self 193 | } 194 | 195 | /// Captures the `stderr` of the spawned process, allowing you to manually 196 | /// receive data via `JoinHandle::stderr` 197 | pub fn stderr>(&mut self, cfg: T) -> &mut Self { 198 | self.stderr = Some(cfg.into()); 199 | self 200 | } 201 | 202 | /// Spawns the process. 203 | pub fn spawn( 204 | &mut self, 205 | args: A, 206 | func: fn(A) -> R, 207 | ) -> JoinHandle { 208 | assert_spawn_okay(); 209 | JoinHandle { 210 | inner: mem::take(self) 211 | .spawn_helper(args, func) 212 | .map(JoinHandleInner::Process), 213 | } 214 | } 215 | 216 | fn spawn_helper( 217 | self, 218 | args: A, 219 | func: fn(A) -> R, 220 | ) -> Result, SpawnError> { 221 | let (server, token) = IpcOneShotServer::>::new()?; 222 | let me = if cfg!(target_os = "linux") { 223 | // will work even if exe is moved 224 | let path: PathBuf = "/proc/self/exe".into(); 225 | if path.is_file() { 226 | path 227 | } else { 228 | // might not exist, e.g. on chroot 229 | env::current_exe()? 230 | } 231 | } else { 232 | env::current_exe()? 233 | }; 234 | let mut child = process::Command::new(me); 235 | child.envs(self.common.vars); 236 | child.env(ENV_NAME, token); 237 | 238 | #[cfg(unix)] 239 | { 240 | use std::os::unix::process::CommandExt; 241 | if let Some(id) = self.common.uid { 242 | child.uid(id); 243 | } 244 | if let Some(id) = self.common.gid { 245 | child.gid(id); 246 | } 247 | if let Some(ref func) = self.common.pre_exec { 248 | let func = func.clone(); 249 | unsafe { 250 | #[allow(clippy::needless_borrow)] 251 | child.pre_exec(move || (&mut *func.lock().unwrap())()); 252 | } 253 | } 254 | } 255 | 256 | let (can_pass_args, should_silence_stdout) = { 257 | #[cfg(feature = "test-support")] 258 | { 259 | match crate::testsupport::update_command_for_tests(&mut child) { 260 | None => (true, false), 261 | Some(crate::testsupport::TestMode { 262 | can_pass_args, 263 | should_silence_stdout, 264 | }) => (can_pass_args, should_silence_stdout), 265 | } 266 | } 267 | #[cfg(not(feature = "test-support"))] 268 | { 269 | (true, false) 270 | } 271 | }; 272 | 273 | if can_pass_args && should_pass_args() { 274 | child.args(env::args_os().skip(1)); 275 | } 276 | 277 | if let Some(stdin) = self.stdin { 278 | child.stdin(stdin); 279 | } 280 | if let Some(stdout) = self.stdout { 281 | child.stdout(stdout); 282 | } else if should_silence_stdout { 283 | child.stdout(Stdio::null()); 284 | } 285 | if let Some(stderr) = self.stderr { 286 | child.stderr(stderr); 287 | } 288 | let process = child.spawn()?; 289 | 290 | let (_rx, tx) = server.accept()?; 291 | 292 | let (args_tx, args_rx) = ipc::channel()?; 293 | let (return_tx, return_rx) = ipc::channel()?; 294 | 295 | tx.send(MarshalledCall::marshal::(func, args_rx, return_tx))?; 296 | with_ipc_mode(|| -> Result<_, SpawnError> { 297 | args_tx.send(args)?; 298 | Ok(()) 299 | })?; 300 | 301 | Ok(ProcessHandle { 302 | recv: return_rx, 303 | state: Arc::new(ProcessHandleState::new(Some(process.id()))), 304 | process, 305 | }) 306 | } 307 | } 308 | 309 | #[derive(Debug)] 310 | pub struct ProcessHandleState { 311 | pub exited: AtomicBool, 312 | pub pid: AtomicUsize, 313 | } 314 | 315 | impl ProcessHandleState { 316 | pub fn new(pid: Option) -> ProcessHandleState { 317 | ProcessHandleState { 318 | exited: AtomicBool::new(false), 319 | pid: AtomicUsize::new(pid.unwrap_or(0) as usize), 320 | } 321 | } 322 | 323 | pub fn pid(&self) -> Option { 324 | match self.pid.load(Ordering::SeqCst) { 325 | 0 => None, 326 | x => Some(x as u32), 327 | } 328 | } 329 | 330 | pub fn kill(&self) { 331 | if !self.exited.load(Ordering::SeqCst) { 332 | self.exited.store(true, Ordering::SeqCst); 333 | if let Some(pid) = self.pid() { 334 | unsafe { 335 | #[cfg(unix)] 336 | { 337 | libc::kill(pid as i32, libc::SIGKILL); 338 | } 339 | #[cfg(windows)] 340 | { 341 | use windows_sys::Win32::System::Threading; 342 | let proc = 343 | Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid as _); 344 | Threading::TerminateProcess(proc, 1); 345 | } 346 | } 347 | } 348 | } 349 | } 350 | } 351 | 352 | pub struct ProcessHandle { 353 | pub(crate) recv: IpcReceiver>, 354 | pub(crate) process: process::Child, 355 | pub(crate) state: Arc, 356 | } 357 | 358 | fn is_ipc_timeout(err: &ipc_channel::ipc::TryRecvError) -> bool { 359 | matches!(err, ipc_channel::ipc::TryRecvError::Empty) 360 | } 361 | 362 | impl ProcessHandle { 363 | pub fn state(&self) -> Arc { 364 | self.state.clone() 365 | } 366 | 367 | pub fn kill(&mut self) -> Result<(), SpawnError> { 368 | if self.state.exited.load(Ordering::SeqCst) { 369 | return Ok(()); 370 | } 371 | 372 | let rv = self.process.kill().map_err(Into::into); 373 | self.wait(); 374 | rv 375 | } 376 | 377 | pub fn stdin(&mut self) -> Option<&mut ChildStdin> { 378 | self.process.stdin.as_mut() 379 | } 380 | 381 | pub fn stdout(&mut self) -> Option<&mut ChildStdout> { 382 | self.process.stdout.as_mut() 383 | } 384 | 385 | pub fn stderr(&mut self) -> Option<&mut ChildStderr> { 386 | self.process.stderr.as_mut() 387 | } 388 | 389 | fn wait(&mut self) { 390 | self.process.wait().ok(); 391 | self.state.exited.store(true, Ordering::SeqCst); 392 | } 393 | } 394 | 395 | impl ProcessHandle { 396 | pub fn join(&mut self) -> Result { 397 | let rv = with_ipc_mode(|| self.recv.recv())?.map_err(Into::into); 398 | self.wait(); 399 | rv 400 | } 401 | 402 | pub fn join_timeout(&mut self, timeout: Duration) -> Result { 403 | let deadline = match Instant::now().checked_add(timeout) { 404 | Some(deadline) => deadline, 405 | None => { 406 | return Err(io::Error::new(io::ErrorKind::Other, "timeout out of bounds").into()) 407 | } 408 | }; 409 | let mut to_sleep = Duration::from_millis(1); 410 | let rv = loop { 411 | match with_ipc_mode(|| self.recv.try_recv()) { 412 | Ok(rv) => break rv.map_err(Into::into), 413 | Err(err) if is_ipc_timeout(&err) => { 414 | if let Some(remaining) = deadline.checked_duration_since(Instant::now()) { 415 | thread::sleep(remaining.min(to_sleep)); 416 | to_sleep *= 2; 417 | } else { 418 | return Err(SpawnError::new_timeout()); 419 | } 420 | } 421 | Err(err) => return Err(err.into()), 422 | } 423 | }; 424 | 425 | self.wait(); 426 | rv 427 | } 428 | } 429 | 430 | pub enum JoinHandleInner { 431 | Process(ProcessHandle), 432 | Pooled(PooledHandle), 433 | } 434 | 435 | /// An owned permission to join on a process (block on its termination). 436 | /// 437 | /// The join handle can be used to join a process but also provides the 438 | /// ability to kill it. 439 | pub struct JoinHandle { 440 | pub(crate) inner: Result, SpawnError>, 441 | } 442 | 443 | impl fmt::Debug for JoinHandle { 444 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 445 | f.debug_struct("JoinHandle") 446 | .field("pid", &self.pid()) 447 | .finish() 448 | } 449 | } 450 | 451 | impl JoinHandle { 452 | pub(crate) fn process_handle_state(&self) -> Option> { 453 | match self.inner { 454 | Ok(JoinHandleInner::Process(ref handle)) => Some(handle.state()), 455 | Ok(JoinHandleInner::Pooled(ref handle)) => handle.process_handle_state(), 456 | Err(..) => None, 457 | } 458 | } 459 | 460 | /// Returns the process ID if available. 461 | /// 462 | /// The process ID is unavailable when pooled calls are not scheduled to 463 | /// processes. 464 | pub fn pid(&self) -> Option { 465 | self.process_handle_state().and_then(|x| x.pid()) 466 | } 467 | 468 | /// Kill the child process. 469 | /// 470 | /// If the join handle was created from a pool this call will do one of 471 | /// two things depending on the situation: 472 | /// 473 | /// * if the call was already picked up by the process, the process will 474 | /// be killed. 475 | /// * if the call was not yet scheduled to a process it will be cancelled. 476 | pub fn kill(&mut self) -> Result<(), SpawnError> { 477 | match self.inner { 478 | Ok(JoinHandleInner::Process(ref mut handle)) => handle.kill(), 479 | Ok(JoinHandleInner::Pooled(ref mut handle)) => handle.kill(), 480 | Err(_) => Ok(()), 481 | } 482 | } 483 | 484 | /// Fetch the `stdin` handle if it has been captured 485 | pub fn stdin(&mut self) -> Option<&mut ChildStdin> { 486 | match self.inner { 487 | Ok(JoinHandleInner::Process(ref mut process)) => process.stdin(), 488 | Ok(JoinHandleInner::Pooled(..)) => None, 489 | Err(_) => None, 490 | } 491 | } 492 | 493 | /// Fetch the `stdout` handle if it has been captured 494 | pub fn stdout(&mut self) -> Option<&mut ChildStdout> { 495 | match self.inner { 496 | Ok(JoinHandleInner::Process(ref mut process)) => process.stdout(), 497 | Ok(JoinHandleInner::Pooled(..)) => None, 498 | Err(_) => None, 499 | } 500 | } 501 | 502 | /// Fetch the `stderr` handle if it has been captured 503 | pub fn stderr(&mut self) -> Option<&mut ChildStderr> { 504 | match self.inner { 505 | Ok(JoinHandleInner::Process(ref mut process)) => process.stderr(), 506 | Ok(JoinHandleInner::Pooled(..)) => None, 507 | Err(_) => None, 508 | } 509 | } 510 | } 511 | 512 | impl JoinHandle { 513 | /// Wait for the child process to return a result. 514 | /// 515 | /// If the join handle was created from a pool the join is virtualized. 516 | pub fn join(self) -> Result { 517 | match self.inner { 518 | Ok(JoinHandleInner::Process(mut handle)) => handle.join(), 519 | Ok(JoinHandleInner::Pooled(mut handle)) => handle.join(), 520 | Err(err) => Err(err), 521 | } 522 | } 523 | 524 | /// Like `join` but with a timeout. 525 | /// 526 | /// Can be called multiple times. If anything other than a timeout error is returned, the 527 | /// handle becomes unusuable, and subsequent calls to either `join` or `join_timeout` will 528 | /// return an error. 529 | pub fn join_timeout(&mut self, timeout: Duration) -> Result { 530 | match self.inner { 531 | Ok(ref mut handle_inner) => { 532 | let result = match handle_inner { 533 | JoinHandleInner::Process(ref mut handle) => handle.join_timeout(timeout), 534 | JoinHandleInner::Pooled(ref mut handle) => handle.join_timeout(timeout), 535 | }; 536 | 537 | if result.is_ok() { 538 | self.inner = Err(SpawnError::new_consumed()); 539 | } 540 | 541 | result 542 | } 543 | Err(ref mut err) => { 544 | let mut rv_err = SpawnError::new_consumed(); 545 | mem::swap(&mut rv_err, err); 546 | Err(rv_err) 547 | } 548 | } 549 | } 550 | } 551 | 552 | /// Spawn a new process to run a function with some payload. 553 | /// 554 | /// ```rust,no_run 555 | /// // call this early in your main() function. This is where all spawned 556 | /// // functions will be invoked. 557 | /// procspawn::init(); 558 | /// 559 | /// let data = vec![1, 2, 3, 4]; 560 | /// let handle = procspawn::spawn(data, |data| { 561 | /// println!("Received data {:?}", &data); 562 | /// data.into_iter().sum::() 563 | /// }); 564 | /// let result = handle.join().unwrap(); 565 | /// ``` 566 | pub fn spawn( 567 | args: A, 568 | f: fn(A) -> R, 569 | ) -> JoinHandle { 570 | Builder::new().spawn(args, f) 571 | } 572 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for working with serde. 2 | //! 3 | //! Because serde is the underlying library used for data passing between 4 | //! processes procspawn provides various utilities that help with common 5 | //! operations. 6 | use ipc_channel::ipc::IpcSharedMemory; 7 | use serde::{de::Deserializer, de::Error, de::Visitor, ser::Serializer}; 8 | use serde::{Deserialize, Serialize}; 9 | use std::fmt; 10 | use std::sync::atomic::{AtomicBool, Ordering}; 11 | 12 | thread_local! { 13 | static IN_PROCSPAWN: AtomicBool = const { AtomicBool::new(false) }; 14 | } 15 | 16 | struct ResetProcspawn(bool); 17 | 18 | impl Drop for ResetProcspawn { 19 | fn drop(&mut self) { 20 | IN_PROCSPAWN.with(|in_procspawn| in_procspawn.swap(self.0, Ordering::Relaxed)); 21 | } 22 | } 23 | 24 | /// Internal helper to mark all serde calls that go across processes 25 | /// so that serializers can respond to it. 26 | pub fn with_ipc_mode R, R>(f: F) -> R { 27 | let old = IN_PROCSPAWN.with(|in_procspawn| in_procspawn.swap(true, Ordering::Relaxed)); 28 | let _dropper = ResetProcspawn(old); 29 | f() 30 | } 31 | 32 | /// Checks if serde is in IPC mode. 33 | /// 34 | /// This can be used to customize the serialization behavior of custom 35 | /// types for IPC specific purposes. This is useful when a type has a regular 36 | /// serialization/deserialization behavior but you want to use a cheaper one 37 | /// when procspawn is used. 38 | /// 39 | /// An example of this can be a type that abstracts over an mmap. It might want 40 | /// to serialize the raw bytes under normal circumstances but for IPC purposes 41 | /// might want to instead serialize the underlying file path and reopen the 42 | /// mmap on the other side. 43 | /// 44 | /// This function returns `true` whenever procspawn is attempting to serialize 45 | /// and deserialize but never anytime else. Internally this is implemented as a 46 | /// thread local. 47 | pub fn in_ipc_mode() -> bool { 48 | IN_PROCSPAWN.with(|in_procspawn| in_procspawn.load(Ordering::Relaxed)) 49 | } 50 | 51 | /// A read-only byte buffer for sending between processes. 52 | /// 53 | /// The buffer behind the scenes uses shared memory which is faster send 54 | /// between processes than to serialize the raw bytes directly. It is however 55 | /// read-only. 56 | pub struct Shmem { 57 | shmem: IpcSharedMemory, 58 | } 59 | 60 | impl fmt::Debug for Shmem { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 | struct ByteRepr<'a>(&'a Shmem); 63 | 64 | impl<'a> fmt::Debug for ByteRepr<'a> { 65 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 66 | write!(f, "b\"")?; 67 | for &byte in self.0.as_bytes() { 68 | if (32..127).contains(&byte) { 69 | write!(f, "{}", byte)?; 70 | } else { 71 | write!(f, "\\x{:02x}", byte)?; 72 | } 73 | } 74 | write!(f, "b\"")?; 75 | Ok(()) 76 | } 77 | } 78 | 79 | f.debug_tuple("Shmem").field(&ByteRepr(self)).finish() 80 | } 81 | } 82 | 83 | impl Shmem { 84 | /// Creates a buffer from some bytes. 85 | pub fn from_bytes(bytes: &[u8]) -> Shmem { 86 | Shmem { 87 | shmem: IpcSharedMemory::from_bytes(bytes), 88 | } 89 | } 90 | 91 | /// Returns the bytes inside. 92 | pub fn as_bytes(&self) -> &[u8] { 93 | &self.shmem 94 | } 95 | } 96 | 97 | impl std::ops::Deref for Shmem { 98 | type Target = [u8]; 99 | 100 | #[inline] 101 | fn deref(&self) -> &[u8] { 102 | &self.shmem 103 | } 104 | } 105 | 106 | impl Serialize for Shmem { 107 | fn serialize(&self, serializer: S) -> Result 108 | where 109 | S: Serializer, 110 | { 111 | if in_ipc_mode() { 112 | self.shmem.serialize(serializer) 113 | } else { 114 | serializer.serialize_bytes(self.as_bytes()) 115 | } 116 | } 117 | } 118 | 119 | struct ShmemVisitor; 120 | 121 | impl<'de> Visitor<'de> for ShmemVisitor { 122 | type Value = Shmem; 123 | 124 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 125 | formatter.write_str("a borrowed byte array") 126 | } 127 | 128 | fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result 129 | where 130 | E: Error, 131 | { 132 | Ok(Shmem { 133 | shmem: IpcSharedMemory::from_bytes(v), 134 | }) 135 | } 136 | 137 | fn visit_borrowed_str(self, v: &'de str) -> Result 138 | where 139 | E: Error, 140 | { 141 | Ok(Shmem { 142 | shmem: IpcSharedMemory::from_bytes(v.as_bytes()), 143 | }) 144 | } 145 | } 146 | 147 | impl<'de> Deserialize<'de> for Shmem { 148 | fn deserialize(deserializer: D) -> Result 149 | where 150 | D: Deserializer<'de>, 151 | { 152 | if in_ipc_mode() { 153 | Ok(Shmem { 154 | shmem: IpcSharedMemory::deserialize(deserializer)?, 155 | }) 156 | } else { 157 | deserializer.deserialize_bytes(ShmemVisitor) 158 | } 159 | } 160 | } 161 | 162 | #[cfg(feature = "json")] 163 | pub use crate::json::Json; 164 | -------------------------------------------------------------------------------- /src/testsupport.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "test-support")] 2 | use std::env; 3 | use std::process::Command; 4 | use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; 5 | 6 | use crate::core::mark_initialized; 7 | 8 | static TEST_MODE: AtomicBool = AtomicBool::new(false); 9 | static TEST_MODULE: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); 10 | 11 | // we need this. 12 | pub use small_ctor::ctor; 13 | 14 | /// Supports the use of procspawn in tests. 15 | /// 16 | /// Due to limitations in rusttest it's currently not easily possible to use 17 | /// procspawn with rusttest. The workaround is to call use this macro toplevel 18 | /// which will define a dummy test which is used to invoke all subprocesses. 19 | /// 20 | /// ```rust,no_run 21 | /// procspawn::enable_test_support!(); 22 | /// ``` 23 | /// 24 | /// Requires the `test-support` feature. 25 | #[macro_export] 26 | macro_rules! enable_test_support { 27 | () => { 28 | #[$crate::testsupport::ctor] 29 | unsafe fn __procspawn_test_support_init() { 30 | // strip the crate name from the module path 31 | let module_path = std::module_path!().splitn(2, "::").nth(1); 32 | $crate::testsupport::enable(module_path); 33 | } 34 | 35 | #[test] 36 | fn procspawn_test_helper() { 37 | $crate::init(); 38 | } 39 | }; 40 | } 41 | 42 | pub fn enable(module: Option<&str>) { 43 | if TEST_MODE.swap(true, Ordering::SeqCst) { 44 | panic!("procspawn testmode can only be enabled once"); 45 | } 46 | 47 | if let Some(module) = module { 48 | let ptr = Box::into_raw(Box::new(module.to_string())); 49 | TEST_MODULE.store(ptr, Ordering::SeqCst); 50 | } 51 | 52 | mark_initialized(); 53 | } 54 | 55 | pub struct TestMode { 56 | pub can_pass_args: bool, 57 | pub should_silence_stdout: bool, 58 | } 59 | 60 | fn test_helper_path() -> String { 61 | match unsafe { TEST_MODULE.load(Ordering::SeqCst).as_ref() } { 62 | Some(module) => format!("{}::procspawn_test_helper", module), 63 | None => "procspawn_test_helper".to_string(), 64 | } 65 | } 66 | 67 | pub fn update_command_for_tests(cmd: &mut Command) -> Option { 68 | if TEST_MODE.load(Ordering::SeqCst) { 69 | cmd.arg(test_helper_path()); 70 | cmd.arg("--exact"); 71 | cmd.arg("--test-threads=1"); 72 | cmd.arg("-q"); 73 | Some(TestMode { 74 | can_pass_args: false, 75 | should_silence_stdout: !env::args().any(|x| x == "--show-output" || x == "--nocapture"), 76 | }) 77 | } else { 78 | None 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/test_basic.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | use procspawn::{self, spawn}; 6 | 7 | procspawn::enable_test_support!(); 8 | 9 | #[test] 10 | fn test_basic() { 11 | let handle = spawn(true, |b| !b); 12 | let value = handle.join().unwrap(); 13 | 14 | assert!(!value); 15 | } 16 | 17 | #[test] 18 | fn test_panic() { 19 | let handle = spawn((), |()| panic!("something went wrong")); 20 | let err = handle.join().unwrap_err(); 21 | 22 | let panic_info = err.panic_info().unwrap(); 23 | assert_eq!(panic_info.message(), "something went wrong"); 24 | assert!(panic_info.backtrace().is_some()); 25 | 26 | let loc = panic_info.location().unwrap(); 27 | assert_eq!(loc.line(), 19); 28 | assert_eq!(loc.column(), 33); 29 | assert!(loc.file().contains("test_basic.rs")); 30 | } 31 | 32 | #[test] 33 | fn test_kill() { 34 | let mut handle = spawn((), |()| { 35 | thread::sleep(Duration::from_secs(10)); 36 | }); 37 | handle.kill().unwrap(); 38 | let err = handle.join().unwrap_err(); 39 | dbg!(&err); 40 | assert!(err.is_remote_close()); 41 | } 42 | 43 | #[test] 44 | fn test_envvar() { 45 | let val = procspawn::Builder::new() 46 | .env("FOO", "42") 47 | .spawn(23, |val| { 48 | env::var("FOO").unwrap().parse::().unwrap() + val 49 | }) 50 | .join() 51 | .unwrap(); 52 | assert_eq!(val, 42 + 23); 53 | } 54 | 55 | #[test] 56 | fn test_nested() { 57 | let five = spawn(5, |x| { 58 | println!("1"); 59 | let x = spawn(x, |y| { 60 | println!("2"); 61 | y 62 | }) 63 | .join() 64 | .unwrap(); 65 | println!("3"); 66 | x 67 | }) 68 | .join() 69 | .unwrap(); 70 | println!("4"); 71 | assert_eq!(five, 5); 72 | } 73 | 74 | #[test] 75 | fn test_timeout() { 76 | let mut handle = spawn((), |()| { 77 | thread::sleep(Duration::from_secs(10)); 78 | }); 79 | 80 | let err = handle.join_timeout(Duration::from_millis(100)).unwrap_err(); 81 | assert!(err.is_timeout()); 82 | 83 | let mut handle = spawn((), |()| { 84 | thread::sleep(Duration::from_millis(100)); 85 | 42 86 | }); 87 | 88 | let val = handle.join_timeout(Duration::from_secs(2)).unwrap(); 89 | assert_eq!(val, 42); 90 | } 91 | -------------------------------------------------------------------------------- /tests/test_macros.rs: -------------------------------------------------------------------------------- 1 | use procspawn::{self, spawn}; 2 | 3 | procspawn::enable_test_support!(); 4 | 5 | #[test] 6 | fn test_macro_no_args() { 7 | let handle = spawn!(() || false); 8 | let value = handle.join().unwrap(); 9 | assert!(!value); 10 | } 11 | 12 | #[test] 13 | fn test_macro_single_arg() { 14 | let value = 42u32; 15 | 16 | let handle = spawn!((value) || value); 17 | assert_eq!(handle.join().unwrap(), 42); 18 | 19 | let handle = spawn!((value => new_name) || new_name); 20 | assert_eq!(handle.join().unwrap(), 42); 21 | 22 | let ref_value = &value; 23 | 24 | let handle = spawn!((*ref_value) || ref_value); 25 | assert_eq!(handle.join().unwrap(), 42); 26 | 27 | let handle = spawn!((*ref_value => new_name) || new_name); 28 | assert_eq!(handle.join().unwrap(), 42); 29 | 30 | let handle = spawn!((mut value) || { 31 | value += 1; 32 | value 33 | }); 34 | assert_eq!(handle.join().unwrap(), 43); 35 | 36 | let handle = spawn!((value => mut new_name) || { 37 | new_name += 1; 38 | new_name 39 | }); 40 | assert_eq!(handle.join().unwrap(), 43); 41 | } 42 | 43 | #[test] 44 | fn test_macro_two_args() { 45 | let value1 = 42u32; 46 | let value2 = 23u32; 47 | 48 | let handle = spawn!((value1, value2) || value1 + value2); 49 | assert_eq!(handle.join().unwrap(), 42 + 23); 50 | 51 | let handle = spawn!((value1 => new_name1, value2) || new_name1 + value2); 52 | assert_eq!(handle.join().unwrap(), 42 + 23); 53 | 54 | let ref_value = &value1; 55 | 56 | let handle = spawn!((*ref_value, value2) || ref_value + value2); 57 | assert_eq!(handle.join().unwrap(), 42 + 23); 58 | 59 | let handle = spawn!((*ref_value => new_name, value2) || new_name + value2); 60 | assert_eq!(handle.join().unwrap(), 42 + 23); 61 | 62 | let handle = spawn!((mut value1, value2) || { 63 | value1 += 1; 64 | value1 + value2 65 | }); 66 | assert_eq!(handle.join().unwrap(), 43 + 23); 67 | 68 | let handle = spawn!((value1 => mut new_name, value2) || { 69 | new_name += 1; 70 | new_name + value2 71 | }); 72 | assert_eq!(handle.join().unwrap(), 43 + 23); 73 | } 74 | 75 | #[test] 76 | fn test_macro_three_args() { 77 | let value1 = 42u32; 78 | let value2 = 23u32; 79 | let value3 = 99u32; 80 | 81 | let handle = spawn!((value1, value2, value3) || value1 + value2 + value3); 82 | assert_eq!(handle.join().unwrap(), 42 + 23 + 99); 83 | 84 | let handle = spawn!((value1 => new_name1, value2, value3) || new_name1 + value2 + value3); 85 | assert_eq!(handle.join().unwrap(), 42 + 23 + 99); 86 | 87 | let ref_value = &value1; 88 | 89 | let handle = spawn!((*ref_value, value2, value3) || ref_value + value2 + value3); 90 | assert_eq!(handle.join().unwrap(), 42 + 23 + 99); 91 | 92 | let handle = spawn!((*ref_value => new_name, value2, value3) || new_name + value2 + value3); 93 | assert_eq!(handle.join().unwrap(), 42 + 23 + 99); 94 | 95 | let handle = spawn!((mut value1, value2, value3) || { 96 | value1 += 1; 97 | value1 + value2 + value3 98 | }); 99 | assert_eq!(handle.join().unwrap(), 43 + 23 + 99); 100 | 101 | let handle = spawn!((value1 => mut new_name, value2, value3) || { 102 | new_name += 1; 103 | new_name + value2 + value3 104 | }); 105 | assert_eq!(handle.join().unwrap(), 43 + 23 + 99); 106 | } 107 | 108 | #[test] 109 | fn test_macro_three_args_rv() { 110 | let value1 = 42u32; 111 | let value2 = 23u32; 112 | let value3 = 99u32; 113 | 114 | let handle = 115 | spawn!((value1, value2, value3) || -> Option<_> { Some(value1 + value2 + value3) }); 116 | assert_eq!(handle.join().unwrap(), Some(42 + 23 + 99)); 117 | 118 | let handle = spawn!((value1 => new_name1, value2, value3) || -> Option<_> { Some(new_name1 + value2 + value3) }); 119 | assert_eq!(handle.join().unwrap(), Some(42 + 23 + 99)); 120 | } 121 | -------------------------------------------------------------------------------- /tests/test_pool.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::time::Duration; 3 | 4 | use procspawn::{self, Pool}; 5 | 6 | procspawn::enable_test_support!(); 7 | 8 | #[test] 9 | fn test_basic() { 10 | let pool = Pool::new(4).unwrap(); 11 | let mut handles = vec![]; 12 | 13 | for x in 0..16 { 14 | handles.push(pool.spawn(x, |x| { 15 | if x % 4 == 0 { 16 | panic!("completely broken"); 17 | } 18 | thread::sleep(Duration::from_millis(200)); 19 | })); 20 | } 21 | 22 | let mut ok = 0; 23 | let mut failed = 0; 24 | for mut handle in handles { 25 | if handle.join_timeout(Duration::from_secs(5)).is_ok() { 26 | ok += 1; 27 | } else { 28 | failed += 1; 29 | } 30 | } 31 | 32 | assert_eq!(ok, 12); 33 | assert_eq!(failed, 4); 34 | } 35 | 36 | #[test] 37 | fn test_overload() { 38 | let pool = Pool::new(2).unwrap(); 39 | let mut handles = vec![]; 40 | let mut with_pid = 0; 41 | 42 | for _ in 0..10 { 43 | handles.push(pool.spawn((), |()| { 44 | thread::sleep(Duration::from_secs(10)); 45 | })); 46 | } 47 | 48 | thread::sleep(Duration::from_millis(100)); 49 | for handle in handles.iter() { 50 | if handle.pid().is_some() { 51 | with_pid += 1; 52 | } 53 | } 54 | 55 | assert_eq!(with_pid, 2); 56 | 57 | // kill the pool 58 | pool.kill(); 59 | } 60 | 61 | #[test] 62 | fn test_timeout() { 63 | let pool = Pool::new(2).unwrap(); 64 | 65 | let mut handle = pool.spawn((), |()| { 66 | thread::sleep(Duration::from_secs(10)); 67 | }); 68 | 69 | let err = handle.join_timeout(Duration::from_millis(100)).unwrap_err(); 70 | assert!(err.is_timeout()); 71 | 72 | let mut handle = pool.spawn((), |()| { 73 | thread::sleep(Duration::from_millis(100)); 74 | 42 75 | }); 76 | 77 | let val = handle.join_timeout(Duration::from_secs(2)).unwrap(); 78 | assert_eq!(val, 42); 79 | } 80 | --------------------------------------------------------------------------------