Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blocking IO makes it impossible to join threads reliably #26446

Closed
ghost opened this issue Jun 20, 2015 · 10 comments
Closed

Blocking IO makes it impossible to join threads reliably #26446

ghost opened this issue Jun 20, 2015 · 10 comments

Comments

@ghost
Copy link

ghost commented Jun 20, 2015

Threads spawned to interface blocking IO with a non-blocking channel can't be cleaned up in a programmatic way.

Example:

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;
            }
        }
    });

To my knowledge, the only way to communicate to listener_thread that it should break is by dropping the receiving end of the channel:

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

The problem is, if listener_thread is blocking waiting for a client, it may never reach accept_tx.send(..), let alone reach it in a computationally timely manner.

You can make dummy connections for TcpListeners, and shutdown TcpStreams via a clone, but these are 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.

Meta

rustc --version --verbose:
rustc 1.0.0 (a59de37 2015-05-13) (built 2015-05-14)
binary: rustc
commit-hash: a59de37
commit-date: 2015-05-13
build-date: 2015-05-14
host: x86_64-unknown-linux-gnu
release: 1.0.0

@Stebalien
Copy link
Contributor

There are three possible solutions here: timeout, close the TCP listener, or select/epoll on a control channel. Eventually, we probably want all three but the simplest one to implement is closing the TCP listener (you can actually do this now with a bit of unsafe code (just manually close the file descriptor)).

Another solution is to send an interrupt signal to the listener thread but I don't know how rust wants to handle interrupts.

Regardless, this issue goes on in the RFC repo.

@ghost
Copy link
Author

ghost commented Jun 20, 2015

@Stebalien Not sure if this is the place to detail such things, but could you explain how polling a "control" channel might fix this? Wouldn't we be in the same place regarding the fact that the IO is blocking and we can't rely on it to check a channel?

In my opinion (read: "I might be wrong, but"), the only safe, semantic design I can picture is all blocking Read implementations having some way to cause them to return Err(_) on read. In which case, I like the idea of having a method that just allows you to cause a certain Err(SpecificError) once only, without any other side effects (closing the stream, etc.) This way, you can simply give the thread a chance to check the state of other things.

@Stebalien
Copy link
Contributor

Wouldn't we be in the same place regarding the fact that the IO is blocking and we can't rely on it to check a channel?

As of 1.1, you can get the raw file descriptor for a TcpListener. This means that, on Linux at least, you can use the epoll/select system calls to block on both a control file descriptor (some other file descriptor such as an eventfd) and the TcpListener's fd. You could then write to the control file descriptor causing the select/epoll call to return. Obviously, this requires a fair bit of low-level code so it would be nice if the standard library provided some abstractions here.

In my opinion (read: "I might be wrong, but"), the only safe, semantic design I can picture is all blocking Read implementations having some way to cause them to return Err(_) on read. In which case, I like the idea of having a method that just allows you to cause a certain Err(SpecificError) once only, without any other side effects (closing the stream, etc.) This way, you can simply give the thread a chance to check the state of other things.

That's what interrupts are for. If you send a SIGINT to the blocking thread, the read should stop blocking and should return an io::Error (with io::ErrorKind::Interrupted).

@alexcrichton
Copy link
Member

As @Stebalien mentions, there isn't currently a way to do this in the standard library and it's also unclear how it would be done in a "standard fashion" if at all. Unfortunately the underlying system does not give the ability to return from a blocking accept call, a different system altogether needs o be used (e.g. epoll/select as @Stebalien mentioned).

As a result I'm going to close this issue for now, but I would certainly recommend checking out mio and/or opening an RFC issue on this topic, although it may fall under the "async I/O story" more than wanting its own issue.

@anil1596
Copy link

anil1596 commented Jun 21, 2016

fn main(){
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
println!("{:?}",listener);
}
It gives the result :
TcpListener { addr: V4(127.0.0.1:8080), fd: 3 }

how can extract fd from the listener???

@Stebalien
Copy link
Contributor

@anil1596 You can use the IntoRawFd trait on unix or IntoRawHandle trait on windows.

@anil1596
Copy link

@Stebalien that worked with implementation of AsRawFd trait.

@Stebalien
Copy link
Contributor

@anil1596 What precisely are you trying to do?

@anil1596
Copy link

@Stebalien I am implementing I/O notification facility based on epoll in rust.

@Stebalien
Copy link
Contributor

@anil1596 got it. Yes, you want AsRawFd.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants