Avoid deadlock in rust when multiple spawns execute code in a loop - multithreading

I am trying to run 2 threads in parallel and share some data between them. When either one of the threads contain a loop statement, the shared data in the other thread goes into a deadlock.
But if I were to add a line to code to break out of the loop statement after a certain number of iterations, the deadlock gets released and the operation in the next thread starts.
Rust Playground
Code:
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Clone, Copy)]
struct SomeNetwork {
is_connected: bool,
}
impl SomeNetwork {
fn connection_manager(&mut self) {
loop {
// if I exit the loop after a few iterations then the deadlock is removed
// eg: when I use `for i in 0..10 {` instead of `loop`
println!("connection_manager thread...");
thread::sleep(Duration::from_millis(2000));
}
}
fn api_calls(&self) {
loop {
if self.is_connected {
//make_an_api_call()
}
println!("api_calls thread...");
thread::sleep(Duration::from_millis(5000));
}
}
pub fn start() {
let self_arc = SomeNetwork {
is_connected: false,
};
let self_arc = Arc::new(Mutex::new(self_arc));
let self_cloned1 = Arc::clone(&self_arc);
let self_cloned2 = Arc::clone(&self_arc);
thread::Builder::new()
.spawn(move || {
let mut n = self_cloned1.lock().unwrap();
n.connection_manager();
})
.unwrap();
thread::Builder::new()
.spawn(move || {
let n = self_cloned2.lock().unwrap(); // <---- deadlock here
n.api_calls();
})
.unwrap();
loop {
thread::sleep(Duration::from_millis(5000))
}
}
}
fn main() {
SomeNetwork::start();
}
Output:
connection_manager thread...
connection_manager thread...
connection_manager thread...
connection_manager thread...
connection_manager thread...
....
Wouldn't the underlying OS take care of the scheduling once a thread goes into sleep?
What could be done here, so that I can run both threads in parallel?

The issue is the mutex you created stays locked during connection_manager.
The way you use a mutex in Rust is that it wraps the data it locks. When you lock the mutex, it blocks the current thread until it can obtain the mutex. Once it has, it gives you a MutexGuard which you can think of as a wrapper for a reference to the mutex. The MutexGuard gives you mutable access to the data inside the mutex. Then once the MutexGuard is no longer needed Rust invokes MutexGuard's implementation of Drop which unlocks the mutex and allows other threads to obtain it.
// Block until mutex is locked for this thread and return MutexGuard
let mut n = self_cloned1.lock().unwrap();
// Do stuff with the locked mutex
n.connection_manager();
// MutexGuard is no longer needed so it gets dropped and the mutex is released
As you can see, if connection_manager never exits the mutex will remain locked for the first thread to obtain the mutex.
What you want is probably to use a mutex with a condvar so the mutex can be released while the thread is sleeping.
Edit:
Here is a rough idea of what that using condvars to handle connecting and channels to pass jobs to workers would look like. Playground Link
use std::sync::{Arc, Mutex, Condvar};
use std::thread::{self, current};
use std::time::Duration;
use crossbeam_channel::{unbounded, Receiver};
#[derive(Clone, Copy)]
struct SomeNetwork {
is_connected: bool,
}
const TIMEOUT: Duration = Duration::from_secs(5);
impl SomeNetwork {
fn connect(&mut self) {
println!("connection_manager thread...");
self.is_connected = true;
}
fn api_calls(&self, job: i32) {
//println!("api_calls thread...");
println!("[Worker {:?}] Handling job {}", current().id(), job);
thread::sleep(Duration::from_millis(50))
}
pub fn start_connection_thread(
self_data: Arc<Mutex<Self>>,
connect_condvar: Arc<Condvar>,
worker_condvar: Arc<Condvar>,
) {
thread::Builder::new()
.spawn(move || {
let mut guard = self_data.lock().unwrap();
loop {
// Do something with the data
if !guard.is_connected {
guard.connect();
// Notify all workers that the connection is ready
worker_condvar.notify_all();
}
// Use condvar to release mutex and wait until signaled to start again
let (new_guard, _) = connect_condvar.wait_timeout(guard, TIMEOUT).unwrap();
guard = new_guard;
}
})
.unwrap();
}
pub fn start_worker_thread(
self_data: Arc<Mutex<Self>>,
connect_condvar: Arc<Condvar>,
worker_condvar: Arc<Condvar>,
requests: Receiver<i32>,
) {
thread::Builder::new()
.spawn(move || {
loop {
// Wait until a request is received
let request = requests.recv().unwrap();
// Lock mutex once we have a request
let mut guard = self_data.lock().unwrap();
// Make sure we are connected before starting tasks
while !guard.is_connected {
// Wake up 1 connection thread if the connection breaks
connect_condvar.notify_one();
// Sleep until signaled that the connection has been fixed
let (new_guard, _) = worker_condvar.wait_timeout(guard, TIMEOUT).unwrap();
guard = new_guard;
}
// Now that we have verified we are connected, handle the request
guard.api_calls(request);
}
})
.unwrap();
}
pub fn start() {
let self_arc = SomeNetwork {
is_connected: false,
};
let self_arc = Arc::new(Mutex::new(self_arc));
let connect_condvar = Arc::new(Condvar::new());
let worker_condvar = Arc::new(Condvar::new());
// Create a channel to send jobs to workers
let (send, recv) = unbounded();
Self::start_connection_thread(self_arc.clone(), connect_condvar.clone(), worker_condvar.clone());
// Start some workers
for _ in 0..5 {
Self::start_worker_thread(self_arc.clone(), connect_condvar.clone(), worker_condvar.clone(), recv.clone());
}
// Send messages to workers
for message in 1..100 {
send.send(message);
}
loop {
thread::sleep(Duration::from_millis(5000))
}
}
}
fn main() {
SomeNetwork::start();
}

