Problem with moving object into a thread spawn scope


#1

Hi there,
I have this kind of object

struct A {
......
......
}

let a = Arc::new(A{});
let moved_a = a.clone();
thread::spawn(move || {
    moved_a.run();
});

And I’m getting a lot of this kind of errors for my struct fields

error: the trait bound `std::cell::Cell<std::option::Option<usize>>: std::marker::Sync` is not satisfied

How to spawn thread with Arc ?


#2

The problem is not with Arc, but with the Cell. Cells are not thread safe, so Rust rightfully protects you from sharing the same cell across several threads. To fix the problem, you should either use a Mutex around your struct, or use atomic types inside of it.

Why cells are not thread safe? Imagine, for example, a huge, but still Copy struct S (like struct S([u64; 16])). Clearly, several operations are needed to write this structure to the memory. Imagine now that two threads try to .set the same Cell<S> at the same time. As each set is in fact several operations, the result may be neither from the first thread nor from the second, but some random interleaving (and UB technically I believe).


#3

@matklad thanks for your replay and explanation, but using Mutex didn’t solved my problem :unamused:
Now I have

struct A {
......
......
}

let a = Arc::new(Mutex::new(A{}));
let moved_a = a.clone();
thread::spawn(move || {
    let mut shared_A = moved_a.lock().unwrap();
    shared_A.run();
});

And still getting the same error. It seem like I can’t declare create object from A struct from outside of Thread and then move it, but that issue is getting very annoyed because I can’t find solution almost 2 days !
Most interesting part is that I need to change a lot of code logic in order to declare object from A inside thread.
Is there anything else that I can try ?


#4

Can you show a more complete example? The definition of A (and how it is used ) is what matters most here. That said, I think you just don’t need any Cells in A, because you are protecting it by the mutex anyway, and so you should be able to mutate it via &mut reference.


#5

And I think the underlying problem you are struggling with is Send and Sync traits. Those are “magical” traits which describe which data can and can’t be shared across threads. Sadly, there seems to be no definite documentation on the topic at the moment. There are only https://doc.rust-lang.org/nomicon/send-and-sync.html and https://doc.rust-lang.org/std/marker/trait.Sync.html


#6

I’m using MIO to implement event loop based networking, so this is the base networking structure. So I need to run this inside new thread, but based on code structure using Arc<TcpNetwork> reference I need to create cuple other objects in main thread.

pub struct TcpNetwork {
    pub connections: TcpConns,
    pub is_api: bool,
    pub event_loop: EventLoop<TcpNetwork>,

    // main server socket
    pub server_sock: Vec<TcpListener>,
    pub server_address: String,
}

// After making some objects I just need to call "run" function in separate thread

Thanks for this link https://doc.rust-lang.org/nomicon/send-and-sync.html, now my code compiles very well. I just implemented unsafe Send and Sync traits.
Will let you know if code works fine, but based on Rust compiler logic, if it’s compiles then it should work !


#7

but based on Rust compiler logic, if it’s compiles then it should work !

This is not the case if you are using unsafe keyword. I may be wrong, but I think that unsafe impl Send opens a door to memory unsafety, segfaults and vulnerabilities in your case :frowning:

unsafe is really scary.

And there is no need to add unsafe: if you can’t share a struct between threads, it should boil down to some kind of Cell. Cells provide interior mutability, but are memory unsafe. But, as you are protecting the struct with the mutex, the is no need in Cells, because the mutex itself provides capabilities for interior mutability.

Is your project on GitHub? Perhaps I can take a closer look and help to sort this out.


#8

What is also a bit strange is that run takes a &mut EventLoop, and this usually means that the thread which called run should own the event loop. So storing a loop inside an Arc is strange, because shared ownership of event loop does not make sense.


#9

No it’s a private project.
I have a lot of code done with C++ Boost ASIO, but after I found some benchmarks about MIO I just wanted to rewrite some parts with it to see is there real performance differences.
But after a week of coding I’m still fighting with compiler.

As you told unsafe is really unsafe ! Code compiling well but I’m getting None option when I’m trying to extract mutable variable from Arc, and program is crashing. Still trying to understand how to deal with it.
I’m sharing code part which is not working.

pub fn new(server_address: &str, is_api: bool, readers_count: usize) -> Arc<TcpNetwork> {
        let addr = SocketAddr::from_str(server_address).unwrap();

        let net = Arc::new(TcpNetwork {
            connections: TcpConns::new(10),
            server_sock: TcpListener::bind(&addr).ok().expect("Error binding server"),
            is_api: is_api,
            server_address: String::from(server_address),
            readers_index: readers_count,
            readers: Vec::new(),
            event_loop: EventLoop::new().ok().expect("Unable to create event loop for networking")
        });


        net.clone()
    }
............
............
pub fn run(server_address: &str, is_api: bool, readers_count: usize) -> Sender<NetLoopCmd> {
        let mut sv = String::from(server_address);
        let (chan_sender, chan_reader) = channel();
        thread::spawn(move || {
            let mut net = TcpNetwork::new(sv.as_str(), is_api, readers_count);
 
            // it looks ugly, but I'm trying to extract mutable variable from Arc
            let mut n = net.clone();
            let mut n1 = net.clone();
            let mut nn = match Arc::get_mut(&mut n) {
                Some(n) => n,
                None => {
                    print!("Can't get mutable value from Arc");
                    return;
                }
            };
            let mut nn1 = Arc::get_mut(&mut n1).unwrap();
            chan_sender.send(net.event_loop.channel());

            let mut n_r = net.clone();
            let mut n_r2 = net.clone();
            let mut n_rm = Arc::get_mut(&mut n_r).unwrap();
            let mut n_rm2 = Arc::get_mut(&mut n_r2).unwrap();
            n_rm.register_server(&mut n_rm2.event_loop);

            println!("Running event loop");

            nn1.event_loop.run(&mut nn);
        });

        return chan_reader.recv().unwrap();
    }

So I need just to create some object from TcpNetwork, return event loop channel for multithread communication from other sources, and just run event loop.
What could I change or rewrite for doing that ?


#10

Ah, now I understand a bit more, and looks like the overall structure of data must be changed.

The main issue, irrelevant to multithreading actually, is that TcpNetwork is both an owner and a handler for the event loop.

That is, you are trying to do something like

struct H {
    loop: EventLoop<H>
}

fn main() {
    let mut h = H::new(...)
    h.loop.run(&mut h)   
}

This is the shape of code that is impossible in Rust. In the h.loop.run(&mut h) line you have two mutable borrows of h, which is prohibited by Rust. This answer talks a lot about this problem: http://stackoverflow.com/a/32300133. And it is really tricky: took me much more than a week to understand.

So you need to store EventLoop and the application state in different structs. Here is an example of this:


#11

And a meta note: mio is really the lowest level possible above the platform APIs. Perhaps you can use a higher level IO library? Though, I don’t know which one is the preferred. I think it should be Tokio (https://github.com/tokio-rs/tokio-proto), but it is a bit too fresh at the moment =)