Read stdin triggered by key event without dropping first letter - multithreading

Context
I am working on a pomodoro command line app written in rust, most of it worked well but now I want to edit the text of a pomodoro item in the database. All of the actions in the app are triggered by keystrokes, pausing/resuming, quitting etc. and as well editing the text.
Now I want to read the text from stdin but the key-events are as sourced as well from stdin, but on a different thread. I came up with using a stdin.lock() - which works almost fine.
The problem
How can I read a line from stdin in the main thread, without dropping the first letter, due to the event listener being triggered in its thread, before the lock in the main thread is acquired.
expected behaviour:
press t => print Reading from stdin!
type abc<enter> => print You typed: Some("abc")
actual behaviour:
press t => print Reading from stdin!
type abc<enter> => print You typed: Some("bc")
Minimal non-working example
Here is an example that shows the described behaviour:
use failure;
use std::io::{stdin, stdout};
use std::sync::mpsc;
use std::thread;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use tui::backend::TermionBackend;
use tui::Terminal;
pub enum Event {
Input(Key),
}
#[allow(dead_code)]
pub struct Events {
rx: mpsc::Receiver<Event>,
input_handle: thread::JoinHandle<()>,
}
impl Events {
pub fn new() -> Events {
let (tx, rx) = mpsc::channel();
let input_handle = {
let tx = tx.clone();
thread::spawn(move || {
let stdin = stdin();
for evt in stdin.keys() {
match evt {
Ok(key) => {
if let Err(_) = tx.send(Event::Input(key)) {
return;
}
}
Err(_) => {}
}
}
})
};
Events {
rx,
input_handle,
}
}
pub fn next(&self) -> Result<Event, mpsc::RecvError> {
self.rx.recv()
}
}
pub fn key_handler(key: Key) -> bool {
match key {
Key::Char('t') => {
println!("Reading from stdin!");
let stdin = stdin();
let mut handle = stdin.lock();
let input = handle.read_line().unwrap();
println!("You typed: {:?}", input);
}
_ =>{
println!("no thing!");
}
};
key == Key::Char('q')
}
fn main() -> Result<(), failure::Error> {
let stdout = stdout().into_raw_mode()?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.clear()?;
terminal.hide_cursor()?;
let events = Events::new();
loop {
match events.next()? {
Event::Input(key) => {
if key_handler(key) {
break;
}
}
}
}
terminal.clear()?;
terminal.show_cursor()?;
Ok(())
}
Update
Cargo.toml
[package]
name = "mnwe"
version = "1.1.0"
edition = "2018"
autotests = false
[[bin]]
bench = false
path = "app/main.rs"
name = "mnwe"
[dependencies]
failure = "0.1"
termion = "1.5.3"
tui = "0.7"

The problem, as correctly identified, is the race on stdin().lock() between stdin.keys() in the events thread and stdin.lock() in key_handler (which the events thread tends to win, and eat one key).
For the sake of the bounty, I see four possible approaches:
At easiest, you can avoid having threads at all, and instead regularly poll for new input with termion::async_std. (It's what I ended up doing for my own tui application. In the end, you're likely to be polling the event receiver anyway, so why not poll stdin directly.)
If your problem allows it, you could do the stdin reading directly on the event thread. Instead of sending key events over the channel, you would send something you could call "user commands" over the channel:
// Channel data:
pub enum Command {
StdinInput(String),
Quit,
// Add more commands
Unknown(Key),
}
// Send side
thread::spawn(move || {
for evt in stdin().keys() {
match evt {
Ok(key) => {
let cmd = match key {
Key::Char('t') => {
println!("Reading from stdin!");
let input = stdin().lock().read_line().unwrap();
Command::StdinInput(input.unwrap_or(String::new()))
}
Key::Char('q') => Command::Quit,
_ => Command::Unknown(key),
};
if let Err(_) = tx.send(cmd) {
return;
}
}
Err(_) => {}
}
}
})
// Receive side
loop {
match events.next()? {
Command::StdinInput(input) => println!("You typed: {}", input),
Command::Quit => break,
Command::Unknown(k) => println!("no thing: {:?}", k),
}
}
If you must absolutely have access stdin from to threads, I would not recommend using a CondVar, but passing the sender of another channel through the event channel. Why? Because it's much harder to get wrong. Any channel will do, but I think oneshot::channel() is most suitable here:
// Channel data
pub enum Event {
Input(Key, oneshot::Sender<()>),
}
// Send side
for evt in stdin.keys() {
let (shot_send, shot_recv) = oneshot::channel();
match evt {
Ok(key) => {
if let Err(_) = tx.send(Event::Input(key, shot_send)) {
return;
}
shot_recv.recv().ok();
}
Err(_) => {}
}
}
// Receive side
loop {
match events.next()? {
Event::Input(key, done) => {
match key {
Key::Char('t') => {
println!("Reading from stdin!");
let stdin = stdin();
let mut handle = stdin.lock();
let input = handle.read_line().unwrap();
println!("You typed: {:?}", input);
// It doesn't really matter whether we send anything
// but using the channel here avoids mean surprises about when it gets dropped
done.send(()).ok();
}
Key::Char('q') => break,
_ => println!("no thing!"),
};
}
}
}
You could also not do the stdin().lock().read_line() at all, but reassemble the user input line from the key stroke events. I wouldn't do that.