Related

Is this because the mutex was not released?

I've read this Turning Our Single-Threaded Server into a Multithreaded Server.
And tried to implement it.
I wrote this:
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
type task = dyn FnOnce() + Send + 'static;
pub struct Threadpool {
threads: Vec<thread::JoinHandle<()>>,
rx: Arc<Mutex<Receiver<Box<task>>>>,
tx: Sender<Box<task>>,
}
impl Threadpool {
pub fn new(size: usize) -> Threadpool {
let mut tasks = Vec::with_capacity(size);
let (tx, rx): (Sender<Box<task>>, Receiver<Box<task>>) = channel();
let rx = Arc::new(Mutex::new(rx));
for _ in 0..size {
let rx = rx.clone();
let task = thread::spawn(move || {
loop {
let job= rx.lock().unwrap().recv().unwrap();
job();
}
});
tasks.push(task);
}
Threadpool {
threads: tasks,
rx,
tx,
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
self.tx.send(Box::new(f)).unwrap();
}
}
It works.
But when I change
let job= rx.lock().unwrap().recv().unwrap();
job();
to
rx.lock().unwrap().recv().unwrap()();
When I open localhost:port/sleep, and then open localhost:port, it will takes 5 seconds.
I set this in main
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "hello.html")
}
I already knew that while let will cause that.
But I can't figure out why my code above will also lead to that.
Can anybody give me the answer.
In Rust temporary objects are dropped at the end of the expression that contains them (with a few caveats not relevant here).
And the temporary we are interested in is the guard of the mutex, whose drop is responsible of releasing the mutex lock.
So, writingh the drop explicitly, your first code:
let job = rx.lock().unwrap().recv().unwrap();
job();
Is equivalent to:
let mut guard = rx.lock().unwrap();
let job = guard.recv().unwrap();
drop(guard);
job();
And your second code:
rx.lock().unwrap().recv().unwrap()();
Is equivalent to:
let mut guard = rx.lock().unwrap();
let job = guard.recv().unwrap()
job();
drop(guard);
As you can see, now you are calling the job() function with the mutex still locked.
Is this because the mutex was not released?
Yes, you are basically doing this
{
let rx = rx.lock().unwrap(); // got the lock
let job = rx.recv().unwrap(); // got the job
// going to sleep while still holding mutex lock
job(); // std::thread::sleep(Duration::from_secs(5))
drop(rx); // lock is released
}
As all the threads share the mutex, and will try to acquire lock, they are effectively blocked until sleeping thread with lock wakes up. Which is why after requesting sleep endpoint, other threads are unable to perform any other job.
There's another issue however. Even if it didn't sleep, it still calls Receiver::recv() while still holding the lock, which blocks the current thread (goes to sleep) until something is sent down the channel. But considering that one thread will only block others if there's no jobs on the channel, I guess that is by design.

