10

It seems to be a common idiom in Rust to spawn off a thread for blocking IO so you can use non-blocking channels:

use std::sync::mpsc::channel;
use std::thread;
use std::net::TcpListener;

fn main() {
    let (accept_tx, accept_rx) = channel();

    let listener_thread = thread::spawn(move || {
        let listener = TcpListener::bind(":::0").unwrap();
        for client in listener.incoming() {
            if let Err(_) = accept_tx.send(client.unwrap()) {
                break;
            }
        }
    });
}

The problem is, rejoining threads like this depends on the spawned thread "realizing" that the receiving end of the channel has been dropped (i.e., calling send(..) returns Err(_)):

drop(accept_rx);
listener_thread.join(); // blocks until listener thread reaches accept_tx.send(..)

You can make dummy connections for TcpListeners, and shutdown TcpStreams via a clone, but these seem like really hacky ways to clean up such threads, and as it stands, I don't even know of a hack to trigger a thread blocking on a read from stdin to join.

How can I clean up threads like these, or is my architecture just wrong?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
sleeparrow
  • 1,242
  • 13
  • 22
  • 3
    The general pattern for threads that can't be cleaned up reliably immediately is to notify them to clean up, just let them run and make sure they no longer produce side-effects. For a TcpListener that's not acceptable but for outgoing requests or file operations it often is. – usr Jun 18 '15 at 10:13
  • 1
    @usr Yep, but I'm looking for a Rust solution. (It doesn't need to fit that mold exactly, I guess.) I asked this question because I haven't been able to find a way using the current (safe) API. – sleeparrow Jun 18 '15 at 11:22
  • 1
    http://www.rust-lang.org/ – sleeparrow Jun 29 '15 at 17:13

1 Answers1

3

One simply cannot safely cancel a thread reliably in Windows or Linux/Unix/POSIX, so it isn't available in the Rust standard library.

Here is an internals discussion about it.

There are a lot of unknowns that come from cancelling threads forcibly. It can get really messy. Beyond that, the combination of threads and blocking I/O will always face this issue: you need every blocking I/O call to have timeouts for it to even have a chance of being interruptible reliably. If one can't write async code, one needs to either use processes (which have a defined boundary and can be ended by the OS forcibly, but obviously come with heavier weight and data sharing challenges) or non-blocking I/O which will land your thread back in an event loop that is interruptible.

mio is available for async code. Tokio is a higher level crate based on mio which makes writing non-blocking async code even more straight forward.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
SpamapS
  • 1,087
  • 9
  • 15