Using error_chain with JoinHandle - multithreading

I use error_chain for my error handling.
[dependencies]
error-chain = "0.12.2"
I have no problem when I'm only using a single-thread.
#[macro_use] extern crate error_chain;
error_chain! {}
fn main() -> Result<()> {
crash_burn()
.chain_err(|| "crash_burn() crashed and burned!")?;
Ok(())
}
fn crash_burn() -> Result<()> {
bail!("I'm an error.")
}
However, when trying to return an error from a thread I get a compilation error.
#[macro_use] extern crate error_chain;
error_chain! {}
use std::thread::*;
fn main() -> Result<()> {
let child: JoinHandle<Result<()>> = spawn(move || {
crash_burn()
.chain_err(|| "crash_burn() crashed and burned!")?;
Ok(())
});
let res = child.join()
.chain_err(|| "Child thread panicked! This error did not come from crash_burn().")?;
res // res contains the error from crash_burn()
}
Here is the compilation error.
error[E0599]: no method named `chain_err` found for enum `std::result::Result<std::result::Result<(), Error>, std::boxed::Box<(dyn std::any::Any + std::marker::Send + 'static)>>` in the current scope
--> src/main.rs:13:10
|
13 | .chain_err(|| "Child thread panicked! This error did not come from crash_burn().")?;
| ^^^^^^^^^ method not found in `std::result::Result<std::result::Result<(), Error>, std::boxed::Box<(dyn std::any::Any + std::marker::Send + 'static)>>`
Why is this error not handled automatically?

Why is this error not handled automatically?
According to error_chain documentation
chain_err can be called on any Result type where the contained error type implements std::error::Error+Send + 'static
The error type in the Result of child.join() is Box<dyn Any> because a thread can panic with any value.
For example the thread can do
panic!(78);
But std::boxed::Box<dyn Any> does not implement the Error trait (only error types implement that trait).
However in order for err_chain to be called it needs to implement the Error trait. So that is why err_chain cannot be used.

#FlyingFish1414 was correct with why error_chain cannot be used directly with JoinHandle::join(), but I wanted to give a solution that allows chaining errors returned by threads.
fn chain_any(x: std::result::Result<std::result::Result<(), Error>, std::boxed::Box<dyn std::any::Any + std::marker::Send>>) -> Result<()> {
match x {
// Child did not panic, but might have thrown an error.
Ok(x) => x.chain_err(|| "Child threw an error.")?,
// Child panicked, let's try to print out the reason why.
Err(e) => {
println! {"{:?}", e.type_id()};
if let Some(s) = e.downcast_ref::<&str>() {
bail!(format!("Child panicked with this message:\n{}", s));
} else if let Some(s) = e.downcast_ref::<String>() {
bail!(format!("Child panicked with this message:\n{}", s));
}
bail!(format!("Child panicked! Not sure why, here's the panic:\n{:?}", e));
}
}
Ok(())
}
Now just join the thread like such:
chain_any(child.join())
Try it out:
fn crash_burn() -> Result<()> {
// panic!(4);
// panic!("OH no a &str!");
// panic!("OH no a {}", "String");
// bail!("I'm an error.");
Ok(())
}
Unfortunately we must check for every panic type individually if we want to print it out. There is no way (that I know of) to check if the Any returned by the child thread implements Display or Debug. According to the docs a &dyn Any is limited to testing whether a value is of a specified concrete type, and cannot be used to test whether a type implements a trait.

Related

Awaiting a Number of Futures Unknown at Compile Time