Related

How to pass a value over a channel without borrow checking issues?

In the following code, I understand why I'm not allowed to do this(I think), but I'm not sure what to do to fix the issue. I'm simply trying to perform an action based upon an incoming message on a UDPSocket. However, by sending the reference to the slice over the channel, I get a problem where the buffer doesn't live long enough. I'm hoping for some suggestions because I don't know enough about Rust to move forward.
fn main() -> std::io::Result<()> {
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
match rx.try_recv() {
Ok(msg) => {
match msg {
"begin" => // run an operation
"end" | _ => // kill the previous operation
}
}
Err = { //Error Handling }
}
}
// start listener
let socket: UdpSocket = UdpSocket::bind("0.0.0.0:9001")?;
loop {
let mut buffer = [0; 100];
let (length, src_address) = socket.recv_from(&mut buffer)?;
println!("Received message of {} bytes from {}", length, src_address);
let cmd= str::from_utf8(&buffer[0..length]).unwrap(); // <- buffer does not live long enough
println!("Command: {}", cmd);
tx.send(cmd).expect("unable to send message to channel"); // Error goes away if I remove this.
}
}
Generally you should avoid sending non-owned values over a channel since its unlikely that a lifetime would be valid for both the sender and receiver (its possible to do, but you'd have to plan for it).
In this situation, you're trying to share pass &str across the channel but since it just references buffer which isn't guaranteed to exist whenever rx receives it, you get a borrow checking error. You would probably want to convert the &str into an owned String and pass that over the channel:
use std::net::UdpSocket;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || loop {
match rx.recv().as_deref() {
Ok("begin") => { /* run an operation */ }
Ok("end") => { /* kill the previous operation */ }
Ok(_) => { /* unknown */ }
Err(_) => { break; }
}
});
let socket = UdpSocket::bind("0.0.0.0:9001").unwrap();
loop {
let mut buffer = [0; 100];
let (length, src_address) = socket.recv_from(&mut buffer).unwrap();
let cmd = std::str::from_utf8(&buffer[0..length]).unwrap();
tx.send(cmd.to_owned()).unwrap();
}
}
As proposed in the comments, you can avoid allocating a string if you parse the value into a known value for an enum and send that across the channel instead:
use std::net::UdpSocket;
use std::sync::mpsc;
enum Command {
Begin,
End,
}
fn main() {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || loop {
match rx.recv() {
Ok(Command::Begin) => { /* run an operation */ }
Ok(Command::End) => { /* kill the previous operation */ }
Err(_) => { break; }
}
});
let socket = UdpSocket::bind("0.0.0.0:9001").unwrap();
loop {
let mut buffer = [0; 100];
let (length, src_address) = socket.recv_from(&mut buffer).unwrap();
let cmd = std::str::from_utf8(&buffer[0..length]).unwrap();
let cmd = match cmd {
"begin" => Command::Begin,
"end" => Command::End,
_ => panic!("unknown command")
};
tx.send(cmd).unwrap();
}
}

