multithreading - How can I send a function to another thread? -


i attempting write simpler unit test runner rust project. have created testfixture trait test fixture structs implement, similar inheriting unit test base class in other testing frameworks. trait simple. 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)>>         self: sized {         vec::new()     } } 

my test running function 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 error

src/tests.rs:73:22: 73:35 error: trait `core::marker::send` not implemented 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 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: trait `core::marker::sync` not implemented 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 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 types being sent 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 like

struct builtintests {     pwd: pathbuf }  impl testfixture 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 isolation test runner. if test fails assertion causes panic kills runner. insight, i'm willing change design if fix panic problem.

there several problems code, i'll show how fix them 1 one.

the first problem you're using map() iterate on iterator. won't work correctly because map() lazy - unless consume iterator, closure passed won't run. correct way use for loop:

for t in fixture().tests().iter() { 

second, you're iterating vector of closures reference:

fixture.tests().iter().map(|t| { 

iter() on vec<t> returns iterator yielding items of type &t, t of type &box<fn(&mut self)>. however, box<fn(&mut t)> not implement sync default (it trait object have no information underlying type except specified explicitly), &box<fn(&mut t)> can't used across multiple threads. that's second error see about.

most don't want use these closures reference; want move them spawned thread entirely. need use into_iter() instead of iter():

for t in fixture.tests().into_iter() { 

now t of type box<fn(&mut t)>. however, still can't sent across threads. again, trait object, , compiler not know if type contained inside send. need add send bound type of closure:

fn tests(&mut self) -> vec<box<fn(&mut self)+send>> 

now error fn gone.

the last error send not being implemented t. need add send bound on t:

pub fn test_fixture_runner<t: testfixture+send>(fixture: &mut t) { 

and error becomes more comprehensible:

test.rs:18:22: 18:35 error: captured variable `fixture` not outlive 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 valid anonymous lifetime #1 defined on block @ 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     t in fixture.tests().into_iter() { test.rs:19         let handle = thread::spawn(move || { test.rs:20             fixture.before_each();            ... note: closure valid static lifetime 

this error happens because you're trying use reference in spawn()ed thread. spawn() requires closure argument have 'static bound, is, captured environment must not contain references non-'static lifetimes. that's happens here - &mut t not 'static. spawn() design not prohibit avoiding joining, explicitly written disallow passing non-'static references spawned thread.

note while you're using &mut t, error unavoidable, if put &mut t in arc, because lifetime of &mut t "stored" in arc , arc<mutex<&mut t>> won't 'static.

there 2 ways want.

first, can use unstable thread::scoped() api. unstable because it shown allow memory unsafety in safe code, , plan provide kind of replacement in future. however, can use in nightly rust (it won't cause memory unsafety itself, in crafted situations):

pub fn test_fixture_runner<t: testfixture+send>(fixture: &mut t) {     fixture.setup();      let tests = fixture.lock().unwrap().tests();     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() written in such way guarantees (in cases) thread won't outlive captured references. had reborrow fixture because otherwise (because &mut references aren't copyable) moved thread , fixture.teardown() prohibited. had extract tests variable because otherwise mutex locked main thread duration of loop naturally disallow locking in child threads.

however, scoped() can't isolate panic in child thread. if child thread panics, panic rethrown join() call. may or may not problem in general, think is problem code.

another way refactor code hold fixture in arc<mutex<..>> beginning:

pub fn test_fixture_runner<t: testfixture + send + 'static>(fixture: arc<mutex<t>>) {     fixture.lock().unwrap().setup();      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 t has 'static, again, because otherwise couldn't used thread::spawn() requires 'static. fixture inside inner closure not &mut t mutexguard<t>, , has explicitly converted &mut t in order pass t.

this may seem overly , unnecessarily complex, however, such design of programming language prevent making many errors in multithreaded programming. each of above errors have seen valid - each of them potential cause of memory unsafety or data races if ignored.


Comments

Popular posts from this blog

How to connect android app to App engine -

gcc - MinGW's ld cannot perform PE operations on non PE output file -

php - display validation error message next to the textbox in codeigniter -