I want to leverage Tokio's runtime to handle a variable amount of async futures. Since the count of futures is unknown at compile time, it seems FuturesUnordered is my best option (macros such as select! require specifying your branches at compile time; join_all might be possible but the docs recommend FuturesUnordered "in a lot of cases" when order doesn't matter).
The logic of this snippet is a recv() loop getting pushed to the bucket of futures, which should always run. When new data arrives, its parsing/processing gets pushed to the futures bucket too (instead of being processed immediately). This ensures the receiver maintains low latency in responding to new events, and data processing (potentially computationally expensive decryption) occurs concurrently with all other data processing async blocks (plus the listening receiver).
This thread explains why the futures get .boxed(), by the way.
The problem is this cryptic error:
error[E0277]: `dyn futures::Future<Output = ()> + std::marker::Send` cannot be shared between threads safely
--> src/main.rs:27:8
|
27 | }).boxed());
| ^^^^^ `dyn futures::Future<Output = ()> + std::marker::Send` cannot be shared between threads safely
|
= help: the trait `Sync` is not implemented for `dyn futures::Future<Output = ()> + std::marker::Send`
= note: required because of the requirements on the impl of `Sync` for `Unique<dyn futures::Future<Output = ()> + std::marker::Send>`
= note: required because it appears within the type `Box<dyn futures::Future<Output = ()> + std::marker::Send>`
= note: required because it appears within the type `Pin<Box<dyn futures::Future<Output = ()> + std::marker::Send>>`
= note: required because of the requirements on the impl of `Sync` for `FuturesUnordered<Pin<Box<dyn futures::Future<Output = ()> + std::marker::Send>>>`
= note: required because of the requirements on the impl of `std::marker::Send` for `&FuturesUnordered<Pin<Box<dyn futures::Future<Output = ()> + std::marker::Send>>>`
= note: required because it appears within the type `[static generator#src/main.rs:16:25: 27:6 _]`
= note: required because it appears within the type `from_generator::GenFuture<[static generator#src/main.rs:16:25: 27:6 _]>`
= note: required because it appears within the type `impl futures::Future`
It looks like pushing to an UnorderedFutures "recursively" (not really I guess, but what else would you call it?) doesn't work, but I'm not sure why. This error indicates some Sync trait requirement isn't met for the Box'd & Pin'd async blocks being tended to by the FuturesUnordered -- a requirement I guess is only imposed because &FuturesUnordered (used during futures.push(...) because that method borrows &self) needs it for its Send trait... or something?
use std::error::Error;
use tokio::sync::mpsc::{self, Receiver, Sender};
use futures::stream::futures_unordered::FuturesUnordered;
use futures::FutureExt;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn Error>> {
let mut futures = FuturesUnordered::new();
let (tx, rx) = mpsc::channel(32);
tokio::spawn( foo(tx) ); // Only the receiver is relevant; its transmitter is
// elsewhere, occasionally sending data.
futures.push((async { // <--- NOTE: futures.push()
loop {
match rx.recv().await {
Some(data) => {
futures.push((async move { // <--- NOTE: nested futures.push()
let _ = data; // TODO: replace with code that processes 'data'
}).boxed());
},
None => {}
}
}
}).boxed());
while let Some(_) = futures.next().await {}
Ok(())
}
I will leave the low-level error for another answer, but I believe a more idiomatic way to solve the high-level problem here would be to combine the use of FuturesUnordered with something like tokio::select! as follows:
use tokio::sync::mpsc;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
#[tokio::main]
pub async fn main() {
let mut futures = FuturesUnordered::new();
let (tx, mut rx) = mpsc::channel(32);
//turn foo into something more concrete
tokio::spawn(async move {
let _ = tx.send(42i32).await;
});
loop {
tokio::select! {
Some(data) = rx.recv() => {
futures.push(async move {
data.to_string()
});
},
Some(result) = futures.next() => {
println!("{}", result)
},
else => break,
}
}
}
You can read more about the select macro here: https://tokio.rs/tokio/tutorial/select
When you box the future created by the async block with the boxed method, you are trying to coerce it to a dyn Future + Send:
pub fn boxed<'a>(
self
) -> Pin<Box<dyn Future<Output = Self::Output> + 'a + Send>>
However, the created future is not Send. Why? Because inside of it, you try to push to the FuturesUnordered, which borrows it:
pub fn push(&self, future: Fut)
This means that the async block captures a &FuturesUnordered. For a type to be Send, all it's fields must be Send, so for the generated future to be Send, &FuturesUnordered must be Send.
For a reference to be Send, the type must also be Sync:
impl<'_, T> Send for &'_ T where
T: Sync
And for FuturesUnordered to be Sync, the stored futures must also be Sync:
impl<Fut: Sync> Sync for FuturesUnordered<Fut> {}
However, the future returned by boxed is not necessarily Sync:
pub fn boxed<'a>(
self
) -> Pin<Box<dyn Future<Output = Self::Output> + 'a + Send>>
Which means that the async generator is not Send, so you cannot coerce it to a dyn Future + Send, and you get a confusing error message.
The solution is to add a Sync bound to the future, and Box::pin manually:
type BoxedFuture = Pin<Box<dyn Future<Output = ()> + Send + Sync>>;
let mut futures = FuturesUnordered::<BoxedFuture>::new();
futures.push(Box::pin(async {
loop {
match rx.recv().await {
Some(data) => {
futures.push(Box::pin(async move {
let _ = data;
}));
}
None => {}
}
}
}));
However, you will then run into a bunch of borrowing issues. A better solution would be to use tokio::select! instead of the outer push, as explained by Michael's answer.

