Send messages to clients with multiple references to websockets - websocket

My question here is in the context of using actix-web with Rust.
Unfortunately I can't explain this without a somewhat hefty code example, so let me start with that.
struct MyWs {
game: Arc<RwLock<Game>>,
}
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Text(text)) => {
debug!("Echoing text with {:?}", text);
self.game.write().unwrap().some_method();
ctx.text(text)
},
_ => (),
}
}
}
struct Game {
websockets: Vec<Arc<RwLock<MyWs>>>,
}
impl Game {
pub fn new() -> GameWrapper {
GameWrapper {
websockets: vec![],
}
}
pub fn add_websocket(&mut self, my_ws: Arc<RwLock<MyWs>>) {
self.websockets.push(my_ws);
}
pub fn some_method(&mut self) {
// Do something to influence internal state.
self.push_state();
}
pub fn push_state(&self) {
for w in self.websockets {
// I'm not sure about this part, idk how to access the
// WebsocketContext with which I can send stuff back to the client.
let game_state = get_game_state_or_something();
w.write().unwrap().ctx.text(self.game_state);
}
}
}
struct GameWrapper {
pub game: Arc<RwLock<Game>>,
}
impl GameWrapper {
pub fn new(game: Arc<RwLock<Game>>) -> GameWrapper {
GameWrapper { game }
}
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
let game = Arc::new(RwLock::new(Game::new()));
let game_wrapper = RwLock::new(GameWrapper::new(game.clone()));
let game_wrapper_data = web::Data::new(game_wrapper);
HttpServer::new(move || {
App::new()
.app_data(game_wrapper_data.clone())
.route("/play_game", web::get().to(play_game))
})
.bind(ip_port)?
.run()
.await
}
pub async fn play_game(
req: HttpRequest,
stream: web::Payload,
game_wrapper: web::Data<GameWrapper>,
) -> impl Responder {
let my_ws = MyWs { game: game_wrapper.game.clone() };
let my_ws = Arc::new(RwLock::new(my_ws));
let mut game = game_wrapper.game.write().unwrap();
game.add_websocket(my_ws);
let resp = ws::start(my_ws, &req, stream); // This is the problem.
let resp = match resp {
Ok(resp) => resp,
Err(e) => return HttpResponse::from_error(e),
};
debug!("Successfully upgraded to websocket");
resp
}
Let me explain what I'm trying to do first. When I client connects, I establish a websocket with them. I need a list of these websockets, so when something changes in Game, I can push an update to all clients.
I bind the play_game function as the handler for the play_game route. In this function, I upgrade the HTTP get request to a websocket. IBefore that, I make a copy of an Arc+RwLock of a Game and pass it into MyWs, the websocket struct. You can see in the handle function of the MyWs impl of StreamHandler that I modify the Game (with the some_method function). This is fine so far.
Things explode when I try to get multiple references to the websocket. You can see in play_game that I call add_websocket, giving Game a reference to it, so it can push updates back to all clients when something changes. For example, after calling some_method, we would call push_updates. The problem with this, is ws::start doesn't take in an Arc, it must take in an Actor that impls StreamHandler with a WebSocketContext.
So my main two issues are:
I need a way to keep multiple references to the websocket, so I can talk to the client from multiple locations (read: threads).
I need some way to even do this. I'm not sure in actix how to actually send messages back to the client outside of the context of my MyWs actor. The framework passes in the WebSocketContext to handle, but I don't know how to get my hands on this myself.
My ideas for fixing this:
In the handle (or started) function of MyWs, pass out a reference to Context into self.game. This doesn't work because I'm moving out a mutable ref.
Make my own ws::start that can take a reference. I haven't tried this yet because it seems like I'd end up rewriting a lot.
Somehow impl Actor and StreamHandler on an Arc, or my own struct with interior mutability / something that allows me to keep multiple references to it.
This doesn't really help me send messages back because I still don't know how to send messages back via the websocket outside of the context of the handle function.
Sorry for the length of this question. The tl;dr is, how do I get multiple references to a websocket in actix-web and send messages to the client with them?
Here are the relevant docs for each of the components I'm using:
https://docs.rs/actix-web-actors/2.0.0/actix_web_actors/ws/fn.start.html
https://docs.rs/actix-web-actors/2.0.0/actix_web_actors/ws/struct.WebsocketContext.html
https://actix.rs/docs/websockets/

