0

I have a ClientManager object, which manages join/leave actions of websocket clients (using simple_websockets library), by fetching events from the lib's event_hub. I create it in main():

1:   let event_hub = simple_websockets::launch(8080)
2:      .expect("failed to listen on port 8080");
3:   let client_manager = ClientManager::new(event_hub);

The endless loop, which is processing events, is implemented in ClientManager::run() method, so I launch it in a separate thread:

4:   thread::spawn(|| client_manager.run() );

It handles the attaching and detaching clients, works as excepted. The problem comes when I want to use the client_manager for other tasks, say, send a message to each attached clients:

5:   client_manager.broadcast(String::from("hello"));
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move

I understand, that the ownership of client_manager is transferred to the closure, so I couldn't use it anymore, but in this case, I'm not happy with this situation. client_manager is running, I want to sent requests to it, but I already lost it at thread creation.

Can I start a thread without closure?

Probably, my whole conception is wrong, and I should not use threads for this task.

ern0
  • 3,074
  • 25
  • 40
  • If you want shared ownership you can use one of the reference counted types: [Need holistic explanation about Rust's cell and reference counted types](https://stackoverflow.com/questions/45674479/need-holistic-explanation-about-rusts-cell-and-reference-counted-types) – cafce25 Feb 25 '23 at 14:01

1 Answers1

1

The usual pattern for sending messages between threads is to use channels. This is outlined in "The Rust Programming Language" in this chapter.

Essentially you would have a sender for this channel (where the receiver is inside the other thread, presumably in the loop you mentioned) and would pass different messages into this channel. Usually this would be done using an enum like similar:

enum ManagerMessage {
    Broadcast(String),
    Quit,
    // etc
}

A simple example:

use std::sync::mpsc::channel;
use std::thread;

enum Message {
    Print(String),
    Quit
}

fn main() {
    let (tx, rx) = channel();

    let handle = thread::spawn(move || {
        loop {
            match rx.recv().unwrap() {
                Message::Print(message) => println!("{}", message),
                Message::Quit => break
            }
        }
    });

    tx.send(Message::Print("Hello world".into())).unwrap();
    tx.send(Message::Quit).unwrap();

    handle.join().unwrap()
}
Kendas
  • 1,963
  • 13
  • 20
  • My `client_manager` has a thread, which is fetching the queue, handling client actions (connect, disconnect etc.). I don't want to launch another thread just to react to external requests. I just want to share (with thread launcher) and use my `client_manager`. – ern0 Feb 25 '23 at 17:05
  • The `mpsc` part there stands for _Multi-producer, single-consumer_. It is possible to clone the `Sender` and have every external thread passing in messages like this. But if this is not sufficient, I suggest to look into shared references, although you will need to implement locking or other synchronization methods in order to make it work. – Kendas Feb 25 '23 at 17:10
  • Yes, I need shared references, and also do proper locking. But anyway, channels will be useful for other purposes, it's strange for me that the characteristics (*mpsc*) is explicitly defined. – ern0 Feb 25 '23 at 17:46
  • There is the [`crossbeam-channel`](https://docs.rs/crossbeam/latest/crossbeam/channel/index.html) crate for a Multi-producer, Multi-consumer channel implementation if you wish to use it. The implementation has been [merged into the Rust standard library](https://github.com/rust-lang/rust/pull/93563) and there seem to be efforts to expose the functionality as new APIs in the future (though don't quote me on that). – Kendas Feb 26 '23 at 11:35