Reputation: 1521
This is my code:
use std::net;
use std::thread;
fn scan_port(host: &str, port: u16) -> bool {
let host = host.to_string();
let port = port;
let t = thread::spawn(move || net::TcpStream::connect((host.as_str(), port)).is_ok());
t.join().unwrap()
}
How do I create a situation where the thread will be terminated or killed if the connection didn't finish in N seconds?
The reason for all of this is that Rust has no way to set a socket connection timeout so I have no way to ensure the program won't get stuck.
Upvotes: 27
Views: 24511
Reputation: 757
You may use tokio.
use std::net;
use std::time::Duration;
use tokio::time::error::Elapsed;
async fn scan_port(host: &str, port: u16) -> Result<bool, Elapsed> {
tokio::time::timeout(Duration::from_secs(10), async {
net::TcpStream::connect((host, port)).is_ok()
})
.await
}
Upvotes: 3
Reputation: 121
Here is an abstraction so you can run any function with a timeout
use std::{
collections::HashMap,
sync::mpsc::{self, RecvTimeoutError},
thread,
time::Duration,
};
#[derive(Debug)]
struct TimeoutError;
fn run_with_timeout<F, T>(f: F, timeout: Duration) -> Result<T, TimeoutError>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let (tx, rx) = mpsc::channel();
let _ = thread::spawn(move || {
let result = f();
match tx.send(result) {
Ok(()) => {} // everything good
Err(_) => {} // we have been released, don't panic
}
});
match rx.recv_timeout(timeout) {
Ok(result) => Ok(result),
Err(RecvTimeoutError::Timeout) => Err(TimeoutError),
Err(RecvTimeoutError::Disconnected) => unreachable!(),
}
}
#[allow(dead_code)]
#[derive(Debug)]
struct Foo {
bar: String,
bar_vec: Vec<String>,
bar_map: HashMap<String, String>,
}
fn return_foo() -> Foo {
Foo {
bar: "bar".to_string(),
bar_vec: vec!["bar".to_string()],
bar_map: [("bar".to_string(), "bar".to_string())]
.iter()
.cloned()
.collect(),
}
}
fn main() {
// This will timeout
let result = run_with_timeout(
|| {
thread::sleep(Duration::from_secs(2));
42
},
Duration::from_secs(1),
);
println!("Result: {:?}", result);
// This will not timeout
let result = run_with_timeout(
|| {
thread::sleep(Duration::from_secs(2));
42
},
Duration::from_secs(3),
);
println!("Result: {:?}", result);
// This will timeout (Custom type)
let result = run_with_timeout(
|| {
thread::sleep(Duration::from_secs(2));
return_foo()
},
Duration::from_secs(1),
);
println!("Result: {:?}", result);
// This will not timeout (Custom type)
let result = run_with_timeout(
|| {
thread::sleep(Duration::from_secs(2));
return_foo()
},
Duration::from_secs(3),
);
println!("Result: {:?}", result);
}
Upvotes: 3
Reputation: 24541
The answer by @ker will always wait 5 seconds, even if the connection finishes more quickly. Here is a similar approach where the timeout and network request both happen on separate threads, and the first one finished wins:
let (sender, receiver) = mpsc::channel();
let tsender = sender.clone();
let t = thread::spawn(move || {
match sender.send(Ok(net::TcpStream::connect((host.as_str(), port)))) {
Ok(()) => {}, // everything good
Err(_) => {}, // we have been released, don't panic
}
});
let timer = thread::spawn(move || {
thread::sleep(Duration::from_millis(5000));
match tsender.send(Err(MyTimeoutError)) {
Ok(()) => {}, // oops, we timed out
Err(_) => {}, // great, the request finished already
}
});
return receiver.recv().unwrap();
But as long as you're doing that, you might as well just use recv_timeout
instead:
let (sender, receiver) = mpsc::channel();
let t = thread::spawn(move || {
match sender.send(net::TcpStream::connect((host.as_str(), port))) {
Ok(()) => {}, // everything good
Err(_) => {}, // we have been released, don't panic
}
});
return receiver.recv_timeout(Duration::from_millis(5000));
Upvotes: 14
Reputation: 31173
As @Shepmaster noted: it's a bad idea to terminate threads.
What you can do instead is to give the thread a Sender
through which it should notify you if it has successfully opened a connection (maybe even by sending you the handle). Then you can let your main thread sleep
for the time you wish to wait. When your thread wakes up, it checks its corresponding Receiver
for some sign of life from the thread. In case the thread did not answer, just release it into the wild by dropping the JoinHandle
and the Receiver
. It's not like it's consuming cpu-time (it's blocked), and it's not consuming too much memory. If it ever unblocks, it'll detect that the Sender
is not connected and can shut down for good.
Of course you should not have bazillions of these open threads, because they still use resources (memory and system thread handles), but on a normal system that's not too much of an issue.
Example:
use std::net;
use std::thread;
use std::sync::mpsc;
fn scan_port(host: &str, port: u16) -> bool {
let host = host.to_string();
let port = port;
let (sender, receiver) = mpsc::channel();
let t = thread::spawn(move || {
match sender.send(net::TcpStream::connect((host.as_str(), port))) {
Ok(()) => {}, // everything good
Err(_) => {}, // we have been released, don't panic
}
});
thread::sleep(std::time::Duration::new(5, 0));
match receiver.try_recv() {
Ok(Ok(handle)) => true, // we have a connection
Ok(Err(_)) => false, // connecting failed
Err(mpsc::TryRecvError::Empty) => {
drop(receiver);
drop(t);
// connecting took more than 5 seconds
false
},
Err(mpsc::TryRecvError::Disconnected) => unreachable!(),
}
}
Upvotes: 12