From 746a47f1cdbc19be391717cc6b739c517bef88d3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 17 Feb 2020 22:41:18 +0100 Subject: [PATCH 1/4] Added macros for spawning --- Makefile | 1 + examples/macro.rs | 16 +++++ src/lib.rs | 4 ++ src/macros.rs | 153 +++++++++++++++++++++++++++++++++++++++++++ tests/test_macros.rs | 120 +++++++++++++++++++++++++++++++++ 5 files changed, 294 insertions(+) create mode 100644 examples/macro.rs create mode 100644 src/macros.rs create mode 100644 tests/test_macros.rs diff --git a/Makefile b/Makefile index e69d176..3b4af94 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ testall: cargo run --all-features --example stdout cargo run --all-features --example timeout cargo run --all-features --example async + cargo run --all-features --example macro cargo run --all-features --example bad-serialization cargo run --all-features --example args -- 1 2 3 .PHONY: testall diff --git a/examples/macro.rs b/examples/macro.rs new file mode 100644 index 0000000..083f330 --- /dev/null +++ b/examples/macro.rs @@ -0,0 +1,16 @@ +use procspawn::{self, spawn}; + +fn main() { + procspawn::init(); + + let a = 42u32; + let b = 23u32; + let c = 1; + let handle = spawn!((a => new_name1, b, mut c) || -> Result<_, ()> { + c += 1; + Ok(new_name1 + b + c) + }); + let value = handle.join().unwrap(); + + println!("{:?}", value); +} diff --git a/src/lib.rs b/src/lib.rs index 5968ed2..f1c015b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,6 +171,8 @@ //! shows JSON based workarounds for bincode limitations. //! * [async.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/async.rs): //! demonstrates async usage. +//! * [macro.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/macro.rs): +//! demonstrates async usage. //! //! More examples can be found in the example folder: [examples](https://github.com/mitsuhiko/procspawn/tree/master/examples) @@ -191,6 +193,8 @@ mod asyncsupport; #[doc(hidden)] pub mod testsupport; +mod macros; + pub use self::core::{assert_spawn_is_safe, init, ProcConfig}; pub use self::error::{Location, PanicInfo, SpawnError}; pub use self::pool::{Pool, PoolBuilder}; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..181da02 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,153 @@ +/// Utility macro to spawn functions. +/// +/// Since a very common pattern is to pass multiple values to a spawned +/// function by using tuples it can be cumbersome to repeat the arguments. +/// This macro makes it possible to not repeat oneself. The first argument +/// to the macro is a tuple of the arguments to pass, the second argument +/// is the closure that should be invoked. +/// +/// For each argument the following expressions are possible: +/// +/// * `ident`: uses the local variable `ident` unchanged. +/// * `ident => other`: serializes `ident` and deserializes into `other` +/// * `*ident`: alias for `*ident => ident`. +/// * `mut ident`: alias for `ident => mut ident`. +/// +/// Example: +/// +/// ```rust,no_run +/// let a = 42u32; +/// let b = 23u32; +/// let handle = procspawn::spawn!((a, mut b) || { +/// b += 1; +/// a + b +/// }); +/// ``` +#[macro_export] +macro_rules! spawn { + ($($args:tt)*) => { $crate::_spawn_impl!($crate::spawn, $($args)*) } +} + +/// Utility macro to spawn async functions. +/// +/// This works exactly like the [`spawn!`](macro.spawn.html) macro but instead +/// will invoke [`spawn_async`](fn.spawn_async.html). +#[macro_export] +#[cfg(feature = "async")] +macro_rules! spawn_async { + ($($args:tt)*) => { $crate::_spawn_impl!($crate::spawn_async, $($args)*) } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! _spawn_impl { + ($func:path, () || $($body:tt)*) => { + $func( + (), + |()| + $($body)* + ) + }; + ($func:path, ($($param:tt)*) || $($body:tt)*) => { + $func( + $crate::_spawn_call_arg!($($param)*), + |($crate::_spawn_decl_arg!($($param)*))| + $($body)* + ) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! _spawn_call_arg { + ($expr:expr => mut $x:ident, $($tt:tt)*) => {( + $expr, $crate::_spawn_call_arg!($($tt)*) + )}; + (*$expr:expr => $x:ident, $($tt:tt)*) => {( + *$expr, $crate::_spawn_call_arg!($($tt)*) + )}; + ($expr:expr => $x:ident, $($tt:tt)*) => {( + $expr, $crate::_spawn_call_arg!($($tt)*) + )}; + ($expr:expr => mut $x:ident) => {( + $expr, + )}; + ($expr:expr => $x:ident) => {( + $expr, + )}; + (mut $x:ident, $($tt:tt)*) => {( + $x, $crate::_spawn_call_arg!($($tt)*) + )}; + (mut $x:ident) => {( + $x, + )}; + (*$x:ident, $($tt:tt)*) => {( + *$x, $crate::_spawn_call_arg!($($tt)*) + )}; + ($x:ident, $($tt:tt)*) => {( + $x, $crate::_spawn_call_arg!($($tt)*) + )}; + (*$x:ident) => {( + *$x, + )}; + ($x:ident) => {( + $x, + )}; + + ($unexpected:tt) => { + $crate::_spawn_unexpected($unexpected); + }; + () => (()) +} + +#[macro_export] +#[doc(hidden)] +macro_rules! _spawn_decl_arg { + ($expr:expr => mut $x:ident, $($tt:tt)*) => {( + mut $x, $crate::_spawn_decl_arg!($($tt)*) + )}; + (*$expr:expr => $x:ident, $($tt:tt)*) => {( + $x, $crate::_spawn_decl_arg!($($tt)*) + )}; + ($expr:expr => $x:ident, $($tt:tt)*) => {( + $x, $crate::_spawn_decl_arg!($($tt)*) + )}; + ($expr:expr => mut $x:ident) => {( + mut $x, + )}; + (*$expr:expr => $x:ident) => {( + $x, + )}; + ($expr:expr => $x:ident) => {( + $x, + )}; + (mut $x:ident, $($tt:tt)*) => {( + mut $x, $crate::_spawn_decl_arg!($($tt)*) + )}; + (mut $x:ident) => {( + mut $x, + )}; + (*$x:ident, $($tt:tt)*) => {( + $x, $crate::_spawn_decl_arg!($($tt)*) + )}; + ($x:ident, $($tt:tt)*) => {( + $x, $crate::_spawn_decl_arg!($($tt)*) + )}; + (*$x:ident) => {( + $x, + )}; + ($x:ident) => {( + $x, + )}; + + ($unexpected:tt) => { + $crate::_spawn_unexpected($unexpected); + }; + () => () +} + +#[macro_export] +#[doc(hidden)] +macro_rules! _spawn_unexpected { + () => {}; +} diff --git a/tests/test_macros.rs b/tests/test_macros.rs new file mode 100644 index 0000000..d0d3460 --- /dev/null +++ b/tests/test_macros.rs @@ -0,0 +1,120 @@ +use procspawn::{self, spawn}; + +procspawn::enable_test_support!(); + +#[test] +fn test_macro_no_args() { + let handle = spawn!(() || false); + let value = handle.join().unwrap(); + assert_eq!(value, false); +} + +#[test] +fn test_macro_single_arg() { + let value = 42u32; + + let handle = spawn!((value) || value); + assert_eq!(handle.join().unwrap(), 42); + + let handle = spawn!((value => new_name) || new_name); + assert_eq!(handle.join().unwrap(), 42); + + let ref_value = &value; + + let handle = spawn!((*ref_value) || ref_value); + assert_eq!(handle.join().unwrap(), 42); + + let handle = spawn!((*ref_value => new_name) || new_name); + assert_eq!(handle.join().unwrap(), 42); + + let handle = spawn!((mut value) || { + value += 1; + value + }); + assert_eq!(handle.join().unwrap(), 43); + + let handle = spawn!((value => mut new_name) || { + new_name += 1; + new_name + }); + assert_eq!(handle.join().unwrap(), 43); +} + +#[test] +fn test_macro_two_args() { + let value1 = 42u32; + let value2 = 23u32; + + let handle = spawn!((value1, value2) || value1 + value2); + assert_eq!(handle.join().unwrap(), 42 + 23); + + let handle = spawn!((value1 => new_name1, value2) || new_name1 + value2); + assert_eq!(handle.join().unwrap(), 42 + 23); + + let ref_value = &value1; + + let handle = spawn!((*ref_value, value2) || ref_value + value2); + assert_eq!(handle.join().unwrap(), 42 + 23); + + let handle = spawn!((*ref_value => new_name, value2) || new_name + value2); + assert_eq!(handle.join().unwrap(), 42 + 23); + + let handle = spawn!((mut value1, value2) || { + value1 += 1; + value1 + value2 + }); + assert_eq!(handle.join().unwrap(), 43 + 23); + + let handle = spawn!((value1 => mut new_name, value2) || { + new_name += 1; + new_name + value2 + }); + assert_eq!(handle.join().unwrap(), 43 + 23); +} + +#[test] +fn test_macro_three_args() { + let value1 = 42u32; + let value2 = 23u32; + let value3 = 99u32; + + let handle = spawn!((value1, value2, value3) || value1 + value2 + value3); + assert_eq!(handle.join().unwrap(), 42 + 23 + 99); + + let handle = spawn!((value1 => new_name1, value2, value3) || new_name1 + value2 + value3); + assert_eq!(handle.join().unwrap(), 42 + 23 + 99); + + let ref_value = &value1; + + let handle = spawn!((*ref_value, value2, value3) || ref_value + value2 + value3); + assert_eq!(handle.join().unwrap(), 42 + 23 + 99); + + let handle = spawn!((*ref_value => new_name, value2, value3) || new_name + value2 + value3); + assert_eq!(handle.join().unwrap(), 42 + 23 + 99); + + let handle = spawn!((mut value1, value2, value3) || { + value1 += 1; + value1 + value2 + value3 + }); + assert_eq!(handle.join().unwrap(), 43 + 23 + 99); + + let handle = spawn!((value1 => mut new_name, value2, value3) || { + new_name += 1; + new_name + value2 + value3 + }); + assert_eq!(handle.join().unwrap(), 43 + 23 + 99); +} + +#[test] +fn test_macro_three_args_rv() { + let value1 = 42u32; + let value2 = 23u32; + let value3 = 99u32; + + let handle = + spawn!((value1, value2, value3) || -> Option<_> { Some(value1 + value2 + value3) }); + assert_eq!(handle.join().unwrap(), Some(42 + 23 + 99)); + + let handle = spawn!((value1 => new_name1, value2, value3) || -> Option<_> { Some(new_name1 + value2 + value3) }); + assert_eq!(handle.join().unwrap(), Some(42 + 23 + 99)); +} From 1002bc60f1625c0e64fb44fe653488accc365740 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 17 Feb 2020 22:42:09 +0100 Subject: [PATCH 2/4] Update readme --- README.md | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2714a3b..22ac115 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,31 @@ This crate provides the ability to spawn processes with a function similar to `thread::spawn`. -Unlike `thread::spawn` data cannot be passed in closures but must be -explicitly passed as single argument which must be [`serde`](https://serde.rs/) -serializable. The return value from the spawned closure also must be -serializable and can then be unwrapped from the returned join handle. -If your function has data enclosed it will panic at runtime. +Unlike `thread::spawn` data cannot be passed by the use of closures. Instead +if must be explicitly passed as serializable object (specifically it must be +[`serde`](https://serde.rs/) serializable). The return value from the +spawned closure also must be serializable and can then be retrieved from +the returned join handle. + +If the spawned functiom causes a panic it will also be serialized across +the process boundaries. + +## Example + +First for all of this to work you need to invoke `procspawn::init` at a +point early in your program (somewhere at the beginning of the main function). +Whatever happens before that point also happens in your spawned functions. + +Subprocesses are by default invoked with the same arguments and environment +variables as the parent process. ```rust -// call this early in your main() function. This is where all spawned -// functions will be invoked. procspawn::init(); +``` + +Now you can start spawning functions: +```rust let data = vec![1, 2, 3, 4]; let handle = procspawn::spawn(data, |data| { println!("Received data {:?}", &data); @@ -24,10 +38,12 @@ let result = handle.join().unwrap(); Because `procspawn` will invoke a subprocess and there is currently no reliable way to intercept `main` in Rust it's necessary for you to call -[`procspawn::init`](https://docs.rs/procspawn/latest/procspawn/fn.init.html) at an early time in the program. The -place where this will be called is the entrypoint for the subprocesses -spawned. The subprocess is invoked with the same arguments and environment -variables by default. +[`procspawn::init`](https://docs.rs/procspawn/latest/procspawn/fn.init.html) explicitly an early time in the program. + +Alternatively you can use the [`ProcConfig`](https://docs.rs/procspawn/latest/procspawn/struct.ProcConfig.html) +builder object to initialize the process which gives you some extra +abilities to customize the processes spawned. This for instance lets you +disable the default panic handling. [`spawn`](https://docs.rs/procspawn/latest/procspawn/fn.spawn.html) can pass arbitrary serializable data, including IPC senders and receivers from the [`ipc-channel`](https://crates.io/crates/ipc-channel) @@ -39,7 +55,7 @@ The default way to spawn processes will start and stop processes constantly. For more uses it's a better idea to spawn a [`Pool`](https://docs.rs/procspawn/latest/procspawn/struct.Pool.html) which will keep processes around for reuse. Between calls the processes will stay around which also means the can keep state between calls if -needed. +needed. Pools are currently not supported for async usage. ## Panics @@ -143,7 +159,7 @@ itself does not support Windows yet. Additionally the findshlibs which is used for the `safe-shared-libraries` feature also does not yet support Windows. -## Examples +## More Examples Here are some examples of `procspawn` in action: @@ -155,6 +171,10 @@ Here are some examples of `procspawn` in action: shows how you can wait on a process with timeouts. * [bad-serialization.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/bad-serialization.rs): shows JSON based workarounds for bincode limitations. +* [async.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/async.rs): + demonstrates async usage. +* [macro.rs](https://github.com/mitsuhiko/procspawn/blob/master/examples/macro.rs): + demonstrates async usage. More examples can be found in the example folder: [examples](https://github.com/mitsuhiko/procspawn/tree/master/examples) From 652e950f835671e47385a11cb841afb744debfc1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 17 Feb 2020 22:44:51 +0100 Subject: [PATCH 3/4] Add macro example to overview page --- README.md | 17 +++++++++++++++-- src/lib.rs | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 22ac115..feb491e 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,6 @@ With this done the following behavior applies: * when trying to spawn with intercepted `stdout` be aware that there is extra noise that will be emitted by rusttest. -Example: - ```rust procspawn::enable_test_support!(); @@ -152,6 +150,21 @@ however a few limitations / differences with async support currently: * there is no native join with timeout support. You can use your executors timeout functionality to achieve the same. +## Macros + +Alternatively the [`spawn!`](https://docs.rs/procspawn/latest/procspawn/macro.spawn.html) macro can be used which can +make passing more than one argument easier: + +```rust +let a = 42u32; +let b = 23u32; +let c = 1; +let handle = procspawn::spawn!((a => base, b, mut c) || -> Result<_, ()> { + c += 1; + Ok(base + b + c) +}); +``` + ## Platform Support Currently this crate only supports macOS and Linux because ipc-channel diff --git a/src/lib.rs b/src/lib.rs index f1c015b..90e32df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,8 +110,6 @@ //! * when trying to spawn with intercepted `stdout` be aware that there is //! extra noise that will be emitted by rusttest. //! -//! Example: -//! //! ```rust,no_run //! procspawn::enable_test_support!(); //! @@ -149,6 +147,21 @@ //! * when you drop a join handle the process is being terminated. //! * there is no native join with timeout support. You can use your executors //! timeout functionality to achieve the same. +//! +//! # Macros +//! +//! Alternatively the [`spawn!`](macro.spawn.html) macro can be used which can +//! make passing more than one argument easier: +//! +//! ```rust,no_run +//! let a = 42u32; +//! let b = 23u32; +//! let c = 1; +//! let handle = procspawn::spawn!((a => base, b, mut c) || -> Result<_, ()> { +//! c += 1; +//! Ok(base + b + c) +//! }); +//! ``` //! //! # Platform Support //! From 73dee13f9c8599e35f71c8809a28477fb86260ee Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 17 Feb 2020 22:46:56 +0100 Subject: [PATCH 4/4] rustfmt --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 90e32df..924af76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,12 +147,12 @@ //! * when you drop a join handle the process is being terminated. //! * there is no native join with timeout support. You can use your executors //! timeout functionality to achieve the same. -//! +//! //! # Macros -//! +//! //! Alternatively the [`spawn!`](macro.spawn.html) macro can be used which can //! make passing more than one argument easier: -//! +//! //! ```rust,no_run //! let a = 42u32; //! let b = 23u32;