How can I share a Vector between 2 threads?

I am pretty new to Rust, and cannot manage to keep both Arcs values updated in both threads I'm spawning. The idea would be that one thread loops over received events and when it receives one, updates the object, which the other thread constantly watches. How can I achieve that in Rust, or if this method isn't adequate, would there be a better way to do it ?
(The concrete idea would be one thread listening for MIDI events and the other one re-rendering on a LED strip the notes received)
Here's what I currently have:
main.rs
mod functions;
mod structs;
use crate::functions::*;
use crate::structs::*;
use portmidi as pm;
use rs_ws281x::{ChannelBuilder, ControllerBuilder, StripType};
use std::sync::{Arc, Mutex};
use std::{fs, thread, time};
const MIDI_TIMEOUT: u64 = 10;
const MIDI_CHANNEL: usize = 0;
#[tokio::main]
async fn main() {
let config: Arc<std::sync::Mutex<Config>> = Arc::new(Mutex::new(
toml::from_str(&fs::read_to_string("config.toml").unwrap()).unwrap(),
));
let config_midi = config.clone();
let config_leds = config.clone();
let leds_status = Arc::new(Mutex::new(vec![0; config.lock().unwrap().leds.num_leds]));
let leds_status_midi = Arc::clone(&leds_status);
let leds_status_leds = Arc::clone(&leds_status);
thread::spawn(move || {
let config = config_midi.lock().unwrap();
let midi_context = pm::PortMidi::new().unwrap();
let device_info = midi_context
.device(config.midi.id)
.expect(format!("Could not find device with id {}", config.midi.id).as_str());
println!("Using device {}) {}", device_info.id(), device_info.name());
let input_port = midi_context
.input_port(device_info, config.midi.buffer_size)
.expect("Could not create input port");
let mut leds_status = leds_status_midi.lock().unwrap();
loop {
if let Ok(_) = input_port.poll() {
if let Ok(Some(events)) = input_port.read_n(config.midi.buffer_size) {
for event in events {
let event_type =
get_midi_event_type(event.message.status, event.message.data2);
match event_type {
MidiEventType::NoteOn => {
let key = get_note_position(event.message.data1, &config);
leds_status[key] = 1;
}
MidiEventType::NoteOff => {
let key = get_note_position(event.message.data1, &config);
leds_status[key] = 0;
}
_ => {}
}
}
}
}
thread::sleep(time::Duration::from_millis(MIDI_TIMEOUT));
}
});
thread::spawn(move || {
let config = config_leds.lock().unwrap();
let mut led_controller = ControllerBuilder::new()
.freq(800_000)
.dma(10)
.channel(
MIDI_CHANNEL,
ChannelBuilder::new()
.pin(config.leds.pin)
.count(config.leds.num_leds as i32)
.strip_type(StripType::Ws2812)
.brightness(config.leds.brightness)
.build(),
)
.build()
.unwrap();
loop {
let leds_status = leds_status_leds.lock().unwrap();
print!("\x1b[2J\x1b[1;1H");
println!(
"{:?}",
leds_status.iter().filter(|x| (**x) > 0).collect::<Vec<_>>()
);
}
});
}
functions.rs
use crate::structs::MidiEventType;
pub fn get_note_position(note: u8, config: &crate::structs::Config) -> usize {
let mut note_offset = 0;
for i in 0..config.leds.offsets.len() {
if note > config.leds.offsets[i][0] {
note_offset = config.leds.offsets[i][1];
break;
}
}
note_offset -= config.leds.shift;
let note_pos_raw = 2 * (note - 20) - note_offset;
config.leds.num_leds - (note_pos_raw as usize)
}
pub fn get_midi_event_type(status: u8, velocity: u8) -> MidiEventType {
if status == 144 && velocity > 0 {
MidiEventType::NoteOn
} else if status == 128 || (status == 144 && velocity == 0) {
MidiEventType::NoteOff
} else {
MidiEventType::ControlChange
}
}
structs.rs
use serde_derive::Deserialize;
#[derive(Deserialize, Debug)]
pub struct Config {
pub leds: LedsConfig,
pub midi: MidiConfig,
}
#[derive(Deserialize, Debug)]
pub struct LedsConfig {
pub pin: i32,
pub num_leds: usize,
pub brightness: u8,
pub offsets: Vec<Vec<u8>>,
pub shift: u8,
pub fade: i8,
}
#[derive(Deserialize, Debug)]
pub struct MidiConfig {
pub id: i32,
pub buffer_size: usize,
}
#[derive(Debug)]
pub enum MidiEventType {
NoteOn,
NoteOff,
ControlChange,
}
Thank you very much !
The idea would be that one thread loops over received events and when it receives one, updates the object, which the other thread constantly watches.
That's a good way to do it, particularly if one of the threads needs to be near-realtime (e.g. live audio processing). You can use channels to achieve this. You transfer the sender to one thread and the receiver to another. In a realtime scenario, the receiver can loop until try_recv errs with Empty (limiting to some number of iterations to prevent starvation of the processing code). For example, something like this, given a r: Receiver:
// Process 100 messages max to not starve the thread of the other stuff
// it needs to be doing.
for _ in 0..100 {
match r.try_recv() {
Ok(msg) => { /* Process msg, applying it to the current state */ },
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
// The sender is gone, maybe this is our signal to terminate?
return;
},
}
}
Alternatively, if one thread needs to act only when a message is received, it can simply iterate the receiver, which will continue to loop as long as messages are received and the channel is open:
for msg in r {
// Handle the message
}
It really is that simple. If the channel is empty but there are senders alive, it will block until a message is received. Once all senders are gone and the channel is empty, the loop will terminate.
A channel can convey messages of exactly one type; if only one kind of message needs to be sent, you can use a struct. Otherwise, an enum with variants for each kind of message works well.
Given the sending side of the channel, s: Sender, you just s.send(your_message_value).
Another option would be to create an Arc<Mutex<_>>, which it looks like you are doing in your sample code. This way is fine if the lock contention is not too high, but this can inhibit the ability of both threads to run concurrently, which is often the goal of multithreading. Channels tend to work better in message-passing scenarios because there isn't a need for a mutual exclusion lock.
As a side note, you are using Tokio with an async main(), but you never actually do anything with any futures, so there's no reason to even use Tokio in this code.