Okay so the solution to my dilemma here was unsurprisingly to change the way I was trying to solve this problem. Instead of holding multiple references to the websockets, what I really need is references to each of the actors that hold the websocket. I figure this is how you're meant to do it, given Actix is an actor framework.
This means the code should look like this:
impl Game {
...
pub fn register_actor(&mut self, actor: Addr<MyWs>) {
self.actors.push(actor);
}
}
pub async fn play_game(
req: HttpRequest,
stream: web::Payload,
game_wrapper: web::Data<GameWrapper>,
) -> impl Responder {
let my_ws = MyWs { game: game_wrapper.game.clone() };
let my_ws = Arc::new(RwLock::new(my_ws));
let mut game = game_wrapper.game.write().unwrap();
let res = ws::start_with_addr(my_ws, &req, stream);
let (addr, resp) = match res {
Ok(res) => res,
Err(e) => return HttpResponse::from_error(e),
};
game_manager.register_actor(handle, addr);
debug!("Successfully upgraded to websocket");
resp
}
You can then send messages to the actor instead via the Addr<MyWs>.
I'm going to leave the question for a while in case others have ideas for how to do this whole thing better.

Related

How should I initialize interface types from the windows crate?

I have a struct that uses some types from the windows crate, but I'm not able to initialize them:
use windows::Win32::{
IUIAutomationFocusChangedEventHandler, IUIAutomationFocusChangedEventHandler_Vtbl,
};
// Here's my struct:
pub struct EventHandler {
// A struct member to handle the event:
event: IUIAutomationFocusChangedEventHandler,
event_vtbl: IUIAutomationFocusChangedEventHandler_Vtbl,
}
// Anyone with experience in the windows API
// Will understand the Virtual tables, and this code.
impl EventHandler {
pub fn new() -> EventHandler {
// Here, I should return a new instance of my struct:
EventHandler {
// Now, I should initialize every struct member:
event: IUIAutomationFocusChangedEventHandler {}, // ...
event_vtbl: IUIAutomationFocusChangedEventHandler_Vtbl {
// This struct needs two members:
base__: IUnknown {}, // IUnknown requires a lot of
// methods and member initialization to initialize it.
// Also the IUIAutomationFocusChangedEvent needs too member initialization....
},
}
}
}
These structs shouldn't be initialized in C++ winapi. I don't know what should I do. Every struct needs member initialization, and every member needs other members, and other members need member initialization!
I feel like I'm in a whirlpool! Am I missing something?
Answering the literal question first: You could, but you probably shouldn't have to.
COM support in the windows crate exposes many types, and not all of them are meant for immediate use by client code. The *_Vtbl structures specifically represent the raw function pointer tables used by COM internally to dispatch interface calls. They are declared and populated by the library and not intended to be used by clients directly (the #[doc(hidden)] attribute is a hint, though I'm sure the library structure and documentation experience can be improved).
Attempting to populate the v-tables in client code puts you into a miserable situation. Luckily, none of that is required, as briefly explained in the FAQ:
How do I implement an existing COM interface?
If you need to implement a COM interface for a type, you'll need to add the implement feature which (like any Cargo feature) can be enabled in your project's Cargo.toml file.
windows = { version = "..", features = ["implement"] }
Then you'll need to declare that your type implements a particular interface by adding the #[implement] proc macro to your type and then writing an impl block for the interface. For an interface called IMyInterface you will need to implement the IMyInterface_Impl trait (note the trailing _Impl in the name).
#[windows::core::implement(IMyInterface)]
struct MyStruct;
impl IMyInterface_Impl for MyStruct {
fn MyMethod(&self) -> windows::core::HRESULT {
todo!("Your implementation goes here");
}
}
Version 0.37.0 made significant changes to the implement macro, making this far more approachable than it may appear. Let's start out by declaring a simple structure with a bit of state information:
#[implement(IUIAutomationFocusChangedEventHandler)]
struct EventHandler {
count: Cell<u64>,
}
impl EventHandler {
fn new() -> Self {
Self {
count: Cell::new(0),
}
}
/// Increments the count and returns the new value
fn increment(&self) -> u64 {
let new_val = self.count.get() + 1;
self.count.set(new_val);
new_val
}
}
This keeps a cumulative count of focus change events that happened. Note that the implementation isn't actually correct: Since the event handler can be called from multiple threads we'd actually need a type that's Sync (which Cell isn't). That's something you'd need to change1.
What's missing is the IUIAutomationFocusChangedEventHandler interface implementation. It only has a single member, so that's easy (the IUnknown implementation is conveniently provided for you by the library already):
impl IUIAutomationFocusChangedEventHandler_Impl for EventHandler {
fn HandleFocusChangedEvent(&self, _sender: &Option<IUIAutomationElement>) -> Result<()> {
let count = self.increment();
println!("Focus changed (cumulative count: {})", count);
Ok(())
}
}
For every focus change event it first increments the cumulative count and then prints a message to STDOUT.
That's all that's required to implement a custom IUIAutomationFocusChangedEventHandler interface. Using that from a program isn't much harder, either, even though there are a lot of pitfalls (see comments):
fn main() -> Result<()> {
// Initialize COM for the current thread. Since we are running event handlers on this
// thread, it needs to live in the MTA.
// See [Understanding Threading Issues](https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-threading)
// for more information.
unsafe { CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED) }?;
// Instantiate a `CUIAutomation` object
let uia: IUIAutomation =
unsafe { CoCreateInstance(&CUIAutomation, None, CLSCTX_INPROC_SERVER) }?;
// Subscribe to the focus changed event; this transfers ownership of `handler` into
// `uia`, making it the sole owner
let handler = EventHandler::new();
unsafe { uia.AddFocusChangedEventHandler(None, &handler.into()) }?;
// Display a message box so that we have an easy way to quit the program
let _ = unsafe {
MessageBoxW(
None,
w!("Click OK to end the program"),
w!("UIA Focus Monitor"),
MB_OK,
)
};
// Optionally unsubscribe from all events; this is not strictly required since we have
// to assume that the `CUIAutomation` object properly manages the lifetime of our
// `EventHandler` object
unsafe { uia.RemoveAllEventHandlers() }?;
// IMPORTANT: Do NOT call `CoUninitialize()` here. `uia`'s `Drop` implementation will
// get very angry at us when it runs after COM has been uninitialized
Ok(())
}
To compile the code you'll want to use the following imports:
use std::{cell::Cell, ptr};
use windows::{
core::{implement, Result},
w,
Win32::{
System::Com::{
CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED,
},
UI::{
Accessibility::{
CUIAutomation, IUIAutomation, IUIAutomationElement,
IUIAutomationFocusChangedEventHandler, IUIAutomationFocusChangedEventHandler_Impl,
},
WindowsAndMessaging::{MessageBoxW, MB_OK},
},
},
};
and this Cargo.toml file:
[package]
name = "uia_focus_change"
version = "0.0.0"
edition = "2021"
[dependencies.windows]
version = "0.39.0"
features = [
"implement",
"Win32_Foundation",
"Win32_System_Com",
"Win32_UI_Accessibility",
"Win32_UI_WindowsAndMessaging",
]
1 Possible alternatives include an AtomicU64 and a Mutex. An atomic is perfectly sufficient here, is easy to use, and will properly work in situations of re-entrancy:
use std::sync::atomic::{AtomicU64, Ordering};
#[implement(IUIAutomationFocusChangedEventHandler)]
struct EventHandler {
count: AtomicU64,
}
impl EventHandler {
fn new() -> Self {
Self {
count: AtomicU64::new(0),
}
}
/// Increments the count and returns the new value
fn increment(&self) -> u64 {
self.count.fetch_add(1, Ordering::SeqCst) + 1
}
}
A mutex, on the other hand, is substantially harder to use, its behavior in part unspecified, and equipped with lots of opportunities to fail. On the upside it is more versatile in protecting arbitrarily large structures:
use std::sync::Mutex;
#[implement(IUIAutomationFocusChangedEventHandler)]
struct EventHandler {
count: Mutex<u64>,
}
impl EventHandler {
fn new() -> Self {
Self {
count: Mutex::new(0),
}
}
/// Increments the count and returns the new value
fn increment(&self) -> u64 {
let mut guard = self.count.lock().expect("Failed to lock mutex");
*guard += 1;
*guard
}
}
Either one works and is compatible with COM objects that live in the MTA.