Propagating errors from within a closure in a thread in Rust [duplicate]

This question already has answers here:
Why do try!() and ? not compile when used in a function that doesn't return Option or Result?
(4 answers)
Closed 3 years ago.
I want to propagate an error from a function called inside a closure inside a call to thread::spawn.
I've tried using a JoinHandle to capture the result of thread::spawn, but I get various errors doing that.
fn start_server(args) -> Result<(), Box<dyn std::error::Error>> {
...
thread::spawn(move || {
// I want to do this (put a ? after run_server)
run_server(args)?;
...
}
...
});
fn run_server(args) -> Result<(), std::io::Error> {
...
}
I get this message
| run_server(args)?;
| ^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
I want to propagate an error from a function called inside a closure inside a call to thread::spawn
Since threads are running in parallel, throwing the error up from the thread scope is not making sense. Better approach will be error handling in the thread itself.
So generally you should not propagate the error to the upper level above the thread.
However you can throw your error that you get in your parallel threads after you joined them to your main thread. This way it will be pretty much like error propagation in synchronous.
Here is how you can manage it:
fn start_server() -> Result<(), Box<std::error::Error>> {
let x = std::thread::spawn(move || -> Result<(), std::io::Error> {
run_server()?;
Ok(())
});
x.join().unwrap()?; // Now you can throw your error up because you joined your thread.
// ...
Ok(())
}
fn run_server() -> Result<(), std::io::Error> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "hello"))
}
fn main() {
let x = start_server();
println!("{:?}", x);
}
Playground

The example from the "chaining computations" section of the Tokio docs does not compile: "expected struct `std::io::Error`, found ()"

I am learning Tokio. I read Getting asynchronous from the official docs, but the source code from the Chaining computations section cannot be compiled under the latest Rust version (Rust 2018, v1.31):
extern crate tokio;
extern crate bytes;
#[macro_use]
extern crate futures;
use tokio::io::AsyncWrite;
use tokio::net::{TcpStream, tcp::ConnectFuture};
use bytes::{Bytes, Buf};
use futures::{Future, Async, Poll};
use std::io::{self, Cursor};
// HelloWorld has two states, namely waiting to connect to the socket
// and already connected to the socket
enum HelloWorld {
Connecting(ConnectFuture),
Connected(TcpStream, Cursor<Bytes>),
}
impl Future for HelloWorld {
type Item = ();
type Error = io::Error;
fn poll(&mut self) -> Poll<(), io::Error> {
use self::HelloWorld::*;
loop {
let socket = match *self {
Connecting(ref mut f) => {
try_ready!(f.poll())
}
Connected(ref mut socket, ref mut data) => {
// Keep trying to write the buffer to the socket as long as the
// buffer has more bytes it available for consumption
while data.has_remaining() {
try_ready!(socket.write_buf(data));
}
return Ok(Async::Ready(()));
}
};
let data = Cursor::new(Bytes::from_static(b"hello world"));
*self = Connected(socket, data);
}
}
}
fn main() {
let addr = "127.0.0.1:1234".parse().unwrap();
let connect_future = TcpStream::connect(&addr);
let hello_world = HelloWorld::Connecting(connect_future);
// Run it
tokio::run(hello_world)
}
The compiler outputs error messages:
error[E0271]: type mismatch resolving `<HelloWorld as futures::Future>::Error == ()`
--> src\main.rs:54:5
|
54 | tokio::run(hello_world)
| ^^^^^^^^^^ expected struct `std::io::Error`, found ()
|
= note: expected type `std::io::Error`
found type `()`
= note: required by `tokio::run`
Is the problem caused by the version of my Rust compiler? How do I fix it?
Tokio::run has the following signature:
pub fn run<F>(future: F)
where
F: Future<Item = (), Error = ()> + Send + 'static,
which means that it accepts a Future which accepts a () as Item and has () as the error type.
On the other hand, your HelloWorld impl has
type Item = ();
type Error = io::Error;
which means they are not compatible, you have to convert the io::Error to () somehow.
I would suggest using map_err
tokio::run(hello_world.map_err(|e| Err(e).unwrap()))
to handle the error in case something bad happens. You could of course make better error handling, but this will work.
Interestingly enough, I have JavaScript disabled in my browser and therefore I see the comments in the Rustdoc on the webpage:
fn main() {
let addr = "127.0.0.1:1234".parse().unwrap();
let connect_future = TcpStream::connect(&addr);
let hello_world = HelloWorld::Connecting(connect_future);
# let hello_world = futures::future::ok::<(), ()>(());
// Run it
tokio::run(hello_world)
}
The # means that Rustdoc should not print that line, but should execute it while testing. I think this is a mistake/oversight and there is also an open issue and a fix pending. PR has been merged, webpage has been updated.