Receiver on tokio's mpsc channel only receives messages when buffer is full

I've spent a few hours trying to figure this out and I'm pretty done. I found the question with a similar name, but that looks like something was blocking synchronously which was messing with tokio. That very well may be the issue here, but I have absolutely no idea what is causing it.
Here is a heavily stripped down version of my project which hopefully gets the issue across.
use std::io;
use futures_util::{
SinkExt,
stream::{SplitSink, SplitStream},
StreamExt,
};
use tokio::{
net::TcpStream,
sync::mpsc::{channel, Receiver, Sender},
};
use tokio_tungstenite::{
connect_async,
MaybeTlsStream,
tungstenite::Message,
WebSocketStream,
};
#[tokio::main]
async fn main() {
connect_to_server("wss://a_valid_domain.com".to_string()).await;
}
async fn read_line() -> String {
loop {
let mut str = String::new();
io::stdin().read_line(&mut str).unwrap();
str = str.trim().to_string();
if !str.is_empty() {
return str;
}
}
}
async fn connect_to_server(url: String) {
let (ws_stream, _) = connect_async(url).await.unwrap();
let (write, read) = ws_stream.split();
let (tx, rx) = channel::<ChannelMessage>(100);
tokio::spawn(channel_thread(write, rx));
tokio::spawn(handle_std_input(tx.clone()));
read_messages(read, tx).await;
}
#[derive(Debug)]
enum ChannelMessage {
Text(String),
Close,
}
// PROBLEMATIC FUNCTION
async fn channel_thread(
mut write: SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
mut rx: Receiver<ChannelMessage>,
) {
while let Some(msg) = rx.recv().await {
println!("{:?}", msg); // This only fires when buffer is full
match msg {
ChannelMessage::Text(text) => write.send(Message::Text(text)).await.unwrap(),
ChannelMessage::Close => {
write.close().await.unwrap();
rx.close();
return;
}
}
}
}
async fn read_messages(
mut read: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
tx: Sender<ChannelMessage>,
) {
while let Some(msg) = read.next().await {
let msg = match msg {
Ok(m) => m,
Err(_) => continue
};
match msg {
Message::Text(m) => println!("{}", m),
Message::Close(_) => break,
_ => {}
}
}
if !tx.is_closed() {
let _ = tx.send(ChannelMessage::Close).await;
}
}
async fn handle_std_input(tx: Sender<ChannelMessage>) {
loop {
let str = read_line().await;
if tx.is_closed() {
break;
}
tx.send(ChannelMessage::Text(str)).await.unwrap();
}
}
As you can see, what I'm trying to do is:
Connect to a websocket
Print outgoing messages from the websocket
Forward any input from stdin to the websocket
Also a custom heartbeat solution which was trimmed out
The issue lies in the channel_thread() function. I move the websocket writer into this function as well as the channel receiver. The issue is, it only loops over the sent objects when the buffer is full.
I've spent a lot of time trying to solve this, any help is greatly appreciated.
Here, you make a blocking synchronous call in an async context:
async fn read_line() -> String {
loop {
let mut str = String::new();
io::stdin().read_line(&mut str).unwrap();
// ^^^^^^^^^^^^^^^^^^^
// This is sync+blocking
str = str.trim().to_string();
if !str.is_empty() {
return str;
}
}
}
You never ever make blocking synchronous calls in an async context, because that prevents the entire thread from running other async tasks. Your channel receiver task is likely also assigned to this thread, so it's having to wait until all the blocking calls are done and whatever invokes this function yields back to the async runtime.
Tokio has its own async version of stdin, which you should use instead.

Tokio channel sends, but doesn't receive