Integrate actix websocket with rabbitmq (lapin) in Rust

I've written a websocket server in Rust using actix. If anyone wants to check the full repo here https://github.com/fabracht/actix-websocket.
Now, I want to integrate rabbitmq into the project. For that, I found the lapin crate https://docs.rs/lapin/1.8.0/lapin/. But I'm having problems integrating it with the actix framework.
I would like to use my current implementation of the websocket to proxy the messages from rabbitmq back to the client.
This is the beginning of my attempt. Very early stages, so let me know if I'm going the wrong way, because now would be the time to change the approach.
First some context:
The websocket actor communicates with another actor that holds information about the sockets and the rooms.
pub struct WSConn {
pub id: Uuid,
room_id: Uuid,
hb: Instant,
lobby_address: Addr<Lobby>,
}
impl WSConn {
pub fn new(lobby: Addr<Lobby>, rabbit: Addr<MyRabbit>) -> Self {
Self {
id: Uuid::new_v4(),
room_id: Uuid::nil(),
hb: Instant::now(),
lobby_address: lobby,
}
}
fn hb(&self, context: &mut WebsocketContext<Self>) {
/// HEARTBEAT CODE GOES HERE ///
}
}
So, when the actor starts, I send a connect message to the Lobby, that handles all the logic for adding the connection to the Lobby struct.
impl Actor for WSConn {
type Context = WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
info!("Starting hearbeat");
self.hb(ctx);
let wsserver_address = ctx.address();
info!("A new client has connected with id {}", self.id);
self.lobby_address.send(Connect {
address: wsserver_address.recipient(),
room_id: self.room_id,
id: self.id
}).into_actor(self).then(|res, _, ctx| {
match res {
Ok(_) => (),
_ => ctx.stop()
}
fut::ready(())
}).wait(ctx);
}
fn stopping(&mut self, _ctx: &mut Self::Context) -> Running {
info!("Stopping actor");
self.lobby_address.do_send(Disconnect {
room_id: self.room_id,
id: self.id
});
Running::Stop
}
}
Here's what the lobby looks like:
type Socket = Recipient<WSServerMessage>;
pub struct Lobby {
pub sessions: DashMap<Uuid, Socket>,//self id to self
pub rooms: DashMap<Uuid, DashSet<Uuid>>,//room id to list of users id
}
The entire Lobby code is quite big, so I won't put it here. Let me know if you need to see that and I'll provide the code.
So, once the client connects, it gets assigned to the default room. When a client sends a message to the server, the message gets processed by the StreamHandler.
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSConn {
fn handle(&mut self, item: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
match item.unwrap() {
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Ping(bin) => {
self.hb = Instant::now();
ctx.pong(&bin);
}
ws::Message::Pong(_) => self.hb = Instant::now(),
ws::Message::Close(reason) => {
ctx.close(reason);
ctx.stop();
}
ws::Message::Text(text) => {
let command = serde_json::from_str::<Command>(&text)
.expect(&format!("Can't parse message {}", &text));
info!("{:?}", command);
if command.command.starts_with("/") {
info!("This is a {} request", command.command);
match command.command.as_ref() {
"/join" => {
info!("Join Room {}", command.payload);
let uid = Uuid::from_str(command.payload.as_str().unwrap()).expect("Can't parse message {} to uuid");
self.lobby_address.send(Join {
current_room: self.room_id,
room_id: uid,
id: self.id
}).into_actor(self).then(|res, _, ctx| {
match res {
Ok(_) => (),
_ => ctx.stop()
}
fut::ready(())
}).wait(ctx);
self.room_id = uid;
},
_ => ()
}
} else {
info!("Text is {}", text);
self.lobby_address.do_send(ClientActorMessage {
id: self.id,
msg: command,
room_id: self.room_id,
});
}
}
_ => {
info!("Something weird happened. Closing");
self.lobby_address.do_send(Disconnect {
room_id: self.room_id,
id: self.id
});
ctx.stop();}
}
}
}
So, as you can see, if you send a message with the command /join and a payload with a valid uuidv4, you join the room. I'm only allowing the client to be a part of one room at a time. So, when you join one, you're automatically removed from the last one.
Ok, so now let's talk about the rabbitmq connection.
The way I thought about this was to use a connection pool to keep the rabbitmq connection and use that connection to create the channels.
So I started by defining the struct that will hold my connection pool.
use actix::{Actor, Context, Handler, StreamHandler};
use deadpool_lapin::{Config, Pool, Runtime};
use deadpool_lapin::lapin::Error;
use lapin::message::Delivery;
use crate::lapin_server::messages::CreateChannel;
pub struct MyRabbit {
pub pool: Pool,
}
impl MyRabbit {
pub fn new() -> Self {
let mut cfg = Config::default();
cfg.url = Some("amqps://ghqcmhat:KbhPAA309QRg7TjdgFEV14pQRheoh44P#codfish.rmq.cloudamqp.com/ghqcmhat".into());
let new_pool = cfg.create_pool(Some(Runtime::Tokio1)).expect("Can't create pool");
MyRabbit {
pool: new_pool
}
}
}
impl Actor for MyRabbit {
type Context = Context<Self>;
}
Having this as an actor allows me to start the actor as I start the websocket server. This is done in main.
#[actix_web::main]
async fn main() -> std::io::Result<()>{
std::env::set_var("RUST_LOG", "actix_web=info,info");
env_logger::init();
// Start Lobby actor and get his address
let websocket_lobby = Lobby::default().start();
let rabbit = MyRabbit::new().start();
let application_data = web::Data::new(Appdata::new());
info!("Starting server on 127.0.0.1:8080");
let server = HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.route("/ws/",web::get().to(websocket_handler))
.app_data(Data::new(websocket_lobby.clone()))
.app_data(Data::new(rabbit.clone()))
.app_data(application_data.clone())
});
server.bind("127.0.0.1:8080")?.run().await
}
To accommodate for the new actor, I added a new parameter to my request handler.
pub async fn websocket_handler(request: HttpRequest, stream: web::Payload, srv: Data<Addr<Lobby>>, rab: Data<Addr<MyRabbit>>, data: Data<Appdata>) -> Result<HttpResponse, Error> {
let mut counter = data.counter.lock().unwrap();
counter.add_assign(1);
info!("This is request # {}", counter);
let ws = WSConn::new(srv.get_ref().clone(), rab.get_ref().clone());
let response = ws::start(ws, &request, stream);
debug!("Response: {:?}", &response);
response
}
Now, my WSConn struct looks like this.
pub struct WSConn {
pub id: Uuid,
room_id: Uuid,
hb: Instant,
lobby_address: Addr<Lobby>,
rabbit_address: Addr<MyRabbit>
}
I know that, if I want to consume from a topic in rabbitmq, I need the exchange name, the type, the routing key and the queue name.
So, I put these in a struct as well
pub struct Channel {
pub queue_name: String,
pub exchange_name: String,
pub exchange_type: ExchangeKind,
pub routing_key: String
}
impl Default for Channel {
fn default() -> Self {
Self {
queue_name: "".to_string(),
exchange_name: "".to_string(),
exchange_type: Default::default(),
routing_key: "".to_string()
}
}
}
But that's where I'm stuck. First, I'm not sure this deadpool_lapin is the right crate to use for this. I'm also not sure how to translate the example on lapin's page, which uses
async_global_executor::block_on
And spawns new threads using async_global_executor::spawn to consume messages.
So, again, what I want is to be able to proxy messages coming from the websocket to rabbitmq and vice versa.
So, if a client connects to the websocket and sends a message like:
{
command: "SUBSCRIBE"
payload: "topic_name"
}
The result should be that messages published on that topic will get sent to him.
Sending an UNSUBSCRIBE should undo that.
Any help here would be greatly appreciated.
Please let me know if more information is needed.
Thank you
Fabricio