How can I create a deadlock with Rust?

I would like to know how to create a deadlock.
I tried to create a program in Rust that has a deadlock.
How to create one?
A very simple variant:
use std::sync::{Arc, Mutex};
fn main() {
let data = Arc::new(Mutex::new(0));
let d1 = data.lock();
let d2 = data.lock(); // cannot lock, since d1 is still active
}
Same as in other programming languages, Rust can have silent killer deadlocks.
If some thread locking a mutex is waiting for another, this is not a good sign: if that other cannot come, the first mutex will never be released.
use std::{sync::{Mutex, MutexGuard}, thread};
use std::thread::sleep;
use std::time::Duration;
use lazy_static::lazy_static;
lazy_static! {
static ref MUTEX1: Mutex<i64> = Mutex::new(0);
static ref MUTEX2: Mutex<i64> = Mutex::new(0);
}
fn main() {
// Spawn thread and store handles
let mut children = vec![];
for i_thread in 0..2 {
children.push(thread::spawn(move || {
for _ in 0..1 {
// Thread 1
if i_thread % 2 == 0 {
// Lock mutex1
// No need to specify type but yes create a dummy variable to prevent rust
// compiler from being lazy
let _guard: MutexGuard<i64> = MUTEX1.lock().unwrap();
// Just log
println!("Thread {} locked mutex1 and will try to lock the mutex2, after a nap !", i_thread);
// Here I sleep to let Thread 2 lock mutex2
sleep(Duration::from_millis(10));
// Lock mutex 2
let _guard = MUTEX2.lock().unwrap();
// Thread 2
} else {
// Lock mutex 1
let _guard = MUTEX2.lock().unwrap();
println!("Thread {} locked mutex2 and will try to lock the mutex1", i_thread);
// Here I freeze !
let _guard = MUTEX1.lock().unwrap();
}
}
}));
}
// Wait
for child in children {
let _ = child.join();
}
println!("This is not printed");
}
Which outputs
Thread 0 locked mutex1 and will try to lock the mutex2, after a nap !
Thread 1 locked mutex2 and will try to lock the mutex1
and then waits forever

