Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added macros for spawning #25

Merged
merged 4 commits into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 48 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -96,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!();

Expand Down Expand Up @@ -136,14 +150,29 @@ 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
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:

Expand All @@ -155,6 +184,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)

Expand Down
16 changes: 16 additions & 0 deletions examples/macro.rs
Original file line number Diff line number Diff line change
@@ -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);
}
21 changes: 19 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!();
//!
Expand Down Expand Up @@ -150,6 +148,21 @@
//! * 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
//!
//! Currently this crate only supports macOS and Linux because ipc-channel
Expand All @@ -171,6 +184,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)

Expand All @@ -191,6 +206,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};
Expand Down
153 changes: 153 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -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 {
() => {};
}
Loading