Rust Actix Actor send message to actor

How to send a message to another actor?
pub struct MyWs {
}
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Text(message)) => {
//considering that here he sent the message to self
ctx.text(message);
//how to do something like this
//find the actor by index (or uuid) and send text
//actors[0].text(message);
//
},
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
Ok(ws::Message::Close(reason)) => ctx.close(reason),
_ => (),
}
}
}
#[get("/ws")]
pub async fn websocket(req: HttpRequest, stream: web::Payload,) -> actix_web::Result<HttpResponse> {
let resp = ws::start(
MyWs {},
&req,
stream,
);
return resp;
}
Could I make a hashMap of actors?
pub struct MyWs { sessions: HashMap<Uuid, Socket> }
and later
self.sessions.text(message)
I'm new to rust and I don't see a way to save the socket (the context or actor) to find it and send it messages.
You might want to check the official example of a chat room app using actix web.
https://github.com/actix/examples/blob/743af0ff1a9be6fb1cc13e6583108463c89ded4d/websockets/chat/src/main.rs
There are three key points:
create another server actor and get the address of it.
let server = server::ChatServer::new(app_state.clone()).start();
set the server actor's address as application data of HttpServer.
HttpServer::new(move || {
App::new()
.data(app_state.clone())
// copy server actor's address into app
.data(server.clone())
.service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found()
.header("LOCATION", "/static/websocket.html")
.finish()
})))
.route("/count/", web::get().to(get_count))
// websocket
.service(web::resource("/ws/").to(chat_route))
// static resources
.service(fs::Files::new("/static/", "static/"))
})
.bind("127.0.0.1:8080")?
.run()
.await
store the address while starting websocket actor in function chat_route. And then you could use the address to send messages whenever you want
/// Entry point for our websocket route
async fn chat_route(
req: HttpRequest,
stream: web::Payload,
srv: web::Data<Addr<server::ChatServer>>,
) -> Result<HttpResponse, Error> {
ws::start(
WsChatSession {
id: 0,
hb: Instant::now(),
room: "Main".to_owned(),
name: None,
addr: srv.get_ref().clone(),
},
&req,
stream,
)
}

