paritytech / parity-tokio-ipc

Parity tokio-ipc
Apache License 2.0
76 stars 48 forks source link

Simple pipe-server hanging forever #15

Open hankbao opened 5 years ago

hankbao commented 5 years ago

Thanks for providing this crate. I'm new to rust and tokio, so please forgive me if this looks dumb.

However, I encountered a hanging issue when using it on Windows. I'm building an IpcServer as a Future from an Incoming and then I run it with tokio. I got an IPC client which can connect (with CreateFile) to the pipe successfully. The client can even write to the pipe asynchronously. But IpcServer::poll never fires again except for the first time.

I'm pretty sure this is related to my code. Would you please kindly point it out? Thanks.

use futures::prelude::*;
use parity_tokio_ipc::{Endpoint, Incoming};
use tokio;
use std::io::ErrorKind;

const DAEMON_PIPE_NAME: &str = "\\\\.\\pipe\\TestPipe\\ServiceControl\0";

pub struct IpcServer {
    incoming: Incoming,
}

impl IpcServer {
    pub fn new() -> Self {
        let endpoint = Endpoint::new(DAEMON_PIPE_NAME.to_string());

        let incoming = match endpoint.incoming(&tokio::reactor::Handle::default()) {
            Ok(inc) => inc,
            Err(e) => {
                println!("IpcServer: Endpoint creation failed {:?}", e);
                panic!("Check the pipe");
            }
        };

        IpcServer {
            incoming,
        }
    }
}

impl Future for IpcServer {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        loop {
            match self.incoming.poll() {
                Ok(Async::Ready(Some((_, _)))) => {
                    println!("IpcServer: new pipe incoming");
                    continue;
                }

                Ok(Async::Ready(None)) => {
                    panic!("impossible!");
                }

                Ok(Async::NotReady) => return Ok(Async::NotReady),

                Err(e) => {
                    match e.kind() {
                        ErrorKind::BrokenPipe | ErrorKind::Interrupted => continue,
                        _ => {
                            println!("IpcServer: endpoint connection error {:?}", e);
                            return Err(());
                        }
                    }
                } 
            }
        }
    }
}

fn main() {
    tokio::run(IpcServer::new());
}
hankbao commented 5 years ago

Ah, I figured it out.

The first time Incoming::poll being called is meant to register itself to the underlying IOCP writable event. To do that, it first needs to register the internal PollEvented2 as a mio Registration.

tokio-named-pipe relies on the mio registration taking place during NamedPipe::from_pipe. But that depends on the handle passed in being a bound tokio::reactor::Reactor handle. Otherwise, it fails silently and never gets notified again.

In the earlier version of tokio, there is a tokio::reactor::Handle::current() method to provide us a bound handle. So everything is fine at that time. But that method is deprecated in the current version. And the new tokio::reactor::Handle::default() method makes things worse by providing a lazy-bind handle which let the code compile but fail to register during NamedPipe::from_pipe at runtime. So the IpcServer hangs eventually after its first poll.

passchaos commented 5 years ago

@hankbao Hi,I also encountered this problem, is there any fix?

hankbao commented 5 years ago

@hankbao Hi,I also encountered this problem, is there any fix?

I just use tokio::reactor::Handle::current() and ignore the deprecation warning for now.