TL;DR I'm trying to have a background thread that's ID'd that is controlled via that ID and web calls, and the background threads doesn't seem to be getting the message via all the types of channels I've tried.
I've tried both the std channels as well as tokio's, and of those I've tried all but the watcher type from tokio. All have the same result which probably means that I've messed something up somewhere without realizing it, but I can't find the issue:
use std::collections::{
hash_map::Entry::{Occupied, Vacant},
HashMap,
};
use std::sync::Arc;
use tokio::sync::mpsc::{self, UnboundedSender};
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use uuid::Uuid;
use warp::{http, Filter};
#[derive(Default)]
pub struct Switcher {
pub handle: Option<JoinHandle<bool>>,
pub pipeline_end_tx: Option<UnboundedSender<String>>,
}
impl Switcher {
pub fn set_sender(&mut self, tx: UnboundedSender<String>) {
self.pipeline_end_tx = Some(tx);
}
pub fn set_handle(&mut self, handle: JoinHandle<bool>) {
self.handle = Some(handle);
}
}
const ADDR: [u8; 4] = [0, 0, 0, 0];
const PORT: u16 = 3000;
type RunningPipelines = Arc<RwLock<HashMap<String, Arc<RwLock<Switcher>>>>>;
#[tokio::main]
async fn main() {
let running_pipelines = Arc::new(RwLock::new(HashMap::<String, Arc<RwLock<Switcher>>>::new()));
let session_create = warp::post()
.and(with_pipelines(running_pipelines.clone()))
.and(warp::path("session"))
.then(|pipelines: RunningPipelines| async move {
println!("session requested OK!");
let id = Uuid::new_v4();
let mut switcher = Switcher::default();
let (tx, mut rx) = mpsc::unbounded_channel::<String>();
switcher.set_sender(tx);
let t = tokio::spawn(async move {
println!("Background going...");
//This would be something processing in the background until it received the end signal
match rx.recv().await {
Some(v) => {
println!(
"Got end message:{} YESSSSSS#!##!!!!!!!!!!!!!!!!1111eleven",
v
);
}
None => println!("Error receiving end signal:"),
}
println!("ABORTING HANDLE");
true
});
let ret = HashMap::from([("session_id", id.to_string())]);
switcher.set_handle(t);
{
pipelines
.write()
.await
.insert(id.to_string(), Arc::new(RwLock::new(switcher)));
}
Ok(warp::reply::json(&ret))
});
let session_end = warp::delete()
.and(with_pipelines(running_pipelines.clone()))
.and(warp::path("session"))
.and(warp::query::<HashMap<String, String>>())
.then(
|pipelines: RunningPipelines, p: HashMap<String, String>| async move {
println!("session end requested OK!: {:?}", p);
match p.get("session_id") {
None => Ok(warp::reply::with_status(
"Please specify session to end",
http::StatusCode::BAD_REQUEST,
)),
Some(id) => {
let mut pipe = pipelines.write().await;
match pipe.entry(String::from(id)) {
Occupied(handle) => {
println!("occupied");
let (k, v) = handle.remove_entry();
drop(pipe);
println!("removed from hashmap, key:{}", k);
let s = v.write().await;
if let Some(h) = &s.handle {
if let Some(tx) = &s.pipeline_end_tx {
match tx.send("goodbye".to_string()) {
Ok(res) => {
println!(
"sent end message|{:?}| to fpipeline: {}",
res, id
);
//Added this to try to get it to at least Error on the other side
drop(tx);
},
Err(err) => println!(
"ERROR sending end message to pipeline({}):{}",
id, err
),
};
} else {
println!("no sender channel found for pipeline: {}", id);
};
h.abort();
} else {
println!(
"no luck finding the value in handle in the switcher: {}",
id
);
};
}
Vacant(_) => {
println!("no luck finding the handle in the pipelines: {}", id)
}
};
Ok(warp::reply::with_status("done", http::StatusCode::OK))
}
}
},
);
let routes = session_create
.or(session_end)
.recover(handle_rejection)
.with(warp::cors().allow_any_origin());
println!("starting server...");
warp::serve(routes).run((ADDR, PORT)).await;
}
async fn handle_rejection(
err: warp::Rejection,
) -> Result<impl warp::Reply, std::convert::Infallible> {
Ok(warp::reply::json(&format!("{:?}", err)))
}
fn with_pipelines(
pipelines: RunningPipelines,
) -> impl Filter<Extract = (RunningPipelines,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || pipelines.clone())
}
depends:
[dependencies]
warp = "0.3"
tokio = { version = "1", features = ["full"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] }
Results when I boot up, send a "create" request, and then an "end" request with the received ID:
starting server...
session requested OK!
Background going...
session end requested OK!: {"session_id": "6b984a45-38d8-41dc-bf95-422f75c5a429"}
occupied
removed from hashmap, key:6b984a45-38d8-41dc-bf95-422f75c5a429
sent end message|()| to fpipeline: 6b984a45-38d8-41dc-bf95-422f75c5a429
You'll notice that the background thread starts (and doesn't end) when the "create" request is made, but when the "end" request is made, while everything appears to complete successfully from the request(web) side, the background thread doesn't ever receive the message. As I've said I've tried all different channel types and moved things around to get it into this configuration... i.e. flattened and thread safetied as much as I could or at least could think of. I'm greener than I would like in rust, so any help would be VERY appreciated!
I think that the issue here is that you are sending the message and then immediately aborting the background task:
tx.send("goodbye".to_string());
//...
h.abort();
And the background task does not have time to process the message, as the abort is of higher priority.
What you need is to join the task, not to abort it.
Curiously, tokio tasks handles do not have a join() method, instead you wait for the handle itself. But for that you need to own the handle, so first you have to extract the handle from the Switcher:
let mut s = v.write().await;
//steal the task handle
if let Some(h) = s.handle.take() {
//...
tx.send("goodbye".to_string());
//...
//join the task
h.await.unwrap();
}
Note that joining a task may fail, in case the task is aborted or panicked. I'm just panicking in the code above, but you may want to do something different.
Or... you could not to wait for the task. In tokio if you drop a task handle, it will be detached. Then, it will finish when it finishes.