I want to keep a reference inside an HashMap but I'm not able to specify correctly the lifetime

I'm using ws-rs to build a chat app. I need to keep associations between a Sender and a Username but I'm having issues in referencing the Sender in my HashMap.
I'm 99.99% sure that Handler keeps the ownership of Sender.
I had solved this problem cloning every time the sender passing it to another thread, together with the username, via a mspc::channel but I wanna try to use smart pointers and reference.
Here is a Minimal, Reproducible Example:
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
trait Factory {
fn connection_made(&mut self, _: Sender) -> MHandler;
}
trait Handler {
fn on_open(&mut self) -> ();
}
struct MFactory<'a> {
connections: Arc<HashMap<String, &'a Sender>>,
}
struct MHandler<'a> {
sender: Sender,
connections: Arc<HashMap<String, &'a Sender>>,
}
struct Sender{}
fn main() {
let mut connections: Arc<HashMap<String, &Sender>> = Arc::new(HashMap::new());
// Server thread
let server = thread::Builder::new()
.name(format!("server"))
.spawn(|| {
let mFactory = MFactory {
connections: connections.clone(),
};
let mHandler = mFactory.connection_made(Sender{});
mHandler.on_open();
})
.unwrap();
}
impl Factory for MFactory<'_> {
fn connection_made(&mut self, s: Sender) -> MHandler {
MHandler {
sender: s,
connections: self.connections.clone(),
}
}
}
impl Handler for MHandler<'_> {
fn on_open(&mut self) -> () {
self.connections.insert(format!("Alan"), &self.sender);
}
}
Playground.
Ps: I'm aware that Arc doesn't guarantee mutual exclusion so I have to wrap my HasMap in a Mutex. I've decided to ignore it for the moment.
What you're trying to do is unsafe. You're keeping in a map that lives for the duration of your program references to a structure that is owned by another object inside a thread. So the map outlives the the objects it stores references to, which Rust prevents.
Following on my comment, this code compiles (I've removed the factory for clarity):
use std::collections::HashMap;
use std::sync::{Arc,Mutex};
use std::thread;
use std::ptr::NonNull;
struct MHandler {
sender: Sender,
}
struct Sender{}
struct Wrapper(NonNull<Sender>);
unsafe impl std::marker::Send for Wrapper { }
fn main() {
let connections: Arc<Mutex<HashMap<String, Wrapper>>> = Arc::new(Mutex::new(HashMap::new()));
// Server thread
let server = thread::Builder::new()
.name(format!("server"))
.spawn(move || {
let mut handler = MHandler {
sender: Sender{},
};
let w = Wrapper(NonNull::new(&mut handler.sender as *mut Sender).unwrap());
Arc::clone(&connections).lock().unwrap().insert(format!("Alan"), w);
})
.unwrap();
}
This is using raw pointers (https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer) and NonNull to be able to implement Send (see https://github.com/rust-lang/rust/issues/21709 and https://play.rust-lang.org/?gist=1ce2532a0eefc60695663c26faddebe1&version=stable)
Not sure this helps you.

How can I lock the internals of my Rust data structure?

I'm trying to implement a collection that stores values in both a vector and a hashmap and this is what I have so far:
pub struct CollectionWrapper {
items: Vec<Item>,
items_map: HashMap<ItemKey, Item>,
}
impl CollectionWrapper {
pub fn new() -> Self {
CollectionWrapper {
items: Vec::new(),
items_map: HashMap::new(),
}
}
pub fn add(&mut self, item: Item) {
let key = item.get_key();
self.items.push(item.clone());
self.items_map.insert(key, item.clone());
}
}
I obviously need some kind of lock. I've looked at the Mutex Rust has, but I do not understand how to use it. When I search for the problem, I only find use cases where people spawn a bunch of threads and synchronize them. I'm looking for something like:
try {
lock.lock();
// insert into both collections
} finally {
lock.unlock();
}
I obviously need some kind of lock
I don't know that I agree with this need. I'd only introduce a lock when multiple threads could be modifying the object concurrently. Note that's two conditions: multiple threads AND concurrent modification.
If you only have one thread, then Rust's enforcement of a single mutable reference to an item will prevent any issues. Likewise, if you have multiple threads and fully transfer ownership of the item between them, you don't need any locking because only one thread can mutate it.
I'm looking for something like:
try {
lock.lock();
// insert into both collections
} finally {
lock.unlock();
}
If you need something like that, then you can create a Mutex<()> — a mutex that locks the unit type, which takes no space:
use std::sync::Mutex;
struct Thing {
lock: Mutex<()>,
nums: Vec<i32>,
names: Vec<String>,
}
impl Thing {
fn new() -> Thing {
Thing {
lock: Mutex::new(()),
nums: vec![],
names: vec![],
}
}
fn add(&mut self) {
let _lock = self.lock.lock().unwrap();
// Lock is held until the end of the block
self.nums.push(42);
self.names.push("The answer".to_string());
}
}
fn main() {
let mut thing = Thing::new();
thing.add();
}
Note that there is no explicit unlock required. When you call lock, you get back a MutexGuard. This type implements Drop, which allows for code to be run when it goes out of scope. In this case, the lock will be automatically released. This is commonly called Resource Acquisition Is Initialization (RAII).
I wouldn't recommend this practice in most cases. It's generally better to wrap the item that you want to lock. This enforces that access to the item can only happen when the lock is locked:
use std::sync::Mutex;
struct Thing {
nums: Vec<i32>,
names: Vec<String>,
}
impl Thing {
fn new() -> Thing {
Thing {
nums: vec![],
names: vec![],
}
}
fn add(&mut self) {
self.nums.push(42);
self.names.push("The answer".to_string());
}
}
fn main() {
let thing = Thing::new();
let protected = Mutex::new(thing);
let mut locked_thing = protected.lock().unwrap();
locked_thing.add();
}
Note that the MutexGuard also implements Deref and DerefMut, which allow it to "look" like the locked type.

Resources