Recovering from `panic!` in another thread - rust

I know that in Rust there is no try/catch, and you can't throw a rolling save from the thread that is currently panicking.
I know you should not create and handle errors like this. This is just for example's sake.
However, I am wondering what the best way to recover from a panic is. This is what I have now:
use std::thread;
fn main() {
println!("Hello, world!");
let h = thread::spawn(|| {
thread::sleep_ms(1000);
panic!("boom");
});
let r = h.join();
match r {
Ok(r) => println!("All is well! {:?}", r),
Err(e) => println!("Got an error! {:?}", e)
}
println!("Exiting main!");
}
Is there a better way to handle errors from other threads? Is there a way to capture the message of the panic? This seems to only tell me that the error is of type Any. Thanks!

Putting aside "you should be using Result where possible," yes, this is basically how you catch a panic in Rust. Keep in mind that "recover" is perhaps not the best way of phrasing this in Rust. You don't really recover from panics in Rust, you isolate them, then detect them. There is no On Error Resume Next :P.
That said, there are two things to add to your example. First is how to get at the panic message. The key observation is that Any, in order to be used, must be explicitly downcast to the exact, concrete type it contains. In this case, since the panic message is a &'static str, you need to downcast to that.
The second thing is that there is a new API in nightly called catch_panic that lets you isolate a panic without having to start a thread. That said, it comes with the same restrictions as spawning a new thread: you cannot pass a non-'static reference across the isolation boundary. Note that this is an unstable addition; there are no guarantees about stability yet, and you'll need a nightly compiler to access it.
Here is an example which shows both of those. You can also run this on the Rust Playpen.
#![feature(catch_panic)]
use std::thread;
fn main() {
println!("Hello, world!");
let h = thread::spawn(|| {
thread::sleep_ms(500);
panic!("boom");
});
let r = h.join();
handle(r);
let r = thread::catch_panic(|| {
thread::sleep_ms(500);
panic!(String::from("boom again!"));
});
handle(r);
println!("Exiting main!");
}
fn handle(r: thread::Result<()>) {
match r {
Ok(r) => println!("All is well! {:?}", r),
Err(e) => {
if let Some(e) = e.downcast_ref::<&'static str>() {
println!("Got an error: {}", e);
} else {
println!("Got an unknown error: {:?}", e);
}
}
}
}

Related

Rust: How to fix borrowed value does not live long enough

