Code Monkey home page Code Monkey logo

rotor's Introduction

Rotor

Status: Alpha
Examples:TCP echo server, TCP client (telnet)
Ecosystem:libraries and apps using rotor
Documentation:http://tailhook.github.com/rotor/

The mio-based framework for rust for doing I/O in simple and composable way.

The rotor core (this crate) basically consists of:

  • An event loop handler (in terms of mio) which turns mio event into event to specific state machine
  • A Future type which allows communication between state machines in safe and efficient way
  • A simple way to combine multiple libraries (e.g. multiple protocol handlers) into single mio event loop

At the end of the day, rotor is the minimalistic core for making composable libraries on top. It's less than 0.5KLoC.

You are expected to use some higher level abstraction most of the time. For example, you should use stream abstraction (yet to be implemented) for making TCP protocol parser.

Resources

Both are rather historical at the moment

Benchmarks

These benchmarks are based on old version of this example. Hopefully we will get updated benchmarks soon.

Just few micro-benchmarks to show that framework has a decent peformance.

The performance on the few years old laptop (i7-3517U CPU @ 1.90GHz):

> wrk -t2 -c 400 http://localhost:8888/
Running 10s test @ http://localhost:8888/
  2 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    11.19ms   18.03ms 627.44ms   99.54%
    Req/Sec    19.66k     1.76k   21.93k    81.00%
  391170 requests in 10.01s, 32.83MB read
Requests/sec:  39071.42
Transfer/sec:      3.28MB

Performance on newer desktop class CPU (i7-4790K CPU @ 4.00GHz):

> ./wrk -t 2 -c 400 http://127.0.0.1:8888
Running 10s test @ http://127.0.0.1:8888
  2 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.24ms    1.56ms 126.94ms   99.91%
    Req/Sec    91.35k     2.27k   93.76k    98.00%
  1818133 requests in 10.00s, 152.58MB read
Requests/sec: 181781.96
Transfer/sec:     15.26MB

Note: both benchmarks are run on single threaded server.

The benchmarks are too early (not a full implementation of HTTP), so no comparison bencmarks listed here.

rotor's People

Contributors

donaldpipowitch avatar ebarnard avatar erikjohnston avatar frewsxcv avatar partim avatar pyfisch avatar seanmonstar avatar tailhook avatar timnn avatar tshepang avatar vks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rotor's Issues

Response structure doesn't permit correct use of edge polling

Each state machine is only allowed to respond to a read/write hint with a single Response, this is in some ways fundamentally incompatible with the way edge polling is meant to work.

For instance, take the case of a TcpAcceptor registered with edge polling and listening for readable events. Assume our state machine has two variants, Acceptor(TcpAcceptor) and Connection(TcpConnection). When we receive a read hint on this acceptor, we must accept in a loop until we receive a WouldBlock error. By the time we get WouldBlock we may have accepted many new connections; however, we are only allowed to spawn one new state machine from each hint.

Add Machine to Running Loop

Is it possible to add a new state machine to a loop that's already running? I'm looking through rotor_cantal and see it maintains a set of listeners, but those are added before the loop is run, right?

I'm building a rotor_http impl for the Elasticsearch API, where we can have multiple nodes at different addresses that I want to maintain a persistent connection to. The issue is that if the nodes change; new nodes appear, nodes curl up and die etc, I need to be able to add/remove connections accordingly.

To add new machines do I need to stop the running loop, recreate the machines and then rerun it? Or can I somehow register a new machine with the scope from another machine?

EDIT: Actually thinking now it might be better to have a known number of connection machines, and connect to any address in the cluster, provided there isn't already a listener on that address. Then if stuff changes, they can either reconnect elsewhere, or just go to sleep. Does that sound like a good idea?

Shutdown on last machine closing?

This may just be me trying to find the easy way out for closing down a complex setup of multiple machines, but would it make sense to let the loop instance shut itself down when the last machine has closed? Naively, I would think that in this case the loop is hanging with no way out, anyway.

Support for DPDK

It would be awesome if there was a polling loop implementation for rotor's event monitoring that used DPDK and userland TCP.

http://www.seastar-project.org/ manages to show some amazing numbers (lies, and more lies I'm sure, or maybe not!) that make me wonder how nice it would be to have that same functionality but without the nightmarish build they have going on.

Can't import rotor::Time

Importing rotor::Time fails with:

src/main.rs:3:5: 3:16 error: unresolved import `rotor::Time`. There is no `Time` in `rotor` [E0432]
src/main.rs:3 use rotor::Time;

I am using needing to use it because I am attempting to run the rotor-http example. I have no idea why this isn't working. I have rotor 0.5.1 and am using rustc 1.8.0-nightly (57c357d89 2016-02-16).

Any ideas?

Deferring work to another protocol

Sorry in advance if I'm asking a dumb question.

How it will look like if I need to query database to serve HTTP request by rotor-based HTTP server (supposing we have rotor-based DB driver)? I've studies all the examples and most of API docs, but still can't figure out what is the right way to defer some work to another protocol and be notified asynchronously when it is done (possibly with some response payload).

Expose mio::Handler.tick

