如何干净地打破tokio-core事件循环和期货:: Rust中的Stream

时间:2017-02-25 22:40:39

标签: rust

我正在涉足tokio-core并且可以弄清楚如何产生一个事件循环。但是我有两件事我不确定 - 如何优雅地退出事件循环以及如何退出在事件循环内运行的流。例如,考虑这段简单的代码,它将两个侦听器生成到事件循环中,并等待另一个线程指示退出条件:

extern crate tokio_core;
extern crate futures;

use tokio_core::reactor::Core;
use futures::sync::mpsc::unbounded;
use tokio_core::net::TcpListener;
use std::net::SocketAddr;
use std::str::FromStr;
use futures::{Stream, Future};
use std::thread;
use std::time::Duration;
use std::sync::mpsc::channel;

fn main() {
    let (get_tx, get_rx) = channel();

    let j = thread::spawn(move || {
        let mut core = Core::new().unwrap();
        let (tx, rx) = unbounded();
        get_tx.send(tx).unwrap(); // <<<<<<<<<<<<<<< (1)

        // Listener-0
        {
            let l = TcpListener::bind(&SocketAddr::from_str("127.0.0.1:44444").unwrap(),
                                      &core.handle())
                .unwrap();

            let fe = l.incoming()
                .for_each(|(_sock, peer)| {
                    println!("Accepted from {}", peer);
                    Ok(())
                })
                .map_err(|e| println!("----- {:?}", e));

            core.handle().spawn(fe);
        }

        // Listener1
        {
            let l = TcpListener::bind(&SocketAddr::from_str("127.0.0.1:55555").unwrap(),
                                      &core.handle())
                .unwrap();

            let fe = l.incoming()
                .for_each(|(_sock, peer)| {
                    println!("Accepted from {}", peer);
                    Ok(())
                })
                .map_err(|e| println!("----- {:?}", e));

            core.handle().spawn(fe);
        }

        let work = rx.for_each(|v| {
            if v {
                // (3) I want to shut down listener-0 above the release the resources
                Ok(())
            } else {
                Err(()) // <<<<<<<<<<<<<<< (2)

            }
        });

        let _ = core.run(work);
        println!("Exiting event loop thread");
    });

    let tx = get_rx.recv().unwrap();

    thread::sleep(Duration::from_secs(2));
    println!("Want to terminate listener-0"); // <<<<<< (3)
    tx.send(true).unwrap();

    thread::sleep(Duration::from_secs(2));
    println!("Want to exit event loop");
    tx.send(false).unwrap();

    j.join().unwrap();
}

所以说在主线程睡眠后我想要一个干净的退出事件循环线程。目前我发送一些东西到事件循环,使其退出,从而释放线程。

然而,(1)(2)都感到hacky - 我强迫错误作为退出条件。我的问题是:

1)我做得对吗?如果没有那么优雅地退出事件循环线程的正确方法是什么。

2)我不知道如何做(3) - 即在外部指示关闭listener-0并释放所有资源的条件。我如何实现这一目标?

2 个答案:

答案 0 :(得分:4)

事件循环(core)不再被转动(例如run())或被遗忘(drop()ed)。没有同步退出。 <{1}}在传递给它的core.run()完成时返回并停止转动循环。

Future通过产生Stream(在下面的代码中标有(3))来完成。 例如, TCP连接关闭,表示它完成的None完成,反之亦然。

Stream

答案 1 :(得分:0)

我通过oneshot频道实现了正常关机。

诀窍是既使用oneshot通道取消tcp侦听器,又在两个期货上使用select!。请注意,在下面的示例中,我使用的是tokio 0.2和Futures 0.3。


use futures::channel::oneshot;
use futures::{FutureExt, StreamExt};
use std::thread;
use tokio::net::TcpListener;

pub struct ServerHandle {
    // This is the thread in which the server will block
    thread: thread::JoinHandle<()>,

    // This switch can be used to trigger shutdown of the server.
    kill_switch: oneshot::Sender<()>,
}

impl ServerHandle {
    pub fn stop(self) {
        self.kill_switch.send(()).unwrap();
        self.thread.join().unwrap();
    }
}

pub fn run_server() -> ServerHandle {
    let (kill_switch, kill_switch_receiver) = oneshot::channel::<()>();

    let thread = thread::spawn(move || {
        info!("Server thread begun!!!");
        let mut runtime = tokio::runtime::Builder::new()
            .basic_scheduler()
            .enable_all()
            .thread_name("Tokio-server-thread")
            .build()
            .unwrap();

        runtime.block_on(async {
            server_prog(kill_switch_receiver).await.unwrap();
        });

        info!("Server finished!!!");
    });

    ServerHandle {
        thread,
        kill_switch,
    }
}

async fn server_prog(kill_switch_receiver: oneshot::Receiver<()>) -> std::io::Result<()> {
    let addr = "127.0.0.1:12345";
    let addr: std::net::SocketAddr = addr.parse().unwrap();
    let mut listener = TcpListener::bind(&addr).await?;
    let mut kill_switch_receiver = kill_switch_receiver.fuse();
    let mut incoming = listener.incoming().fuse();

    loop {
        futures::select! {
            x = kill_switch_receiver => {
                break;
            },
            optional_new_client = incoming.next() => {
                if let Some(new_client) = optional_new_client {
                    let peer_socket = new_client?;
                    info!("Client connected!");
                    let peer = process_client(peer_socket, db.clone());
                    peers.lock().unwrap().push(peer);
                } else {
                    info!("No more incoming connections.");
                    break;
                }
            },
        };
    }
    Ok(())
}

希望这对其他人(或将来对我有帮助))。

我的代码住在这里:

https://github.com/windelbouwman/lognplot/blob/master/lognplot/src/server/server.rs