Second lock on same variable in a single expression blocks indefinitely

I have a Node containing a Mutex on a shared Protocol which is in turn used among different threads within a thread pool:
use std::sync::{Arc, Mutex};
pub struct Node {
thread_pool: ThreadPool,
protocol: Arc<Mutex<Protocol>>,
}
pub struct Protocol {}
impl Protocol {
pub fn is_leader(&self) -> bool {
// Do stuff...
}
pub fn is_co_leader(&self) -> bool {
// Do stuff...
}
}
When I try to acquire a lock on the protocol of the Node within the same if-statement, the code within that statement is never executed.
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
if !protocol_handler.lock().unwrap().is_leader()
&& !protocol_handler.lock().unwrap().is_co_leader()
{
// This is never executed
}
// And this neither...
})
}
}
However, if the values of the method invocations are assigned to two variables, everything works as intended:
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
let is_leader = protocol_handler.lock().unwrap().is_leader();
let is_co_leader = protocol_handler.lock().unwrap().is_co_leader();
if !is_leader && !is_co_leader {
// Either this will be executed
}
// or this ...
})
}
}
Is there any specific cause for Rust's behaviour to wait indefinitely in the first case?
Here is an MCVE for your problem:
use std::sync::Mutex;
fn main() {
let foo = Mutex::new(42i32);
let f1 = (*foo.lock().unwrap()).count_ones();
println!("f1: {}", f1);
let f2 = (*foo.lock().unwrap()).count_zeros();
println!("f2: {}", f2);
let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
println!("tot: {}", tot);
}
playground
When running this code it will print f1 and f2, then hang when trying to compute tot.
The problem is that Mutex::lock returns a MutexGuard which releases the lock automatically when it goes out of scope. In the example above, the guards go out of scope at the end of the expressions in which they are used. So when I write:
let f1 = (*foo.lock().unwrap()).count_ones();
I acquire the lock, read the value, and release the lock. Therefore the lock is free when computing f2.
However, when I write:
let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
I acquire the lock, read the value, try to acquire the lock again and only release both guards at the end of the line. This causes the code to deadlock when I try to acquire the lock for the second time without having released it first.
Note as commented by trentcl that your two steps example is subject to race conditions if things are changed between the two times the mutex is locked. You should rather use something like this:
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
let handler = protocol_handler.lock().unwrap();
if !handler.is_leader && !handler.is_co_leader {
// Either this will be executed
}
// or this ...
})
}
}

How to cleanly break tokio-core event loop and futures::Stream in Rust

