How can I quit/pause/unpause a timer thread - multithreading

I am trying to build a pausable pomodoro timer, but can't figure out how my threads should be set up. Specifically I am having a hard time on finding a way to pause/quit the timer thread. Here's what I am currently trying to do:
I have an input thread collecting crossterm input events and sending them via an mpsc-channel to my main thread
My main thread runs the timer (via thread::sleep) and receives the input messages from the other thread
However because sleep is a blocking operation, the main thread is often unable to react to my key-events.
Any advice how I could be going about this?
Here is a minimum reproducable example:
pub fn run() {
enable_raw_mode().expect("Can run in raw mode");
let (input_worker_tx, input_worker_rx): (Sender<CliEvent<Event>>, Receiver<CliEvent<Event>>) =
mpsc::channel();
poll_input_thread(input_worker_tx);
main_loop(input_worker_rx);
}
fn main_loop(input_worker_rx: Receiver<CliEvent<Event>>) {
let stdout = std::io::stdout();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).expect("Terminal could be created");
terminal.clear().expect("Terminal could be cleared");
let mut remaining_time = 20;
while remaining_time > 0 {
if let InputEvent::Quit = handle_input(&input_worker_rx) {
break;
};
remaining_time -= 1;
let time = util::seconds_to_time(remaining_time);
view::render(&mut terminal, &time);
sleep(time::Duration::new(1, 0));
}
quit(&mut terminal, Some("Cya!"));
}
pub fn poll_input_thread(input_worker_tx: Sender<CliEvent<Event>>) {
let tick_rate = Duration::from_millis(200);
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).expect("poll works") {
let crossterm_event = event::read().expect("can read events");
input_worker_tx
.send(CliEvent::Input(crossterm_event))
.expect("can send events");
}
if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
last_tick = Instant::now();
}
}
});
}
pub fn handle_input(input_worker_rx: &Receiver<CliEvent<Event>>) -> InputEvent {
match input_worker_rx.try_recv() {
Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
KeyEvent {
code: KeyCode::Char('q'),
..
}
| KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
} => {
return InputEvent::Quit;
}
_ => {}
},
Ok(CliEvent::Tick) => {}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => eject("Input handler disconnected"),
_ => {}
}
InputEvent::Continue
}

Don't use sleep and don't use try_recv. Instead use recv_timeout with a timeout corresponding to your sleep duration:
pub fn run() {
enable_raw_mode().expect("Can run in raw mode");
let (input_worker_tx, input_worker_rx): (Sender<CliEvent<Event>>, Receiver<CliEvent<Event>>) =
mpsc::channel();
poll_input_thread(input_worker_tx);
main_loop(input_worker_rx);
}
fn main_loop(input_worker_rx: Receiver<CliEvent<Event>>) {
let stdout = std::io::stdout();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).expect("Terminal could be created");
terminal.clear().expect("Terminal could be cleared");
let mut target_time = Instant::now() + Duration::from_secs(20);
let mut paused = false;
let mut remaining = Duration::from_secs (0);
while paused || (target_time > Instant::now()) {
match handle_input(&input_worker_rx, Duration::from_secs (1)) {
InpuEvent::Quit => break,
InputEvent::Pause => {
if paused {
target_time = Instant::now() + remaining;
} else {
remaining = target_time - Instant::now();
}
paused = !paused;
},
_ => {},
}
let time = (if paused { remaining } else { target_time - Instant::now() })
.as_secs();
view::render(&mut terminal, &time);
}
quit(&mut terminal, Some("Cya!"));
}
pub fn poll_input_thread(input_worker_tx: Sender<CliEvent<Event>>) {
let tick_rate = Duration::from_millis(200);
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).expect("poll works") {
let crossterm_event = event::read().expect("can read events");
input_worker_tx
.send(CliEvent::Input(crossterm_event))
.expect("can send events");
}
if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
last_tick = Instant::now();
}
}
});
}
pub fn handle_input(input_worker_rx: &Receiver<CliEvent<Event>>, timeout: Duration) -> InputEvent {
match input_worker_rx.recv_timeout(timeout) {
Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
KeyEvent {
code: KeyCode::Char('q'),
..
}
| KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
} => {
return InputEvent::Quit;
}
KeyEvent {
code: KeyCode::Char(' '),
..
} => {
return InputEvent::Pause;
}
_ => {}
},
Ok(CliEvent::Tick) => {}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => eject("Input handler disconnected"),
_ => {}
}
InputEvent::Continue
}

Related

How to cancel tokio spawn threads from the method calling them