handling user input

How can I have some part of my application reading user input and also listening for a shutdown.
According to tokio-docs to do this type of things I should use blocking IO in a spawned task.
For interactive uses, it is recommended to spawn a thread dedicated to user input and use blocking IO directly in that thread.
And so I did with something like this
async fn read_input(mut rx: watch::Receiver<&str>) {
let mut line = String::new();
let stdin = io::stdin();
loop {
stdin.lock().read_line(&mut line).expect("Could not read line");
let op = line.trim_right();
if op == "EXIT" {
break;
} else if op == "send" {
// send_stuff();
}
line.clear();
}
}
the thing is, how can I check the receiver channel for a shutdown and break this loop?
If I await the code will block.
Am I approaching this with the wrong concept/architecture ?
Without managing your own thread, there has to be a way to use some non-blocking OS API on stdin and wrap it for tokio (tokio::io::Stdin 1.12 uses a blocking variant).
Otherwise if we follow the advice from the docs and spawn our own thread,
this is how it could be done:
fn start_reading_stdin_lines(
sender: tokio::sync::mpsc::Sender<String>,
runtime: tokio::runtime::Handle
) {
std::thread::spawn(move || {
let stdin = std::io::stdin();
let mut line_buf = String::new();
while let Ok(_) = stdin.read_line(&mut line_buf) {
let line = line_buf.trim_end().to_string();
line_buf.clear();
let sender2 = sender.clone();
runtime.spawn(async move {
let result = sender2.send(line).await;
if let Err(error) = result {
println!("start_reading_stdin_lines send error: {:?}", error);
}
});
}
});
}
fn start_activity_until_shutdown(watch_sender: tokio::sync::watch::Sender<bool>) {
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
println!("exiting after a signal...");
let result = watch_sender.send(true);
if let Err(error) = result {
println!("watch_sender send error: {:?}", error);
}
});
}
async fn read_input(
mut line_receiver: tokio::sync::mpsc::Receiver<String>,
mut watch_receiver: tokio::sync::watch::Receiver<bool>
) {
loop {
tokio::select! {
Some(line) = line_receiver.recv() => {
println!("line: {}", line);
// process the input
match line.as_str() {
"exit" => {
println!("exiting manually...");
break;
},
"send" => {
println!("send_stuff");
}
unexpected_line => {
println!("unexpected command: {}", unexpected_line);
}
}
}
Ok(_) = watch_receiver.changed() => {
println!("shutdown");
break;
}
}
}
}
#[tokio::main]
async fn main() {
let (line_sender, line_receiver) = tokio::sync::mpsc::channel(1);
start_reading_stdin_lines(line_sender, tokio::runtime::Handle::current());
let (watch_sender, watch_receiver) = tokio::sync::watch::channel(false);
// this will send a shutdown signal at some point
start_activity_until_shutdown(watch_sender);
read_input(line_receiver, watch_receiver).await;
}
Potential improvements:
if you are ok with tokio_stream wrappers, this could be combined more elegantly with start_reading_stdin_lines producing a stream of lines and mapping them to typed commands. read_input could be then based on StreamMap instead of select!.
enabling experimental stdin_forwarders feature makes reading lines easier with a for loop over lines()