I have simple client/server application. I am receiving message on the server side from client but I want to send that response to the channel from server to other file and I am receiving error "borrowed value does not live long enough".
I have searched in the stack overflow for similar previous questions but not getting enough understanding of lifetime. Is there a good documentation or if simple example available on this topic?
For now if someone can help me to fix this code (may be edit the portion of code which needs to fix) that would be helpful.
Thanks in advance.
Server side:
use std::os::unix::net::UnixDatagram;
use std::path::Path;
fn unlink_socket (path: impl AsRef<Path>) {
let path = path.as_ref();
if Path::new(path).exists() {
let result = std::fs::remove_file(path);
match result {
Err(e) => {
println!("Couldn't remove the file: {:?}", e);
},
_ => {}
}
}
}
pub fn tcp_datagram_server() {
pub static FILE_PATH: &'static str = "/tmp/datagram.sock";
let (tx, rx) = mpsc::channel();
let mut buf = vec![0; 1024];
unlink_socket(FILE_PATH);
let socket = match UnixDatagram::bind(FILE_PATH) {
Ok(socket) => socket,
Err(e) => {
println!("Couldn't bind: {:?}", e);
return;
}
};
println!("Waiting for client to connect...");
loop {
let received_bytes = socket.recv(buf.as_mut_slice()).expect("recv function failed");
println!("Received {:?}", received_bytes);
let received_message = from_utf8(buf.as_slice()).expect("utf-8 convert failed");
tx.clone().send(received_message);
}
}
fn main() {
tcp_datagram_server();
}
client side:
use std::sync::mpsc;
use std::os::unix::net::UnixDatagram;
use std::path::Path;
use std::io::prelude::*;
pub fn tcp_datagram_client() {
pub static FILE_PATH: &'static str = "/tmp/datagram.sock";
let socket = UnixDatagram::unbound().unwrap();
match socket.connect(FILE_PATH) {
Ok(socket) => socket,
Err(e) => {
println!("Couldn't connect: {:?}", e);
return;
}
};
println!("TCP client Connected to TCP Server {:?}", socket);
loop {
socket.send(b"Hello from client to server").expect("recv function failed");
}
}
fn main() {
tcp_datagram_client();
}
Error I am getting
error[E0597]: `buf` does not live long enough
--> src/unix_datagram_server.rs:38:42
|
38 | let received_message = from_utf8(buf.as_slice()).expect("utf-8 convert failed");
| ^^^ borrowed value does not live long enough
...
41 | }
| -
| |
| `buf` dropped here while still borrowed
| borrow might be used here, when `tx` is dropped and runs the `Drop` code for type `std::sync::mpsc::Sender`
|
= note: values in a scope are dropped in the opposite order they are defined
error: aborting due to previous error; 8 warnings emitted
For now if someone can help me to fix this code (may be edit the portion of code which needs to fix) that would be helpful.
Well the message seems rather clear. send does exactly what it says it does, it sends the parameter through the channel. This means the data must live long enough and remain valid "forever" (it needs to be alive and valid in the channel, as well as when fetched from it by the receiver).
That is not the case here. rustc can't understand that the function never returns, and it can panic anyway which will end up the same: the function will terminate, which will invalidate buf. Since received_message borrows buf, that means received_message can't be valid after the function has terminated. But at that point the message would still be in the channel waiting to be read (or retrieved by the receiver doing who knows what).
Therefore your construction is not allowed.
A second issue is that you're overwriting the buffer data on every loop, which has the same effect of breaking the message you sent during the previous iteration, and thus is not correct either. Though Rust won't let you do that either: if you work around the first error it will tell you that there's an outstanding shared borrow (the message sent through the channel) so you can't modify the backing buffer in the following iteration.
The solution is quite simple: have each iteration create an owned string (copying the current iteration's message) and send that through the channel:
tx.clone().send(received_message.to_string());
Also, these are more style / inefficiency remarks but:
The clone() on tx is completely redundant. The point of having a sender that is Clone is being able to send from multiple threads (hence mp in the channel name, that's for multiple producers). Here you have a single thread, the original sender works fine.
.as_slice() and .as_mut_slice() are rarely used unless necessary, which they aren't here: array references coerce to slices, so you can just use &mut buf and &buf. And why are you calling Path::new on something that's already a path? It doesn't do anything but it's not useful either.
It is rather annoying that your snippet is missing multiple imports and thus doesn't even compile as is.
From more of a unixy perspective, errors are usually printed on stderr. In Rust, eprintln does that for you (otherwise working in the same way println does). And I don't understand the purpose of marking a lexically nested static pub. Since the static is inside the function it's not even visible to the function's siblings, to say nothing of external callers. As a result I'd end up with this:
use std::os::unix::net::UnixDatagram;
use std::path::Path;
use std::sync::mpsc;
use std::str::from_utf8;
fn unlink_socket (path: impl AsRef<Path>) {
let path = path.as_ref();
if path.exists() {
if let Err(e) = std::fs::remove_file(path) {
eprintln!("Couldn't remove the file: {:?}", e);
}
}
}
static FILE_PATH: &'static str = "/tmp/datagram.sock";
pub fn tcp_datagram_server() {
unlink_socket(FILE_PATH);
let socket = match UnixDatagram::bind(FILE_PATH) {
Ok(socket) => socket,
Err(e) => {
eprintln!("Couldn't bind: {:?}", e);
return;
}
};
let (tx, _) = mpsc::channel();
let mut buf = vec![0; 1024];
println!("Waiting for client to connect...");
loop {
let received_bytes = socket.recv(&mut buf).expect("recv function failed");
println!("Received {:?}", received_bytes);
let received_message = from_utf8(&buf).expect("utf-8 convert failed");
tx.send(received_message.to_string());
}
}
There's a hint in the compiler message, that values in a scope are dropped in the opposite order they are defined in, and in the example, buf is defined after tx, which means it will be dropped before tx. Since a reference to buf (in the form of received_message) is passed to tx.send(), then buf should live longer that tx, and therefore switching the definition order will fix this particular error (ie. switch lines 19 and 20).

Rust futures `try_filter_map` causing panic: 'async fn resumed after completion'

I have a Stream that generates Result's, I want to apply a try_filter_map() on these items and take the first item that does not result in an error.
Consider the following example: (Playground)
use futures::{pin_mut, prelude::*};
use tokio;
#[tokio::main]
async fn main() {
let s = stream::iter(vec![Ok(1), Ok(2), Ok(3)])
.try_filter_map(|v| async move { if v == 1 { Err("Ohno!") } else { Ok(Some(v)) } })
.inspect_err(|err| println!("Error {:?}", err))
.filter_map(|r| future::ready(r.ok()));
pin_mut!(s);
let result = s.next().await;
println!("{:?}", result);
}
When I run this code I receive the following error:
Error "Ohno!"
thread 'main' panicked at '`async fn` resumed after completion'
I am fairly new to Rust and could use some help to solve this issue.
It seems like this is a bug. Until a new release fixes the issue, it looks like you are using try_filter_map without actually filtering the stream? If that is the case, you can use and_then instead:
let s = stream::iter(vec![Ok(1), Ok(2), Ok(3)])
.and_then(|v| future::ready(if v == 1 { Err("Ohno!") } else { Ok(v) }))
.inspect_err(|err| println!("Error {:?}", err))
.filter_map(|r| future::ready(r.ok()));

If a Result returns Err(_), I want the whole function to return a HTTP request error

I'm trying to use the Iron framework to build a simple backend in Rust. This handler is just supposed to return the content of a certain file and I can get it to work properly with unwrap() but I want to try to do proper error handling. This is how I would imagine it would look like:
fn get_content(res: &mut Request) -> IronResult<Response> {
let mut id = String::new();
res.body.read_to_string(&mut id).unwrap();
let file_path_string = &("../content/".to_string() + &id + ".rdt");
// TODO: Error handling
match File::open(file_path_string) {
Ok(f) => {
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Err(err) => Err(Response::with(((status::InternalServerError), "File not found")))
};
}
This throws the error not all control paths return a value [E0269], which is fine. But if I add a response after the match part:
match File::open(file_path_string) {
Ok(f) => {
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Err(err) => Err(Response::with(((status::InternalServerError), "File not found")))
};
Err(Response::with(((status::InternalServerError), "File not found")))
I instead get the error message:
expected `iron::error::IronError`,
found `iron::response::Response`
(expected struct `iron::error::IronError`,
found struct `iron::response::Response`) [E0308]
src/main.rs:95
Err(Response::with(((status::InternalServerError), "File not found")))
I think the problem is the collision between Rust Err and Iron Err? I'm not sure though. And I have not done much web development (or Rust for that matter) in the past so any feedback on the code is also appreciated!
UPDATE: I think this is more "The Rust Way" to do it? But I'm not sure
fn get_content(res: &mut Request) -> IronResult<Response> {
let mut id = String::new();
res.body.read_to_string(&mut id).unwrap();
let file_path_string = &("../content/".to_string() + &id + ".rdt");
// TODO: Error handling
let f;
match File::open(file_path_string) {
Ok(file) => f = file,
Err(err) => Err(HttpError::Io(err))
};
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Having the code inside the error handling seems weird as read_to_string also needs to be taken care of and that would create a nested mess of error handling? However, these matching arms are obviously of incompatible types so it won't work... any suggestions?
An Ok() takes an Response, but an Err() takes an IronError.
Hence your call Err(...) is not valid when ... is a Response!
How to correct it? Well the first step is, you must create an IronError to send back. I believe (not familiar with Iron) that Iron will automatically an appropriate error code and that it's not your job to do that. In the documentation we find one key type implementing IronError:
pub enum HttpError {
Method,
Uri(ParseError),
Version,
Header,
TooLarge,
Status,
Io(Error),
Ssl(Box<Error + 'static + Send + Sync>),
Http2(HttpError),
Utf8(Utf8Error),
// some variants omitted
}
I can't see one which allows for an arbitrary string like "file not found". However, your use case is one of an IO failure, right? So it would make sense to use HttpError::Io with the std::IoError that you got back from File::open():
match File::open(file_path_string) {
Ok(f) => {
let mut s = String::new();
f.read_to_string(&mut s);
Ok(Response::with(((status::Ok), s)))
}
Err(err) => Err(HttpError::Io(err))
};
By the way, it also fixes your "TODO: error handling"! How beautiful!
(Code untested, please feel free to edit if compilation fails)

Extend lifetime of a variable for thread

I am reading a string from a file, splitting it by lines into a vector and then I want to do something with the extracted lines in separate threads. Like this:
use std::fs::File;
use std::io::prelude::*;
use std::thread;
fn main() {
match File::open("data") {
Ok(mut result) => {
let mut s = String::new();
result.read_to_string(&mut s);
let k : Vec<_> = s.split("\n").collect();
for line in k {
thread::spawn(move || {
println!("nL: {:?}", line);
});
}
}
Err(err) => {
println!("Error {:?}",err);
}
}
}
Of course this throws an error, because s will go out of scope before the threads are started:
s` does not live long enough
main.rs:9 let k : Vec<_> = s.split("\n").collect();
^
What can I do now? I've tried many things like Box or Arc, but I couldn't get it working. I somehow need to create a copy of s which also lives in the threads. But how do I do that?
The problem, fundamentally, is that line is a borrowed slice into s. There's really nothing you can do here, since there's no way to guarantee that each line will not outlive s itself.
Also, just to be clear: there is absolutely no way in Rust to "extend the lifetime of a variable". It simply cannot be done.
The simplest way around this is to go from line being borrowed to owned. Like so:
use std::thread;
fn main() {
let mut s: String = "One\nTwo\nThree\n".into();
let k : Vec<String> = s.split("\n").map(|s| s.into()).collect();
for line in k {
thread::spawn(move || {
println!("nL: {:?}", line);
});
}
}
The .map(|s| s.into()) converts from &str to String. Since a String owns its contents, it can be safely moved into each thread's closure, and will live independently of the thread that created it.
Note: you could do this in nightly Rust using the new scoped thread API, but that is still unstable.

How to resolve this Rust lifetime issue?

I am trying to read the contents of files in a directory in parallel. I'm running into lifetime issues.
My code looks like this:
use std::io::fs;
use std::io;
use std::collections::HashMap;
use std::comm;
use std::io::File;
fn main() {
let (tx, rx) = comm::channel(); // (Sender, Receiver)
let paths = fs::readdir(&Path::new("resources/tests")).unwrap();
for path in paths.iter() {
let task_tx = tx.clone();
spawn(proc() {
match File::open(path).read_to_end() {
Ok(data) => task_tx.send((path.filename_str().unwrap(), data)),
Err(e) => fail!("Could not read one of the files! Error: {}", e)
};
});
}
let mut results = HashMap::new();
for _ in range(0, paths.len()) {
let (filename, data) = rx.recv();
results.insert(filename, data);
}
println!("{}", results);
}
The compilation error I'm getting is:
error: paths does not live long enough
note: reference must be valid for the static lifetime...
note: ...but borrowed value is only valid for the block at 7:19
I also tried to use into_iter() (or move_iter() previously) in the loop without much success.
I'm suspecting it has to do with the spawned tasks remaining alive beyond the entire main() scope, but I don't know how I can fix this situation.
The error message might be a bit confusing but what it's telling you is that you are trying to use a reference path inside of a task.
Because spawn is using proc you can only use data that you can transfer ownership of to that task (Send kind).
To solve that you can do this (you could use a move_iter but then you can't access paths after the loop):
for path in paths.iter() {
let task_tx = tx.clone();
let p = path.clone();
spawn(proc() {
match File::open(&p).read_to_end() {
The second problem is that you are trying to send &str (filename) over a channel. Same as for tasks types used must be of kind Send:
match File::open(&p).read_to_end() {
Ok(data) => task_tx.send((p.filename_str().unwrap().to_string(), data)),
Err(e) => fail!("Could not read one of the files! Error: {}", e)
};

Resources