The test code below considers a situation in which there are three different threads.
Each thread has to do certain asynchronous tasks, that may take a certain time to finish.
This is "simulated" in the code below with a sleep.
On top of that, two of the threads collect information that they have to send to the third one for further processing. This is done using mpsc channels.
Due to the fact that there are out of our control information obtained from outside of the Rust application, the threads may get interrupted. This is emulated by generating a random number, and the loop on each thread breaks when that happens.
What I'm trying to achieve is a system in which whenever one of the threads has an error (simulated with the random number = 9), every other thread is cancelled too.
`
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver, TryRecvError};
use std::thread::sleep;
use tokio::time::Duration;
use rand::distributions::{Uniform, Distribution};
#[tokio::main]
async fn main() {
execution_cycle().await;
}
async fn execution_cycle() {
let (tx_first, rx_first) = channel::<Message>();
let (tx_second, rx_second) = channel::<Message>();
let handle_sender_first = tokio::spawn(sender_thread(tx_first));
let handle_sender_second = tokio::spawn(sender_thread(tx_second));
let handle_receiver = tokio::spawn(receiver_thread(rx_first, rx_second));
let mut thread_rng = rand::thread_rng();
let rng_generator = Uniform::from(1..10);
let mut cancel_from_cycle = rng_generator.sample(&mut thread_rng);
while !&handle_sender_first.is_finished() && !&handle_sender_second.is_finished() && !&handle_receiver.is_finished() {
cancel_from_cycle = rng_generator.sample(&mut thread_rng);
if (cancel_from_cycle == 9) {
println!("Aborting from the execution cycle.");
handle_receiver.abort();
handle_sender_first.abort();
handle_sender_second.abort();
}
}
if handle_sender_first.is_finished() {
println!("handle_sender_first finished.");
} else {
println!("handle_sender_first ongoing.");
}
if handle_sender_second.is_finished() {
println!("handle_sender_second finished.");
} else {
println!("handle_sender_second ongoing.");
}
if handle_receiver.is_finished() {
println!("handle_receiver finished.");
} else {
println!("handle_receiver ongoing.");
}
}
async fn sender_thread(tx: Sender<Message>) {
let mut thread_rng = rand::thread_rng();
let rng_generator = Uniform::from(1..20);
let mut random_id = rng_generator.sample(&mut thread_rng);
while random_id != 9 {
let msg = Message {
id: random_id,
text: "hello".to_owned()
};
println!("Sending message {}.", msg.id);
random_id = rng_generator.sample(&mut thread_rng);
println!("Generated id {}.", random_id);
let result = tx.send(msg);
match result {
Ok(res) => {},
Err(error) => {
println!("Sending error {:?}", error);
random_id = 9;
}
}
sleep(Duration::from_millis(2000));
}
}
async fn receiver_thread(rx_first: Receiver<Message>, rx_second: Receiver<Message>) {
let mut channel_open_first = true;
let mut channel_open_second = true;
let mut thread_rng = rand::thread_rng();
let rng_generator = Uniform::from(1..15);
let mut random_event = rng_generator.sample(&mut thread_rng);
while channel_open_first && channel_open_second && random_event != 9 {
channel_open_first = receiver_inner(&rx_first);
channel_open_second = receiver_inner(&rx_second);
random_event = rng_generator.sample(&mut thread_rng);
println!("Generated event {}.", random_event);
sleep(Duration::from_millis(800));
}
}
fn receiver_inner(rx: &Receiver<Message>) -> bool {
let value = rx.try_recv();
match value {
Ok(msg) => {
println!("Message {} received: {}", msg.id, msg.text);
},
Err(error) => {
if error != TryRecvError::Empty {
println!("{}", error);
return false;
} else { /* Channel is empty.*/ }
}
}
return true;
}
struct Message {
id: usize,
text: String,
}
`
In the working example here, it does exactly that, however, it does it only from inside the threads, and I would like to add a "kill switch" in the execution_cycle() method, allowing to cancel all the three threads when a certain event takes place (the random number cancel_from_cycle == 9), and do that in the most simple way possible... I tried drop(handler_sender), and also panic!() from the execution_cycle() but the spawn threads keep running, preventing the application to finish. I also tried handle_receiver().abort() without success.
How can I achieve the wished result?

What's wrong with tokio unix socket server/client?

I have a server that broadcast messages to connected client, though the messages doesn't get delivered and my tests fails.
I'm using the following
use anyhow::Result;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::Arc;
use tokio::io::AsyncWriteExt;
use tokio::net::{UnixListener, UnixStream};
use tokio::sync::broadcast::*;
use tokio::sync::Notify;
use tokio::task::JoinHandle;
This is how I start and setup my server
pub struct Server {
#[allow(dead_code)]
tx: Sender<String>,
rx: Receiver<String>,
address: Arc<PathBuf>,
handle: Option<JoinHandle<Result<()>>>,
abort: Arc<Notify>,
}
impl Server {
pub fn new<P: AsRef<Path>>(address: P) -> Self {
let (tx, rx) = channel::<String>(400);
let address = Arc::new(address.as_ref().to_path_buf());
Self {
address,
handle: None,
tx,
rx,
abort: Arc::new(Notify::new()),
}
}
}
/// Start Server
pub async fn start(server: &mut Server) -> Result<()> {
tokio::fs::remove_file(server.address.as_path()).await.ok();
let listener = UnixListener::bind(server.address.as_path())?;
println!("[Server] Started");
let tx = server.tx.clone();
let abort = server.abort.clone();
server.handle = Some(tokio::spawn(async move {
loop {
let tx = tx.clone();
let abort1 = abort.clone();
tokio::select! {
_ = abort.notified() => break,
Ok((client, _)) = listener.accept() => {
tokio::spawn(async move { handle(client, tx, abort1).await });
}
}
}
println!("[Server] Aborted!");
Ok(())
}));
Ok(())
}
my handle function
/// Handle stream
async fn handle(mut stream: UnixStream, tx: Sender<String>, abort: Arc<Notify>) {
loop {
let mut rx = tx.subscribe();
let abort = abort.clone();
tokio::select! {
_ = abort.notified() => break,
result = rx.recv() => match result {
Ok(output) => {
stream.write_all(output.as_bytes()).await.unwrap();
stream.write(b"\n").await.unwrap();
continue;
}
Err(e) => {
println!("[Server] {e}");
break;
}
}
}
}
stream.write(b"").await.unwrap();
stream.flush().await.unwrap();
}
my connect function
/// Connect to server
async fn connect(address: Arc<PathBuf>, name: String) -> Vec<String> {
use tokio::io::{AsyncBufReadExt, BufReader};
let mut outputs = vec![];
let stream = UnixStream::connect(&*address).await.unwrap();
let mut breader = BufReader::new(stream);
let mut buf = vec![];
loop {
if let Ok(len) = breader.read_until(b'\n', &mut buf).await {
if len == 0 {
break;
} else {
let value = String::from_utf8(buf.clone()).unwrap();
print!("[{name}] {value}");
outputs.push(value)
};
buf.clear();
}
}
println!("[{name}] ENDED");
outputs
}
This what I feed to the channel and want to have broadcasted to all clients
/// Feed data
pub fn feed(tx: Sender<String>, abort: Arc<Notify>) -> Result<JoinHandle<Result<()>>> {
use tokio::io::*;
use tokio::process::Command;
Ok(tokio::spawn(async move {
let mut child = Command::new("echo")
.args(&["1\n", "2\n", "3\n", "4\n"])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.stdin(Stdio::null())
.spawn()?;
let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines();
loop {
let sender = tx.clone();
tokio::select! {
result = stdout.next_line() => match result {
Err(e) => {
println!("[Server] FAILED to send an output to channel: {e}");
},
Ok(None) => break,
Ok(Some(output)) => {
let output = output.trim().to_string();
println!("[Server] {output}");
if !output.is_empty() {
if let Err(e) = sender.send(output) {
println!("[Server] FAILED to send an output to channel: {e}");
}
}
}
}
}
}
println!("[Server] Process Completed");
abort.notify_waiters();
Ok(())
}))
}
my failing test
#[tokio::test]
async fn test_server() -> Result<()> {
let mut server = Server::new("/tmp/testsock.socket");
start(&mut server).await?;
feed(server.tx.clone(), server.abort.clone()).unwrap();
let address = server.address.clone();
let client1 = connect(address.clone(), "Alpha".into());
let client2 = connect(address.clone(), "Beta".into());
let client3 = connect(address.clone(), "Delta".into());
let client4 = connect(address.clone(), "Gamma".into());
let (c1, c2, c3, c4) = tokio::join!(client1, client2, client3, client4,);
server.handle.unwrap().abort();
assert_eq!(c1.len(), 4, "Alpha");
assert_eq!(c2.len(), 4, "Beta");
assert_eq!(c3.len(), 4, "Delta");
assert_eq!(c4.len(), 4, "Gamma");
println!("ENDED");
Ok(())
}
Logs:
[Server] Started
[Server] 1
[Server] 2
[Server] 3
[Server] 4
[Server]
[Delta] 1
[Gamma] 1
[Alpha] 1
[Beta] 1
[Server] Process Completed
[Server] Aborted!
[Gamma] ENDED
[Alpha] ENDED
[Beta] ENDED
[Delta] ENDED
well, not an answer but I just want to suggest to use task::spawn to generate a JoinHandle from a function, then, say your handle could be:
fn handle(mut stream: UnixStream, tx: Sender<String>, abort: Arc<Notify>) -> JoinHandle {
let mut rx = tx.subscribe();
let abort = abort.clone();
task::spawn( async move {
loop {
tokio::select! {
_ = abort.notified() => break,
result = rx.recv() => match result {
Ok(output) => {
stream.write_all(output.as_bytes()).await.unwrap();
stream.write(b"\n").await.unwrap();
continue;
}
Err(e) => {
println!("[Server] {e}");
break;
}
}
}
}
stream.write(b"").await.unwrap();
stream.flush().await.unwrap();
})
}
I mean, I did not tested this, but I see a sort of duplication in the code above, like 2 loop, 2 select! and twice the abort check