Besides for some debugging purposes, (such as seeing how many ticks occurring before my other code wakes the socket up again), this would also be useful for the Machine to be able to notify a user if they are doing blocking work, or otherwise too much work, in the ready event. One could observe the time difference between ticks, and above a threshold, act accordingly.

Default Machine::spawned implementation

Each time I've implemented Machine, I've used the exact same function body for the spawned method. Perhaps a default implementation could be provided?

fn spawned(self, scope: &mut Scope<Self::Context>) -> Response<Self, Self::Seed> {
    Response::ok(self)
}

Slightly related, but perhaps more controversial: Maybe Machine could provide default stubs for other less important methods also, like mio::Handler does. Such as timeout and wakeup defaulting to just return ok(self).

Communication between machines and the world outside the MIO event loop

Hello,

After reading the 3rd post about rotor, I had a quick look to check if it could fit my needs (rust implementation of nanomsg's scalability protocols) and it seems to be quite close. The only thing that I feel is missing is a way for a client outside the event loop to talk with the machines.

Modifying the tcp_echo_server as follow, one can manage to send a single message to the machine, but only once.

    let mut port : Option<Port<u8>> = None;
    let lst = TcpListener::bind(&"127.0.0.1:3000".parse().unwrap()).unwrap();
    let ok = handler.add_machine_with(&mut event_loop, |scope| {
        let (p, _) = scope.create_future();
        port = Some(p);
        Ok(Echo::new(lst, scope))
    }).is_ok();
    assert!(ok);
    event_loop.run(&mut handler).unwrap();

    if let Some(p) = port.take() {
        p.set(42);
    }

The way the event loop channel is hidden behind a oneshot port/future pair makes it impossible (as far as I can see) to send an arbitrary number of messages to the machine. Is there a way to send several messages that I didn't find out ?

Regards,
Benoît

Rust Nightly Error: Found value used as a type

When compiling with rust nightly (1.8) I get the following errors:

rotor-0.4.0/src/compose.rs:34:22: 34:31 error: found value `compose::Compose2::A` used as a type [E0248]
rotor-0.4.0/src/compose.rs:34             As(s) => A::create(s, scope).map(A).map_err(|mut e| {
                                                   ^~~~~~~~~
rotor-0.4.0/src/compose.rs:34:22: 34:31 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:36:44: 36:51 error: found value `compose::Compose2::A` used as a type [E0248]
rotor-0.4.0/src/compose.rs:36                     let mut s: NoSlabSpace<A::Seed> = unsafe { zeroed() };
                                                                         ^~~~~~~
rotor-0.4.0/src/compose.rs:36:44: 36:51 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:35:20: 35:50 error: the type of this value must be known in this context
rotor-0.4.0/src/compose.rs:35                 if e.is::<NoSlabSpace<A::Seed>>() {
                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rotor-0.4.0/src/compose.rs:35:39: 35:46 error: found value `compose::Compose2::A` used as a type [E0248]
rotor-0.4.0/src/compose.rs:35                 if e.is::<NoSlabSpace<A::Seed>>() {
                                                                    ^~~~~~~
rotor-0.4.0/src/compose.rs:35:39: 35:46 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:38:54: 38:61 error: found value `compose::Compose2::A` used as a type [E0248]
rotor-0.4.0/src/compose.rs:38                         e.downcast_mut::<NoSlabSpace<A::Seed>>().unwrap());
                                                                                   ^~~~~~~
rotor-0.4.0/src/compose.rs:38:54: 38:61 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:40:50: 40:57 error: found value `compose::Compose2::B` used as a type [E0248]
rotor-0.4.0/src/compose.rs:40                     Box::new(NoSlabSpace(As::<_, B::Seed>(s.0))) as Box<Error>
                                                                               ^~~~~~~
rotor-0.4.0/src/compose.rs:40:50: 40:57 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:45:22: 45:31 error: found value `compose::Compose2::B` used as a type [E0248]
rotor-0.4.0/src/compose.rs:45             Bs(s) => B::create(s, scope).map(B).map_err(|mut e| {
                                                   ^~~~~~~~~
rotor-0.4.0/src/compose.rs:45:22: 45:31 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:47:44: 47:51 error: found value `compose::Compose2::B` used as a type [E0248]
rotor-0.4.0/src/compose.rs:47                     let mut s: NoSlabSpace<B::Seed> = unsafe { zeroed() };
                                                                         ^~~~~~~
rotor-0.4.0/src/compose.rs:47:44: 47:51 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:46:20: 46:50 error: the type of this value must be known in this context
rotor-0.4.0/src/compose.rs:46                 if e.is::<NoSlabSpace<B::Seed>>() {
                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rotor-0.4.0/src/compose.rs:46:39: 46:46 error: found value `compose::Compose2::B` used as a type [E0248]
rotor-0.4.0/src/compose.rs:46                 if e.is::<NoSlabSpace<B::Seed>>() {
                                                                    ^~~~~~~
rotor-0.4.0/src/compose.rs:46:39: 46:46 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:49:54: 49:61 error: found value `compose::Compose2::B` used as a type [E0248]
rotor-0.4.0/src/compose.rs:49                         e.downcast_mut::<NoSlabSpace<B::Seed>>().unwrap());
                                                                                   ^~~~~~~
rotor-0.4.0/src/compose.rs:49:54: 49:61 help: run `rustc --explain E0248` to see a detailed explanation
rotor-0.4.0/src/compose.rs:51:47: 51:54 error: found value `compose::Compose2::A` used as a type [E0248]
rotor-0.4.0/src/compose.rs:51                     Box::new(NoSlabSpace(Bs::<A::Seed, _>(s.0))) as Box<Error>
                                                                            ^~~~~~~
rotor-0.4.0/src/compose.rs:51:47: 51:54 help: run `rustc --explain

How to wakeup a protocol?

Reading the code it appears that to wake a protocol/event machine/thing up I need to know its associated mio::Token, however that doesn't seem to be exposed anywhere. Is this possible to do currently?

Want to be on Rusty Radio?

Hey!

First, super sorry for making this an issue, I couldn't find a public email for you (@tailhook) so this was the next best option.

I wanted to reach out to you about being a part of an episode of Rusty Radio! My good friend @posix4e is the host and we've gotten requests to hear more about you and about Rotor! We've both enjoyed the blog posts about how it works.

If you're interested please reach out to me ([email protected]) and we'll get @posix4e looped into the conversation about when works best for you. The entire process should take maybe 45 minutes including getting the audio working.

All the best! Feel free to close this issue at any time.

Comparison with tokio-core

tokio-core is somewhat similar in purpose to this library. However, this library uses explicit state machines, whereas tokio-core uses futures that create state machines.

What are the advantages and disadvantages of each library?

UdpSocket is never writable

I tried to use rotor with this minimal code (maybe I am doing something wrong). But after starting the loop the socket never becomes ready:

use std::str;
use mio::udp::UdpSocket;
use rotor::{EventSet, PollOpt, Loop, Config, Void};
use rotor::{Machine, Response, Scope, EarlyScope};

struct Context;

enum Client {
    Foo
}

impl Client {
    fn new(socket: UdpSocket, scope: &mut EarlyScope) -> Response<Client, Void> {
        scope.register(&socket, EventSet::all(), PollOpt::level()).unwrap();
        Response::ok(Client::Foo)
    }
}

impl Machine for Client {
    type Context = Context;
    type Seed = Void;

    fn create(_: Void, scope: &mut Scope<Context>) -> Response<Self, Void>
    {
        println!("create");
        Response::ok(Client::Foo)
    }

    fn ready(self, events: EventSet, _scope: &mut Scope<Context>) -> Response<Self, Void>
    {
        println!("ready: {:?}", events);
        Response::ok(Client::Foo)
    }

    fn spawned(self, _scope: &mut Scope<Context>) -> Response<Self, Void>
    {
        println!("spawned");
        Response::ok(Client::Foo)
    }

    fn timeout(self, _scope: &mut Scope<Context>) -> Response<Self, Void>
    {
        println!("timeout");
        Response::ok(Client::Foo)
    }

    fn wakeup(self, _scope: &mut Scope<Context>) -> Response<Self, Void>
    {
        println!("wakeup");
        Response::ok(Client::Foo)
    }
}

pub fn get() {
    println!("starting ...");
    let mut loop_creator = Loop::new(&Config::new()).unwrap();
    let any = str::FromStr::from_str("0.0.0.0:0").unwrap();
    let socket = UdpSocket::bound(&any).unwrap();
    loop_creator.add_machine_with(|scope| {
        Client::new(socket, scope)
    }).unwrap();
    loop_creator.run(Context).unwrap();
}

This example with "raw" mio works as expected:

extern crate mio;

use mio::*;
use mio::udp::*;
use std::str;

const LISTENER: Token = Token(0);

pub struct UdpHandler {
    tx: UdpSocket,
}

impl UdpHandler {
    fn new(tx: UdpSocket) -> UdpHandler {
        UdpHandler {
            tx: tx,
        }
    }
}

impl Handler for UdpHandler {
    type Timeout = usize;
    type Message = ();

    fn ready(&mut self, event_loop: &mut EventLoop<UdpHandler>, token: Token, events: EventSet) {
        println!("{:?}", events);
    }
}

fn main() {
    let mut event_loop = EventLoop::new().unwrap();
    let any = str::FromStr::from_str("0.0.0.0:0").unwrap();
    let tx = UdpSocket::bound(&any).unwrap();
    event_loop.register(&tx, LISTENER, EventSet::all(), PollOpt::edge()).unwrap();
    event_loop.run(&mut UdpHandler::new(tx)).unwrap();
}

A way to identify Timeouts

A way is needed to identify which timeout is ocurring in Machine::timeout. The only way to attempt to match timeouts currently is by checking the clock and comparing with timeouts that have been set. However, this isn't precise, and is possibly an unnecessary check of the clock.

Scope::timeout_ms could either return a TimeoutId or similar, that is passed to Machine::timeout, so they can be matched, or probably the better option, is to allow timeouts to be generic over an associated type. scope.timeout_ms(Next::Read, 5_000); scope.timeout_ms(Next::Write, 1_000), for instance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.