Communicating between a libp2p thread and a UI thread #3812
-
I have a network thread running a libp2p swarm, and I have a UI thread. What would be a convenient design pattern to make the two threads communicate? My idea was to establish two mpsc channels: one for network events and one for UI events. That sounds nice, but there is one problem. The network thread already blocks when awaiting the next swarm event. I'm not sure how to make it wait for both a swarm event or a message from the UI thread (whichever happens to trigger first in that iteration of the loop). I might be able to poll both every 50 ms without blocking, but you guys probably know a cleaner way from personal experience. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
|
Beta Was this translation helpful? Give feedback.
-
The general idea would be to use channels while using a oneshot for returning any values you are awaiting on while polling swarm. So to break it down you would do something like the following: Have an enum being something like enum Command {
CommandOne(oneshot::Sender<Result<(), Error>>)
}
To make it easier you could have a behaviour that emits your commands from your sender to swarm so you could process it while polling swarm. Eg this is an old code I tried (not compatible with current version of libp2p but not hard to update) use core::task::{Context, Poll};
use futures::{channel::mpsc::Receiver, StreamExt};
use libp2p::swarm::{
self, dummy::ConnectionHandler as DummyConnectionHandler, NetworkBehaviour, PollParameters,
};
use std::pin::Pin;
type NetworkBehaviourAction<T> = swarm::NetworkBehaviourAction<T, void::Void>;
pub struct Behaviour<T> {
rx: Receiver<T>,
}
impl<T> Behaviour<T> {
pub fn new(rx: Receiver<T>) -> Self {
Behaviour { rx }
}
}
impl<T> NetworkBehaviour for Behaviour<T>
where
T: 'static + Send,
{
type ConnectionHandler = DummyConnectionHandler;
type OutEvent = T;
fn new_handler(&mut self) -> Self::ConnectionHandler {
DummyConnectionHandler
}
fn on_swarm_event(&mut self, _: swarm::FromSwarm<Self::ConnectionHandler>) {}
fn on_connection_handler_event(
&mut self,
_peer_id: libp2p::PeerId,
_connection_id: swarm::ConnectionId,
_event: swarm::THandlerOutEvent<Self>,
) {
}
fn poll(
&mut self,
cx: &mut Context,
_: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<T>> {
match Pin::new(&mut self.rx).as_mut().poll_next_unpin(cx) {
Poll::Ready(Some(event)) => Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)),
_ => Poll::Pending,
}
}
} Then in your main behaviour, you would simply To see examples, you could take a look at beetle, rust-ipfs (i maintain this project), ipfs-embed to see how they allow polling the swarm in a separate task while sending commands |
Beta Was this translation helpful? Give feedback.
Right, a
Receiver
implementsStream
and so doesSwarm
. Meaning, via theStreamExt
extension trait, you can call.next()
on it and do:This will give you an
Either
whereEither::Left
contains a tuple of:(Some(command), unfinished_swarm_future)
andEither::Right
contains a tuple of(Some(swarm_event), unfinished_receiver_future)
.Both,
Receiver
andSwarm
return cancellation-safe futures, meaning you can just drop the unfinished futures, i.e. do nothing with them.Here is a good example from a workshop I did recently: https://github.com/thomaseizinger/libp2p-workshop/blob/iteration-4/src/event_loop.rs