Websocket client message payload "does not live long enough"

I modified an existing websocket client to save the message payload from the websocket server:
fn main()
{
let mut globaltest = "";
// ...some othercode
let receive_loop = thread::spawn(move || {
// Receive loop
for message in receiver.incoming_messages() {
let message: Message = match message {
Ok(m) => m,
Err(e) => {
println!("Receive Loop: {:?}", e);
let _ = tx_1.send(Message::close());
return;
}
};
match message.opcode {
Type::Close => {
// Got a close message, so send a close message and return
let _ = tx_1.send(Message::close());
return;
}
Type::Ping => match tx_1.send(Message::pong(message.payload)) {
// Send a pong in response
Ok(()) => (),
Err(e) => {
println!("Receive Loop: {:?}", e);
return;
}
},
// Say what we received
_ => {
println!("Receive Loop: {:?}", message);
// recive from motion
let buf = &message.payload;
{
let s = match str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("{}",s);
////>>> problem here <<<
globaltest = s;
}
},
}
}
});
// ...some othercode
}
When I build, I get an error message:
nathaniel#nathaniel-virtual-machine:~/rustcoderep/rsdummywsclient$ sudo cargo build
Compiling rsdummywsclient v0.1.0 (file:///home/nathaniel/rustcoderep/rsdummywsclient)
src/main.rs:94:36: 94:51 error: `message.payload` does not live long enough
src/main.rs:94 let buf = &message.payload;
^~~~~~~~~~~~~~~
note: reference must be valid for the static lifetime...
src/main.rs:75:6: 106:4 note: ...but borrowed value is only valid for the block suffix following statement 0 at 75:5
src/main.rs:75 };
src/main.rs:76 match message.opcode {
src/main.rs:77 Type::Close => {
src/main.rs:78 // Got a close message, so send a close message and return
src/main.rs:79 let _ = tx_1.send(Message::close());
src/main.rs:80 return;
...
error: aborting due to previous error
I have no idea why. I tried a lot of solutions like Arc and Mutex, but none of them work :(
When I remove globaltest = s, the code builds and runs without problems. So I tried to write a simpler example:
use std::str;
use std::thread;
fn main() {
let mut y = 2;
let receive_loop = thread::spawn(move || {
let x = 1;
y = x;
println!("tt{:?}",y);
});
let receive_loop2 = thread::spawn(move || {
println!("tt2{:?}",y);
});
println!("{:?}",y);
}
This works... with almost the same structure.
Here is the full code, only a little different from the rust-websocket client sample:
extern crate websocket;
fn main() {
use std::thread;
use std::sync::mpsc::channel;
use std::io::stdin;
use std::str;
use websocket::{Message, Sender, Receiver};
use websocket::message::Type;
use websocket::client::request::Url;
use websocket::Client;
let mut globaltest ="";
let url = Url::parse("ws://127.0.0.1:2794").unwrap();
println!("Connecting to {}", url);
let request = Client::connect(url).unwrap();
let response = request.send().unwrap(); // Send the request and retrieve a response
println!("Validating response...");
response.validate().unwrap(); // Validate the response
println!("Successfully connected");
let (mut sender, mut receiver) = response.begin().split();
let (tx, rx) = channel();
let tx_1 = tx.clone();
let send_loop = thread::spawn(move || {
loop {
// Send loop
let message: Message = match rx.recv() {
Ok(m) => m,
Err(e) => {
println!("Send Loop: {:?}", e);
return;
}
};
match message.opcode {
Type::Close => {
let _ = sender.send_message(&message);
// If it's a close message, just send it and then return.
return;
},
_ => (),
}
// Send the message
match sender.send_message(&message) {
Ok(()) => (),
Err(e) => {
println!("Send Loop: {:?}", e);
let _ = sender.send_message(&Message::close());
return;
}
}
}
});
let receive_loop = thread::spawn(move || {
// Receive loop
for message in receiver.incoming_messages() {
let message: Message = match message {
Ok(m) => m,
Err(e) => {
println!("Receive Loop: {:?}", e);
let _ = tx_1.send(Message::close());
return;
}
};
match message.opcode {
Type::Close => {
// Got a close message, so send a close message and return
let _ = tx_1.send(Message::close());
return;
}
Type::Ping => match tx_1.send(Message::pong(message.payload)) {
// Send a pong in response
Ok(()) => (),
Err(e) => {
println!("Receive Loop: {:?}", e);
return;
}
},
// Say what we received
_ => {
println!("Receive Loop: {:?}", message);
// recive from motion
let buf = &message.payload;
{
let s = match str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("{}",s);
globaltest = s;
}
},
}
}
});
loop {
let mut input = String::new();
stdin().read_line(&mut input).unwrap();
let trimmed = input.trim();
let message = match trimmed {
"/close" => {
// Close the connection
let _ = tx.send(Message::close());
break;
}
// Send a ping
"/ping" => Message::ping(b"PING".to_vec()),
// Otherwise, just send text
_ => Message::text(trimmed.to_string()),
};
match tx.send(message) {
Ok(()) => (),
Err(e) => {
println!("Main Loop: {:?}", e);
break;
}
}
}
// We're exiting
println!("Waiting for child threads to exit");
let _ = send_loop.join();
let _ = receive_loop.join();
println!("Exited");
}
#fjh thanks your reply!
I modified my code into this, changing globaltest in receive_loop and accessing it from the main thread loop. I still get a confusing error, even after spending three hours I still cannot solve it :(
fn main() {
let mut globaltest:Arc<Mutex<String>> = Arc::new(Mutex::new(String::from("")));
//some other code...
let receive_loop = thread::spawn(move || {
// Receive loop
for message in receiver.incoming_messages() {
let message: Message = match message {
Ok(m) => m,
Err(e) => {
println!("Receive Loop: {:?}", e);
let _ = tx_1.send(Message::close());
return;
}
};
match message.opcode {
Type::Close => {
// Got a close message, so send a close message and return
let _ = tx_1.send(Message::close());
return;
}
Type::Ping => match tx_1.send(Message::pong(message.payload)) {
// Send a pong in response
Ok(()) => (),
Err(e) => {
println!("Receive Loop: {:?}", e);
return;
}
},
// Say what we received
_ => {
let mut globaltest_child = globaltest.lock().unwrap();
println!("Receive Loop: {:?}", message);
// recive from motion
let buf = &message.payload;
{
let s = match str::from_utf8(buf) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
{
//>>> if I do like this, globaltest value will same like globaltest_child??
*globaltest_child = String::from(s);
println!("{:?}",globaltest_child.clone());
}
}
},
}
}
});
loop {
let message = Message::text("mtconnect");
match tx.send(message) {
Ok(()) => (),
Err(e) => {
println!("Main Loop: {:?}", e);
break;
}
}
///>>> problem here////
println!("{:?}",globaltest.clone());
thread::sleep(time::Duration::from_millis(3000));
}
}
The compiler always tells me:
athaniel#nathaniel-virtual-machine:~/rustcoderep/rsadapter$ sudo cargo run
Compiling rsadapter v0.1.0 (file:///home/nathaniel/rustcoderep/rsadapter)
src/main.rs:166:25: 166:35 error: use of moved value: `globaltest` [E0382]
src/main.rs:166 println!("{:?}",globaltest.clone());
^~~~~~~~~~
<std macros>:2:25: 2:56 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
src/main.rs:166:9: 166:45 note: in this expansion of println! (defined in <std macros>)
src/main.rs:166:25: 166:35 help: run `rustc --explain E0382` to see a detailed explanation
src/main.rs:102:35: 153:3 note: `globaltest` moved into closure environment here because it has type `alloc::arc::Arc<std::sync::mutex::Mutex<collections::string::String>>`, which is non-copyable
src/main.rs:102 let receive_loop = thread::spawn(move || {
src/main.rs:103
src/main.rs:104
src/main.rs:105 // Receive loop
src/main.rs:106 for message in receiver.incoming_messages() {
src/main.rs:107 let message: Message = match message {
...
src/main.rs:102:35: 153:3 help: perhaps you meant to use `clone()`?
error: aborting due to previous error
I still can't access globaltest in another thread.
When I remove globaltest = s, the code builds and runs without problems.
Yes, because that assignment is not safe to do. You're trying to modify a local variable declared in the main thread from within your other thread. That could lead to all sorts of problems, like data races, which is why the compiler won't let you do it.
It's difficult to say what the best way to fix this is without knowing more about what you want to do. That being said, you could probably fix this by making globaltest an Arc<Mutex<String>> instead of a &str, so you could safely access it from both threads.
Whenever you have a problem, you should spend time reducing the problem. This helps you understand where the problem is and is likely to remove extraneous details that may be confusing you.
In this case, you could start by removing all of the other match arms, replacing them with panic! calls. Then try replacing libraries with your own code, then eventually just standard library code. Eventually you will get to something much smaller that reproduces the problem.
This is called creating an MCVE, and is highly encouraged when you ask a question on Stack Overflow. However, it's 100% useful to yourself whenever you have a problem you don't yet understand. As a professional programmer, you are expected to do this legwork.
Here's one possible MCVE I was able to create:
use std::{str, thread};
fn main() {
let mut global_string = "one";
let child = thread::spawn(move || {
let payload = b"Some allocated raw data".to_vec();
let s = str::from_utf8(&payload).unwrap();
global_string = s;
});
println!("{}", global_string);
}
And it produces the same error ("reference must be valid for the static lifetime"). Specifically, the global_string variable is a &'static str, while s is a &str with a lifetime equivalent to the payload it is borrowed from. Simply put, the payload will be deallocated before the thread exits, which means that the string would point to invalid memory, which could cause a crash or security vulnerability. This is a class of errors that Rust prevents against.
This is what fjh is telling you.
Instead, you need to be able to ensure that the string will continue to live outside of the thread. The simplest way is to allocate memory that it will control. In Rust, this is a String:
use std::{str, thread};
fn main() {
let mut global_string = "one".to_string();
let child = thread::spawn(move || {
let payload = b"Some allocated raw data".to_vec();
let s = str::from_utf8(&payload).unwrap();
global_string = s.to_string();
});
println!("{}", global_string);
}
Now we've changed the error to "use of moved value: global_string", because we are transferring ownership of the String from main to the thread. We could try to fix that by cloning the string before we give it to the thread, but then we wouldn't be changing the outer one that we want.
Even if we could set the value in the outer thread, we'd get in trouble because we'd be creating a race condition where two threads are acting in parallel on one piece of data. You have no idea what state the variable is in when you try to access it. That's where a Mutex comes in. It makes it so that multiple threads can safely share access to one piece of data, one at a time.
However, you still have the problem that only one thread can own the Mutex at a time, but you need two threads to own it. That's where Arc comes in. An Arc can be cloned and the clone can be given to another thread. Both Arcs then point to the same value and ensure it is cleaned up when nothing is using it any more.
Note that we have to clone the Arc<Mutex<String>>> before we spawn the thread because we are transferring ownership of it into the thread:
use std::{str, thread};
use std::sync::{Arc, Mutex};
fn main() {
let global_string = Arc::new(Mutex::new("one".to_string()));
let inner = global_string.clone();
let child = thread::spawn(move || {
let payload = b"Some allocated raw data".to_vec();
let s = str::from_utf8(&payload).unwrap();
*inner.lock().unwrap() = s.to_string();
});
child.join().unwrap();
let s = global_string.lock().unwrap();
println!("{}", *s);
}

Resources