How to create other threads in main function

I am using the stream function of redis in actix-web 4, I want to create the consumer in the main function, this is my current code
[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["full"] }
redis = { version = "0.21", features = [
# "cluster",
"tokio-comp",
"tokio-native-tls-comp",
] }
#[actix_web::main]
async fn main() -> std::io::Result<()> {
utils::init::init_envfile();
env_logger::init_from_env(env_logger::Env::new());
let redis_pool = utils::init::init_redis_pool();
let mysql_pool = utils::init::init_mysql_pool();
let redist_stream_consumer = web::block(redis_stream_group);
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(redis_pool.clone()))
.app_data(web::Data::new(mysql_pool.clone()))
.service(web::scope("/api").configure(controller::api::config))
})
.bind(("0.0.0.0", 7777))?
.run()
.await?;
redist_stream_consumer.await.unwrap();
Ok(())
}
fn redis_stream_group() {
let client = redis::Client::open("redis://127.0.0.1/").expect("client");
let mut con = client.get_connection().expect("con");
let key = "s.order";
let group_name = "g1";
let consumer_name = "c1";
let _: Result<(), _> = con.xgroup_create_mkstream(key, group_name, "$");
let opts = StreamReadOptions::default()
.group(group_name, consumer_name)
.count(1)
.block(0);
loop {
let read_reply: StreamReadReply =
con.xread_options(&[key], &[">"], &opts).expect("read err");
for StreamKey { key, ids } in read_reply.keys {
for StreamId { id, map } in &ids {
log::info!("id:{} | key:{} | data:{:?}", id, key, map);
}
let id_strs: Vec<&String> = ids.iter().map(|StreamId { id, map: _ }| id).collect();
let _: usize = con.xack(key, group_name, &id_strs).expect("ack err");
}
}
}
When I use cargo r, I can run the program normally and get the sent messages, but when I execute ctrl+c, I can't exit the program.
Also I'm not sure if using web::block in the main function is correct and if there is a better way to run child threads
UPDATE: Tried using tokio::spawn, seems to work
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let redis_pool = utils::init::init_redis_pool();
let mysql_pool = utils::init::init_mysql_pool();
for consumer_index in 1..=2 {
let c_redis_pool = redis_pool.clone();
tokio::spawn(async move {
let mut con = c_redis_pool.get().await.unwrap();
let key = "s.order";
let group_name = "g1";
let consumer_name = &format!("c{consumer_index}");
let _: Result<(), _> = con.xgroup_create_mkstream(key, group_name, "$").await;
let opts = StreamReadOptions::default()
.group(group_name, consumer_name)
.count(1)
.block(5000);
loop {
let read_reply: StreamReadReply = con
.xread_options(&[key], &[">"], &opts)
.await
.expect("err");
for StreamKey { key, ids } in read_reply.keys {
for StreamId { id, map } in &ids {
log::info!(
"consumer: {} | id:{} | key:{} | data:{:?}",
consumer_name,
id,
key,
map
);
}
let id_strs: Vec<&String> =
ids.iter().map(|StreamId { id, map: _ }| id).collect();
let _: usize = con
.xack(key, group_name, &id_strs)
.await
.expect("ack err");
}
}
});
}
let serve = HttpServer::new(move || {
...
}
This can be done with the standard library by useing std::thread and then creating the thread and whatever you want the other thread to do in a closure
fn main() {
thread::spawn(|| {
println!("doing things in the thread!");
});
println!("doing things outside the thread.... how boring");
}
if you want to pass data between them, you can use std::sync::mpsc to transfer data between the threads safely and quickly, using let (item_one,item_two) = mpsc::channel();, like so
fn main() {
let (sender,receiver) = mpsc::channel();
thread::spawn(move || {
let message = String::from("This message is from the thread");
sender.send(message).unwrap();
});
let letter = receiver.recv().unwrap();
note that the main thread proceeds as normal until it comes to the .recv(), at which it either receives the data from the thread, or waits until the other thread is done.
in your example you could do something like
use std::sync::mpsc;
use std::thread;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
utils::init::init_envfile();
env_logger::init_from_env(env_logger::Env::new());
let port = get_env!("ACTIX_PORT", u16);
log::info!(
"starting HTTP server at http://{}:{}",
local_ipaddress::get().unwrap_or("localhost".to_string()),
port
);
let redis_pool = utils::init::init_redis_pool();
let mysql_pool = utils::init::init_mysql_pool();
let (consumer_sender,consumer_listener) = mpsc::channel();
thread::spawn(move || {
consumer_sender.send(redis_stream_group()).expect("You probably want to handle this case, but I'm too lazy");
});
let serve = HttpServer::new(move || {
let app_state = utils::init::AppState {
app_name: get_env!("APP_NAME", String),
pwd_secret: get_env!("PWD_SECRET", String),
jwt_secret: get_env!("JWT_SECRET", String),
jwt_exp: get_env!("JWT_EXP", i64),
};
App::new()
.app_data(web::Data::new(awc::Client::default()))
.app_data(web::Data::new(app_state))
.app_data(web::Data::new(redis_pool.clone()))
.app_data(web::Data::new(mysql_pool.clone()))
.wrap(actix_cors::Cors::default().allowed_origin_fn(|_, _| true))
.service(web::scope("/chat").configure(controller::chat::config))
.service(web::scope("/ws").configure(controller::ws::config))
.service(web::scope("/api").configure(controller::api::config))
});
if cfg!(debug_assertions) {
serve.bind(("0.0.0.0", port))?
} else {
let p = format!("/tmp/{}.socket", get_env!("APP_NAME", String));
let r = serve.bind_uds(&p)?;
let mut perms = std::fs::metadata(&p)?.permissions();
perms.set_readonly(false);
std::fs::set_permissions(&p, perms)?;
r
}
.run()
.await?;
let consumer = consumer_listener.recv().unwrap();
//then put things to do with the consumer here, or not idc
Ok(())
}
fn redis_stream_group() {
let client = redis::Client::open("redis://127.0.0.1/").expect("client");
let mut con = client.get_connection().expect("con");
let key = "s.order";
let group_name = "g1";
let consumer_name = "c1";
let _: Result<(), _> = con.xgroup_create_mkstream(key, group_name, "$");
let opts = StreamReadOptions::default()
.group(group_name, consumer_name)
.count(1)
.block(0);
loop {
let read_reply: StreamReadReply =
con.xread_options(&[key], &[">"], &opts).expect("read err");
for StreamKey { key, ids } in read_reply.keys {
for StreamId { id, map } in &ids {
log::info!("id:{} | key:{} | data:{:?}", id, key, map);
}
let id_strs: Vec<&String> = ids.iter().map(|StreamId { id, map: _ }| id).collect();
let _: usize = con.xack(key, group_name, &id_strs).expect("ack err");
}
}
}

serial-rs multiple bluetooth connections

Using serial-rs it's possible to open a Bluetooth connection between my Mac and Arduino (HC-05). But if I want to open multiple Bluetooth connections at the same time, only the most recent connection stays open.
I am not completely sure how Qt handles this, but it's possible to read/write to multiple devices same time using QSerialPort.
Is this a serial-rs unimplemented feature, or does Qt do something like switching connections (to have only one opened in time) so it looks like multiple connections are handled?
extern crate serial;
#[macro_use]
extern crate text_io;
use std::process::Command;
use std::io;
use std::time::Duration;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
use std::io::prelude::*;
use serial::prelude::*;
use std::sync::Arc;
use std::sync::Mutex;
fn print_available_ports() {
let status = Command::new("ls")
.arg("/dev/")
.status()
.unwrap_or_else(|e| panic!("Failed to execute process: {}", e));
}
fn print_available_commands() {
println!("Available commands:");
println!(" print_ports - prints available serial ports");
println!(" connect - make an asynchronous connection to port");
println!(" start - signal ports to start collecting data");
println!(" stop - opposite to start");
println!(" monitor - print info of current state of reading");
println!(" help - prints this info.");
println!(" exit");
}
fn connect_to_port(portname: &String,
rate: usize,
tx: Sender<String>,
port_state: Arc<Mutex<bool>>)
-> io::Result<()> {
let mut port = serial::open(portname.trim()).unwrap();
try!(port.reconfigure(&|settings| {
try!(settings.set_baud_rate(serial::BaudRate::from_speed(rate)));
settings.set_char_size(serial::Bits8);
settings.set_parity(serial::ParityNone);
settings.set_stop_bits(serial::Stop1);
settings.set_flow_control(serial::FlowNone);
Ok(())
}));
try!(port.set_timeout(Duration::from_millis(10000)));
println!("Serial port to {} opened successfully.", portname);
println!("Waiting for the start..");
while *(port_state.lock().unwrap()) != true {
}
println!("Port named {} started reading.", portname);
let mut ans_number: usize = 0;
let mut answer = String::new();
let mut bytes_received: usize = 0;
let mut buf = vec![0;128];
loop {
match port.read(&mut buf[..]) {
Ok(n) => {
bytes_received += n;
}
Err(_) => {
println!("Error in reading from {}", portname);
bytes_received = bytes_received;
}
}
if bytes_received > 10000 {
answer = String::new();
answer = format!("#{} Port {} received 10000 bytes of data",
ans_number,
portname);
tx.send(answer);
bytes_received = 0;
ans_number += 1;
}
if *(port_state.lock().unwrap()) == false {
println!("Port named {} got signal to stop. Abort.", portname);
break;
}
}
Ok(())
}
fn main() {
print_available_commands();
let mut reading_active = Arc::new(Mutex::new(false));
let (dtx, drx): (Sender<String>, Receiver<String>) = mpsc::channel();
let mut ports = vec![];
let mut input = String::new();
loop {
input = String::new();
match io::stdin().read_line(&mut input) {
Ok(n) => println!("Command received: {}", input.trim()),
Err(error) => println!("error: {}", error),
}
match input.trim() {
"connect" => {
let portname: String;
let baudrate: usize;
println!("Enter port name:");
portname = read!();
println!("Enter baudrate:");
baudrate = read!();
let thread_state = reading_active.clone();
let thread_tx = dtx.clone();
ports.push(thread::Builder::new().name(portname.clone()).spawn(move || {
connect_to_port(&portname, baudrate, thread_tx, thread_state);
}));
}
"start" => {
*(reading_active.lock().unwrap()) = true;
}
"stop" => {
*(reading_active.lock().unwrap()) = false;
}
"help" => print_available_commands(),
"print_ports" => print_available_ports(),
"exit" => {
println!("Closing used ports..");
}
"monitor" => {
loop {
println!("{:?}", drx.recv());
}
}
_ => println!("Unsupported command."),
}
}
}

Simple server - threads are left open if try to stream a file

I'm fairly new to rust and I'm trying to learn it by doing www.rust-class.org. In one of the assignments I've to implement simple web server. Most of the code on github is for v0.9 so I had to rewrite some of the things. Anyway:
Webserver code is below but I don't expect you to read everything so down below I highlight the part when the problem occurs.
use std::io::*;
use std::io::net::ip::SocketAddr;
use std::{str, os};
use std::sync::{Mutex, Arc, Semaphore};
use std::path::{Path, PosixPath};
use std::io::fs::PathExtensions;
use std::collections::{BinaryHeap, HashMap};
use std::io::timer::sleep;
use std::time::duration::Duration;
static CONTENT_TYPE_HTML: &'static str = "Content-Type: text/html; charset=UTF-8\r\n\r\n";
static HTTP_SUCCESS: &'static str = "HTTP/1.1 200 OK\r\n";
static HTTP_NOT_FOUND: &'static str = "HTTP/1.1 404 OK\r\n";
static START_COUNTER_STYLE: &'static str = "
<doctype !html><html>
<head>
<title>Hello, Rust!</title>
<style>
body { background-color: #884414; color: #FFEEAA}
h1 { font-size:2cm; text-align: center; color: black; text-shadow: 0 0 4mm red }
h2 { font-size:2cm; text-align: center; color: black; text-shadow: 0 0 4mm green }
</style>
</head>
<body>";
static END_COUNTER_STYLE: &'static str = "</body></html>\r\n";
static FILE_CHUNK: uint = 8192;
static MAX_CONCURRENCY: int = 4;
#[deriving(PartialEq, Eq)]
struct HTTPRequest {
peer_name: SocketAddr,
path: PosixPath,
file_size: uint,
priority: uint
}
impl PartialOrd for HTTPRequest {
fn partial_cmp(&self, other: &HTTPRequest) -> Option<Ordering> {
// Comparison is reversed to make PriorityQueue behave like a min-heap
(self.priority).partial_cmp(&other.priority)
}
}
impl Ord for HTTPRequest {
fn cmp(&self, other: &HTTPRequest) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
pub struct WebServer {
port: uint,
ip_str: String,
request_queue_arc: Arc<Mutex<BinaryHeap<HTTPRequest>>>,
stream_map_arc: Arc<Mutex<HashMap<SocketAddr, Result<net::tcp::TcpStream, IoError>>>>,
notify_sender: Sender<()>,
notify_recv: Receiver<()>,
www_dir_path: Path,
concurrency_limit: Arc<Semaphore>,
}
impl WebServer {
pub fn new(ip_str: String, port: uint, www_dir_str: String) -> WebServer {
let (notify_sender, notify_recv) = channel();
let www_dir_path = Path::new(www_dir_str);
debug!("I'm serving server from directory: {}", www_dir_path.display());
WebServer {
ip_str: ip_str,
port: port,
www_dir_path: www_dir_path,
request_queue_arc: Arc::new(Mutex::new(BinaryHeap::new())),
stream_map_arc: Arc::new(Mutex::new(HashMap::new())),
notify_sender: notify_sender,
notify_recv: notify_recv,
concurrency_limit: Arc::new(Semaphore::new(MAX_CONCURRENCY))
}
}
pub fn run(&mut self) {
self.listen();
self.dequeue_static_file_request();
}
pub fn listen(&mut self) {
let addr = from_str::<SocketAddr>(format!("{}:{}", self.ip_str, self.port).as_slice()).expect("Address error.");
let stream_map_arc = self.stream_map_arc.clone();
let notify_sender = self.notify_sender.clone();
let request_queue_arc = self.request_queue_arc.clone();
let www_dir_path = self.www_dir_path.clone();
spawn(proc(){
let mut acceptor = net::tcp::TcpListener::bind(addr).listen();
println!("Listening on {}", addr);
let mut requests_counter: uint = 0;
for stream in acceptor.incoming() {
match stream.clone() {
Ok(mut res) => { res.set_timeout(Some(1000*10)); },
Err(why) => { panic!("Couldn't set timout for stream: {}", why.desc); }
}
requests_counter += 1;
let stream_map_arc = stream_map_arc.clone();
let notify_sender = notify_sender.clone();
let request_queue_arc = request_queue_arc.clone();
let www_dir_path = www_dir_path.clone();
spawn(proc() {
let mut stream = stream;
let mut buf = [0, ..500];
stream.read(&mut buf);
let request_str = str::from_utf8(buf.as_slice());
debug!("Request:\n{}", request_str);
let peer_name: SocketAddr = WebServer::peer_name(stream.clone());
match WebServer::get_request_path(www_dir_path.clone(), buf) {
Ok(request_path) => {
let extension = match request_path.extension_str() {
Some(ext) => ext,
None => ""
};
debug!("Requested path :\n{}", request_path.as_str());
debug!("Extension :\n{}",extension);
if request_path.as_str().expect("Request path err") == "www" {
debug!("===== Counter Page request =====");
WebServer::respond_with_counter_page(stream, requests_counter);
debug!("=====Terminated connection from [{}:{}].=====", peer_name.ip, peer_name.port);
} else if request_path.is_file() && (extension == "html" || extension == "bin") {
debug!("===== Static page request =====");
WebServer::enqueue_static_file_request(
stream,
request_path.clone(),
peer_name,
stream_map_arc,
request_queue_arc,
notify_sender
);
} else if request_path.is_file() && extension == "html" {
debug!("===== Dynamic page request =====");
// WebServer::respond_with_dynamic_page(stream, request_path.clone());
debug!("=====Terminated connection from [{}:{}].=====", peer_name.ip, peer_name.port);
} else {
debug!("===== Respond with error page =====");
WebServer::respond_with_error_page(stream);
debug!("=====Terminated connection from [{}:{}].=====", peer_name.ip, peer_name.port);
}
},
Err(_) => {
debug!("===== Respond with error page =====");
WebServer::respond_with_error_page(stream);
debug!("=====Terminated connection from [{}:{}].=====", peer_name.ip, peer_name.port);
}
}
})
}
});
}
fn respond_with_counter_page(stream: Result<net::tcp::TcpStream, IoError>, requests_counter: uint) {
WebServer::force_write(stream.clone(), format!("{}{}{}Requests:{}{}",
HTTP_SUCCESS,
CONTENT_TYPE_HTML,
START_COUNTER_STYLE,
requests_counter,
END_COUNTER_STYLE
).as_bytes())
}
fn respond_with_error_page(stream: Result<net::tcp::TcpStream, IoError>) {
WebServer::force_write(stream.clone(), HTTP_NOT_FOUND.as_bytes());
}
// fn respond_with_dynamic_page(stream: Result<net::tcp::TcpStream, IoError>, request_path: Path) {
// // WebServer::respond_with_static_page(stream, request_path);
// }
// TODO: Application-layer file caching.
fn respond_with_static_page(stream: net::tcp::TcpStream, request_path: Path) {
let mut stream = stream;
let mut file = match File::open(&request_path) {
Err(why) => {
debug!("File couln't be opened because: {} kind: {}", why.desc, why.kind);
return;
},
Ok(f) => { f }
};
stream.write(HTTP_SUCCESS.as_bytes());
stream.write(CONTENT_TYPE_HTML.as_bytes());
loop {
let mut buf = vec!();
match file.push_at_least(FILE_CHUNK, FILE_CHUNK, &mut buf) {
Err(why) => {
debug!("File reading problem: {}, {}", why.kind, why.desc)
if buf.len() > 0 {
stream.write(buf.as_slice());
}
return;
},
Ok(read_bytes_size) => {
match stream.write(buf.as_slice()) {
Err(why) => {
debug!("Stream broken: desc: {}, kind: {}", why.desc, why.kind);
return;
},
Ok(_) => {}
}
}
}
}
}
fn enqueue_static_file_request(
stream: Result<net::tcp::TcpStream, IoError>,
request_path: Path,
peer_name: SocketAddr,
stream_map_arc: Arc<Mutex<HashMap<SocketAddr, Result<net::tcp::TcpStream, IoError>>>>,
request_queue_arc: Arc<Mutex<BinaryHeap<HTTPRequest>>>,
notify_sender: Sender<()>,
) {
debug!("Enqueuing static file, waiting for streams lock... to stream: {}", request_path.display());
let mut local_map = stream_map_arc.lock();
local_map.insert(peer_name.clone(), stream);
debug!("Enqueuing static file, waiting for requests lock...");
let mut local_req = request_queue_arc.lock();
local_req.push(
HTTPRequest {
peer_name: peer_name.clone(),
path: request_path.clone(),
file_size: 1,
priority: 1
});
println!("enqueue_static_file_request: request_queue_arc length: {}", local_req.len());
println!("enqueue_static_file_request: stream_map_arc length: {}", local_map.len());
notify_sender.send(());
}
fn dequeue_static_file_request(&mut self) {
let stream_map_arc = self.stream_map_arc.clone();
let request_queue_arc = self.request_queue_arc.clone();
loop {
debug!("Waiting for requests!");
self.notify_recv.recv();
debug!("Dequeuing static file, waiting for requests lock...");
let mut local_req = request_queue_arc.lock();
match local_req.pop() {
Some(request) => {
println!("dequeue_static_file_request, request_queue_arc length: {}, {}",
local_req.len(),
&request.path.display()
);
debug!("Dequeuing static file, waiting for streams lock...");
let mut local_map = stream_map_arc.lock();
println!("dequeue_static_file_request, stream_map_arc length: {}", local_map.len());
match local_map.remove(&request.peer_name) {
None => { },
Some(stream) => match stream {
Ok(res) => {
self.concurrency_limit.acquire();
let child_concurrency_limit = self.concurrency_limit.clone();
let res = res.clone();
spawn(proc(){
WebServer::respond_with_static_page(res, request.path);
child_concurrency_limit.release();
debug!("=====Terminated connection from [{}:{}].=====",
&request.peer_name.ip, &request.peer_name.port
);
});
},
Err(_) => {
debug!("Stream had broken in the meantime.");
}
}
}
}
None => {}
}
}
}
fn peer_name(stream: Result<net::tcp::TcpStream, IoError>) -> net::ip::SocketAddr {
match stream {
Err(_) => panic!("Stream broken # peer_name"),
Ok(res) => {
match res.clone().peer_name() {
Ok(addr) => addr,
Err(_) => panic!("Couldn't obtain peername from stream")
}
}
}
}
fn get_request_path(root: Path, buf: [u8, ..500]) -> Result<Path, &'static str> {
match str::from_utf8(buf.as_slice()) {
Some(request_str) => {
let request_headers: Vec<&str> = request_str.splitn(3, ' ').collect();
if request_headers.len() == 4 {
Ok(root.join(Path::new(format!("./{}", request_headers[1]))))
} else {
Err("Bad headers")
}
},
None => {
Err("Empty headers")
}
}
}
fn force_write(stream: Result<net::tcp::TcpStream, IoError>, content: &[u8]) {
let mut stream = stream;
match stream.as_mut() {
Err(_) => { debug!("Well. I wanted to safetly write to a... BROKEN stream."); },
Ok(res) => {
// ?? wtf
match res.write(content) {
Err(_) => { },
Ok(_) => { }
}
}
}
}
}
The problem is that when I try to stream a file I'm getting threads(due to the streams not freed?) left open and RAM usage is increasing slowly. I've narrowed it down to the streaming part because for instance if I'll put sleep(Duration::seconds(2)) and not try to stream a file, the queue will grow and over time go back to zero. Such thing doesn't happen when I try to stream a file.
Clean state:
After running httpref:
And future requests are waiting forever for streams lock. I've tried hunting for infinite loops somewhere but without a success - everything seems to work fine.
Do you have any suggestion what might cause such behavior?
You are missing your "Content-Length" header assuming the content of the file is loaded into body or you can get len some other way try
stream.write(HTTP_SUCCESS.as_bytes());
stream.write(CONTENT_TYPE_HTML.as_bytes());
stream.write(format!("Content-length: {}", body.len())).unwrap();

Resources