I am dabbling in tokio-core and can figure out how to spawn an event loop. However there are two things i am not sure of - how to gracefully exit the event loop and how to exit a stream running inside an event loop. For e.g consider this simple piece of code which spawns two listeners into the event loop and waits for another thread to indicate an exit condition:
extern crate tokio_core;
extern crate futures;
use tokio_core::reactor::Core;
use futures::sync::mpsc::unbounded;
use tokio_core::net::TcpListener;
use std::net::SocketAddr;
use std::str::FromStr;
use futures::{Stream, Future};
use std::thread;
use std::time::Duration;
use std::sync::mpsc::channel;
fn main() {
let (get_tx, get_rx) = channel();
let j = thread::spawn(move || {
let mut core = Core::new().unwrap();
let (tx, rx) = unbounded();
get_tx.send(tx).unwrap(); // <<<<<<<<<<<<<<< (1)
// Listener-0
{
let l = TcpListener::bind(&SocketAddr::from_str("127.0.0.1:44444").unwrap(),
&core.handle())
.unwrap();
let fe = l.incoming()
.for_each(|(_sock, peer)| {
println!("Accepted from {}", peer);
Ok(())
})
.map_err(|e| println!("----- {:?}", e));
core.handle().spawn(fe);
}
// Listener1
{
let l = TcpListener::bind(&SocketAddr::from_str("127.0.0.1:55555").unwrap(),
&core.handle())
.unwrap();
let fe = l.incoming()
.for_each(|(_sock, peer)| {
println!("Accepted from {}", peer);
Ok(())
})
.map_err(|e| println!("----- {:?}", e));
core.handle().spawn(fe);
}
let work = rx.for_each(|v| {
if v {
// (3) I want to shut down listener-0 above the release the resources
Ok(())
} else {
Err(()) // <<<<<<<<<<<<<<< (2)
}
});
let _ = core.run(work);
println!("Exiting event loop thread");
});
let tx = get_rx.recv().unwrap();
thread::sleep(Duration::from_secs(2));
println!("Want to terminate listener-0"); // <<<<<< (3)
tx.send(true).unwrap();
thread::sleep(Duration::from_secs(2));
println!("Want to exit event loop");
tx.send(false).unwrap();
j.join().unwrap();
}
So say after the sleep in the main thread i want a clean exit of the event loop thread. Currently I send something to the event loop to make it exit and thus releasing the thread.
However both, (1) and (2) feel hacky - i am forcing an error as an exit condition. My questions are:
1) Am I doing it right ? If not then what is the correct way to gracefully exit the event loop thread.
2) I don't event know how to do (3) - i.e. indicate a condition externally to shutdown listener-0 and free all it's resources. How do i achieve this ?
The event loop (core) is not being turned any more (e.g. by run()) or is forgotten (drop()ed). There is no synchronous exit. core.run() returns and stops turning the loop when the Future passed to it completes.
A Stream completes by yielding None (marked with (3) in the code below).
When e.g. a TCP connection is closed the Stream representing it completes and the other way around.
extern crate tokio_core;
extern crate futures;
use tokio_core::reactor::Core;
use futures::sync::mpsc::unbounded;
use tokio_core::net::TcpListener;
use std::net::SocketAddr;
use std::str::FromStr;
use futures::{Async, Stream, Future, Poll};
use std::thread;
use std::time::Duration;
struct CompletionPact<S, C>
where S: Stream,
C: Stream,
{
stream: S,
completer: C,
}
fn stream_completion_pact<S, C>(s: S, c: C) -> CompletionPact<S, C>
where S: Stream,
C: Stream,
{
CompletionPact {
stream: s,
completer: c,
}
}
impl<S, C> Stream for CompletionPact<S, C>
where S: Stream,
C: Stream,
{
type Item = S::Item;
type Error = S::Error;
fn poll(&mut self) -> Poll<Option<S::Item>, S::Error> {
match self.completer.poll() {
Ok(Async::Ready(None)) |
Err(_) |
Ok(Async::Ready(Some(_))) => {
// We are done, forget us
Ok(Async::Ready(None)) // <<<<<< (3)
},
Ok(Async::NotReady) => {
self.stream.poll()
},
}
}
}
fn main() {
// unbounded() is the equivalent of a Stream made from a channel()
// directly create it in this thread instead of receiving a Sender
let (tx, rx) = unbounded::<()>();
// A second one to cause forgetting the listener
let (l0tx, l0rx) = unbounded::<()>();
let j = thread::spawn(move || {
let mut core = Core::new().unwrap();
// Listener-0
{
let l = TcpListener::bind(
&SocketAddr::from_str("127.0.0.1:44444").unwrap(),
&core.handle())
.unwrap();
// wrap the Stream of incoming connections (which usually doesn't
// complete) into a Stream that completes when the
// other side is drop()ed or sent on
let fe = stream_completion_pact(l.incoming(), l0rx)
.for_each(|(_sock, peer)| {
println!("Accepted from {}", peer);
Ok(())
})
.map_err(|e| println!("----- {:?}", e));
core.handle().spawn(fe);
}
// Listener1
{
let l = TcpListener::bind(
&SocketAddr::from_str("127.0.0.1:55555").unwrap(),
&core.handle())
.unwrap();
let fe = l.incoming()
.for_each(|(_sock, peer)| {
println!("Accepted from {}", peer);
Ok(())
})
.map_err(|e| println!("----- {:?}", e));
core.handle().spawn(fe);
}
let _ = core.run(rx.into_future());
println!("Exiting event loop thread");
});
thread::sleep(Duration::from_secs(2));
println!("Want to terminate listener-0");
// A drop() will result in the rx side Stream being completed,
// which is indicated by Ok(Async::Ready(None)).
// Our wrapper behaves the same when something is received.
// When the event loop encounters a
// Stream that is complete it forgets about it. Which propagates to a
// drop() that close()es the file descriptor, which closes the port if
// nothing else uses it.
l0tx.send(()).unwrap(); // alternatively: drop(l0tx);
// Note that this is async and is only the signal
// that starts the forgetting.
thread::sleep(Duration::from_secs(2));
println!("Want to exit event loop");
// Same concept. The reception or drop() will cause Stream completion.
// A completed Future will cause run() to return.
tx.send(()).unwrap();
j.join().unwrap();
}
I implemented graceful shutdown via a oneshot channel.
The trick was to use both a oneshot channel to cancel the tcp listener, and use a select! on the two futures. Note I'm using tokio 0.2 and futures 0.3 in the example below.
use futures::channel::oneshot;
use futures::{FutureExt, StreamExt};
use std::thread;
use tokio::net::TcpListener;
pub struct ServerHandle {
// This is the thread in which the server will block
thread: thread::JoinHandle<()>,
// This switch can be used to trigger shutdown of the server.
kill_switch: oneshot::Sender<()>,
}
impl ServerHandle {
pub fn stop(self) {
self.kill_switch.send(()).unwrap();
self.thread.join().unwrap();
}
}
pub fn run_server() -> ServerHandle {
let (kill_switch, kill_switch_receiver) = oneshot::channel::<()>();
let thread = thread::spawn(move || {
info!("Server thread begun!!!");
let mut runtime = tokio::runtime::Builder::new()
.basic_scheduler()
.enable_all()
.thread_name("Tokio-server-thread")
.build()
.unwrap();
runtime.block_on(async {
server_prog(kill_switch_receiver).await.unwrap();
});
info!("Server finished!!!");
});
ServerHandle {
thread,
kill_switch,
}
}
async fn server_prog(kill_switch_receiver: oneshot::Receiver<()>) -> std::io::Result<()> {
let addr = "127.0.0.1:12345";
let addr: std::net::SocketAddr = addr.parse().unwrap();
let mut listener = TcpListener::bind(&addr).await?;
let mut kill_switch_receiver = kill_switch_receiver.fuse();
let mut incoming = listener.incoming().fuse();
loop {
futures::select! {
x = kill_switch_receiver => {
break;
},
optional_new_client = incoming.next() => {
if let Some(new_client) = optional_new_client {
let peer_socket = new_client?;
info!("Client connected!");
let peer = process_client(peer_socket, db.clone());
peers.lock().unwrap().push(peer);
} else {
info!("No more incoming connections.");
break;
}
},
};
}
Ok(())
}
Hopes this helps others (or future me ;)).
My code lives here:
https://github.com/windelbouwman/lognplot/blob/master/lognplot/src/server/server.rs

Resources