Is Option<i32> unwind safe?

I am implementing a wrapper of a C library which takes callbacks and the callbacks will be implemented in Rust. Given that panicking in Rust when calling from C is undefined behavior, I want to catch any potential Rust panics before they get into C.
I have been reading about std::panic::catch_unwind. The wrapper is performance sensitive and I would prefer to avoid using types like Mutex. I would like to hold my result in an Option<i32> and set this to Some(value) in the case where there is no panic. None will indicate that the function did not execute successfully and thus, a panic must have occurred.
Is Option<i32> unwind safe? If not, under what conditions would there be a problem? Can I wrap it in std::panic::AssertUnwindSafe?
Here is an example where I used AssertUnwindSafe to wrap the entire closure.
use std::panic::{self, AssertUnwindSafe};
fn random_function_that_might_panic(a: i32) -> i32 {
if a == 42 {
panic!("did you forget a towel?");
}
a * 2
}
fn do_not_panic(a: i32) {
let mut result = None;
let unwind_state = panic::catch_unwind(AssertUnwindSafe(|| {
result = Some(random_function_that_might_panic(a)); // get result, but this could panic
}));
match unwind_state {
Ok(()) => {
match result {
Some(value) => {
println!("Result: {:?}", value);
}
None => {
// this should never happen...
println!("No result but no panic?");
}
}
}
Err(e) => {
println!("caught panic: {:?}", e);
}
}
}
fn main() {
do_not_panic(1);
do_not_panic(2);
do_not_panic(3);
do_not_panic(42);
}
(See the above on the playground.)
I could not figure out how to wrap just Option<i32> in AssertUnwindSafe, so here I wrapped the whole closure. How would I wrap just the Option<i32>?
Is Option<i32> unwind safe?
Yes. There's no reason to ask a human this question when you can ask the compiler:
fn implements<T: std::panic::UnwindSafe>() {}
fn main() {
implements::<Option<i32>>();
}
Your real question should be:
Is &mut Option<i32> unwind safe?
This is not, but you probably already knew that. I'm guessing that you got this compiler error before you added AssertUnwindSafe which tells you it isn't safe:
error[E0277]: the trait bound `&mut std::option::Option<i32>: std::panic::UnwindSafe` is not satisfied in `[closure#src/main.rs:12:44: 14:6 result:&mut std::option::Option<i32>, a:&i32]`
--> src/main.rs:12:24
|
12 | let unwind_state = panic::catch_unwind(|| {
| ^^^^^^^^^^^^^^^^^^^ the type &mut std::option::Option<i32> may not be safely transferred across an unwind boundary
|
= help: within `[closure#src/main.rs:12:44: 14:6 result:&mut std::option::Option<i32>, a:&i32]`, the trait `std::panic::UnwindSafe` is not implemented for `&mut std::option::Option<i32>`
= note: required because it appears within the type `[closure#src/main.rs:12:44: 14:6 result:&mut std::option::Option<i32>, a:&i32]`
= note: required by `std::panic::catch_unwind`
I'd write your code as this, for what it's worth:
fn do_not_panic(a: i32) {
let result = panic::catch_unwind(|| random_function_that_might_panic(a)).ok();
match result {
Some(value) => {
println!("Result: {:?}", value);
}
None => {
println!("caught panic");
}
}
}
No mutable variables, no extra nesting, no "this should never happen" comments.
See also:
The documentation for UnwindSafe
Is there a way to tell the Rust compiler to call drop on partially-initialized array elements when handling a panic?

How can I send a function to another thread?

I am attempting to write a simpler unit test runner for my Rust project. I have created a TestFixture trait that my test fixture structs will implement, similar to inheriting from the unit test base class in other testing frameworks. The trait is fairly simple. This is my test fixture
pub trait TestFixture {
fn setup(&mut self) -> () {}
fn teardown(&mut self) -> () {}
fn before_each(&mut self) -> () {}
fn after_each(&mut self) -> () {}
fn tests(&mut self) -> Vec<Box<Fn(&mut Self)>>
where Self: Sized {
Vec::new()
}
}
My test running function is as follows
pub fn test_fixture_runner<T: TestFixture>(fixture: &mut T) {
fixture.setup();
let _r = fixture.tests().iter().map(|t| {
let handle = thread::spawn(move || {
fixture.before_each();
t(fixture);
fixture.after_each();
});
if let Err(_) = handle.join() {
println!("Test failed!")
}
});
fixture.teardown();
}
I get the error
src/tests.rs:73:22: 73:35 error: the trait `core::marker::Send` is not implemented for the type `T` [E0277]
src/tests.rs:73 let handle = thread::spawn(move || {
^~~~~~~~~~~~~
note: in expansion of closure expansion
src/tests.rs:69:41: 84:6 note: expansion site
src/tests.rs:73:22: 73:35 note: `T` cannot be sent between threads safely
src/tests.rs:73 let handle = thread::spawn(move || {
^~~~~~~~~~~~~
note: in expansion of closure expansion
src/tests.rs:69:41: 84:6 note: expansion site
src/tests.rs:73:22: 73:35 error: the trait `core::marker::Sync` is not implemented for the type `for<'r> core::ops::Fn(&'r mut T)` [E0277]
src/tests.rs:73 let handle = thread::spawn(move || {
^~~~~~~~~~~~~
note: in expansion of closure expansion
src/tests.rs:69:41: 84:6 note: expansion site
src/tests.rs:73:22: 73:35 note: `for<'r> core::ops::Fn(&'r mut T)` cannot be shared between threads safely
src/tests.rs:73 let handle = thread::spawn(move || {
^~~~~~~~~~~~~
note: in expansion of closure expansion
I have tried adding Arcs around the types being sent to the thread, no dice, same error.
pub fn test_fixture_runner<T: TestFixture>(fixture: &mut T) {
fixture.setup();
let fix_arc = Arc::new(Mutex::new(fixture));
let _r = fixture.tests().iter().map(|t| {
let test_arc = Arc::new(Mutex::new(t));
let fix_arc_clone = fix_arc.clone();
let test_arc_clone = test_arc.clone();
let handle = thread::spawn(move || {
let thread_test = test_arc_clone.lock().unwrap();
let thread_fix = fix_arc_clone.lock().unwrap();
(*thread_fix).before_each();
(*thread_test)(*thread_fix);
(*thread_fix).after_each();
});
if let Err(_) = handle.join() {
println!("Test failed!")
}
});
fixture.teardown();
}
A sample test fixture would be something like
struct BuiltinTests {
pwd: PathBuf
}
impl TestFixture for BuiltinTests {
fn setup(&mut self) {
let mut pwd = env::temp_dir();
pwd.push("pwd");
fs::create_dir(&pwd);
self.pwd = pwd;
}
fn teardown(&mut self) {
fs::remove_dir(&self.pwd);
}
fn tests(&mut self) -> Vec<Box<Fn(&mut BuiltinTests)>> {
vec![Box::new(BuiltinTests::cd_with_no_args)]
}
}
impl BuiltinTests {
fn new() -> BuiltinTests {
BuiltinTests {
pwd: PathBuf::new()
}
}
}
fn cd_with_no_args(&mut self) {
let home = String::from("/");
env::set_var("HOME", &home);
let mut cd = Cd::new();
cd.run(&[]);
assert_eq!(env::var("PWD"), Ok(home));
}
#[test]
fn cd_tests() {
let mut builtin_tests = BuiltinTests::new();
test_fixture_runner(&mut builtin_tests);
}
My whole intention of using threads is isolation from the test runner. If a test fails an assertion it causes a panic which kills the runner. Thanks for any insight, I'm willing to change my design if that will fix the panic problem.
There are several problems with your code, I'll show you how to fix them one by one.
The first problem is that you're using map() to iterate over an iterator. It won't work correctly because map() is lazy - unless you consume the iterator, the closure you passed to it won't run. The correct way is to use for loop:
for t in fixture().tests().iter() {
Second, you're iterating the vector of closures by reference:
fixture.tests().iter().map(|t| {
iter() on a Vec<T> returns an iterator yielding items of type &T, so your t will be of type &Box<Fn(&mut Self)>. However, Box<Fn(&mut T)> does not implement Sync by default (it is a trait object which have no information about the underlying type except that you specified explicitly), so &Box<Fn(&mut T)> can't be used across multiple threads. That's what the second error you see is about.
Most likely you don't want to use these closures by reference; you probably want to move them to the spawned thread entirely. For this you need to use into_iter() instead of iter():
for t in fixture.tests().into_iter() {
Now t will be of type Box<Fn(&mut T)>. However, it still can't be sent across threads. Again, it is a trait object, and the compiler does not know if the type contained inside is Send. For this you need to add Send bound to the type of the closure:
fn tests(&mut self) -> Vec<Box<Fn(&mut Self)+Send>>
Now the error about Fn is gone.
The last error is about Send not being implemented for T. We need to add a Send bound on T:
pub fn test_fixture_runner<T: TestFixture+Send>(fixture: &mut T) {
And now the error becomes more comprehensible:
test.rs:18:22: 18:35 error: captured variable `fixture` does not outlive the enclosing closure
test.rs:18 let handle = thread::spawn(move || {
^~~~~~~~~~~~~
note: in expansion of closure expansion
test.rs:18:5: 28:6 note: expansion site
test.rs:15:66: 31:2 note: captured variable is valid for the anonymous lifetime #1 defined on the block at 15:65
test.rs:15 pub fn test_fixture_runner<T: TestFixture+Send>(fixture: &mut T) {
test.rs:16 fixture.setup();
test.rs:17
test.rs:18 for t in fixture.tests().into_iter() {
test.rs:19 let handle = thread::spawn(move || {
test.rs:20 fixture.before_each();
...
note: closure is valid for the static lifetime
This error happens because you're trying to use a reference in a spawn()ed thread. spawn() requires its closure argument to have 'static bound, that is, its captured environment must not contain references with non-'static lifetimes. But that's exactly what happens here - &mut T is not 'static. spawn() design does not prohibit avoiding joining, so it is explicitly written to disallow passing non-'static references to the spawned thread.
Note that while you're using &mut T, this error is unavoidable, even if you put &mut T in Arc, because then the lifetime of &mut T would be "stored" in Arc and so Arc<Mutex<&mut T>> also won't be 'static.
There are two ways to do what you want.
First, you can use the unstable thread::scoped() API. It is unstable because it is shown to allow memory unsafety in safe code, and the plan is to provide some kind of replacement for it in the future. However, you can use it in nightly Rust (it won't cause memory unsafety by itself, only in specifically crafted situations):
pub fn test_fixture_runner<T: TestFixture+Send>(fixture: &mut T) {
fixture.setup();
let tests = fixture.lock().unwrap().tests();
for t in tests.into_iter() {
let f = &mut *fixture;
let handle = thread::scoped(move || {
f.before_each();
t(f);
f.after_each();
});
handle.join();
}
fixture.teardown();
}
This code compiles because scoped() is written in such a way that it guarantees (in most cases) that the thread won't outlive all captured references. I had to reborrow fixture because otherwise (because &mut references aren't copyable) it would be moved into the thread and fixture.teardown() would be prohibited. Also I had to extract tests variable because otherwise the mutex will be locked by the main thread for the duration of the for loop which would naturally disallow locking it in the child threads.
However, with scoped() you can't isolate the panic in the child thread. If the child thread panics, this panic will be rethrown from join() call. This may or may not be a problem in general, but I think it is a problem for your code.
Another way is to refactor your code to hold the fixture in Arc<Mutex<..>> from the beginning:
pub fn test_fixture_runner<T: TestFixture + Send + 'static>(fixture: Arc<Mutex<T>>) {
fixture.lock().unwrap().setup();
for t in fixture.lock().unwrap().tests().into_iter() {
let fixture = fixture.clone();
let handle = thread::spawn(move || {
let mut fixture = fixture.lock().unwrap();
fixture.before_each();
t(&mut *fixture);
fixture.after_each();
});
if let Err(_) = handle.join() {
println!("Test failed!")
}
}
fixture.lock().unwrap().teardown();
}
Note that now T has also to be 'static, again, because otherwise it couldn't be used with thread::spawn() as it requires 'static. fixture inside the inner closure is not &mut T but a MutexGuard<T>, and so it has to be explicitly converted to &mut T in order to pass it to t.
This may seem overly and unnecessarily complex, however, such design of a programming language does prevent you from making many errors in multithreaded programming. Each of the above errors we have seen is valid - each of them would be a potential cause of memory unsafety or data races if it was ignored.
As stated in the Rust HandBook's Concurrency section:
When a type T implements Send, it indicates to the compiler that something of this type is able to have ownership transferred safely between threads.
If you do not implement Send, ownership cannot be transfered between threads.

Resources