diff options
author | Yorhel <git@yorhel.nl> | 2016-09-04 13:23:55 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2016-09-04 13:23:55 +0200 |
commit | 72813e1af8aaeef36a22592b83204dcbb48afdd9 (patch) | |
tree | 3c6dbf93891b4310bfdc240f0828917173d109c4 | |
parent | 8578695be143045453cad83436f395cd09873435 (diff) |
Add generic event loop + bind+listen handling code
-rw-r--r-- | Cargo.lock | 113 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/config.rs | 4 | ||||
-rw-r--r-- | src/eventloop.rs | 144 | ||||
-rw-r--r-- | src/listener.rs | 49 | ||||
-rw-r--r-- | src/main.rs | 15 |
6 files changed, 321 insertions, 6 deletions
@@ -5,7 +5,9 @@ dependencies = [ "env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -17,6 +19,16 @@ dependencies = [ ] [[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "env_logger" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -40,6 +52,11 @@ dependencies = [ ] [[package]] +name = "lazycell" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "libc" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -58,6 +75,58 @@ dependencies = [ ] [[package]] +name = "mio" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "nom" version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -80,6 +149,24 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "thread-id" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -102,6 +189,11 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -111,19 +203,40 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" +"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "82dcb9ceed3868a03b335657b85a159736c961900f7e7747d3b0b97b9ccb5ccb" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum mio 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2dadd39d4b47343e10513ac2a731c979517a4761224ecb6bbd243602300c9537" +"checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a" +"checksum net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "5edf9cb6be97212423aed9413dd4729d62b370b5e1c571750e882cebbbc1e3e2" +"checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2" "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" "checksum regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)" = "56b7ee9f764ecf412c6e2fff779bca4b22980517ae335a21aeaf4e32625a5df2" "checksum regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "31040aad7470ad9d8c46302dcffba337bb4289ca5da2e3cd6e37b64109a85199" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" @@ -8,3 +8,5 @@ log = "0.*" env_logger = "0.*" getopts = "0.2" nom = "1.2" +mio = "0.6" +slab = "0.3.0" diff --git a/src/config.rs b/src/config.rs index ea3a14a..c3882d8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -42,12 +42,12 @@ named!(parse_expr<&[u8],Expr>, alt!( #[derive(Debug,Clone,Default)] pub struct Interface { - addr: Vec<SocketAddr> + pub addr: Vec<SocketAddr> } #[derive(Debug,Clone,Default)] pub struct Config { - itf: Vec<Interface> + pub itf: Vec<Interface> } diff --git a/src/eventloop.rs b/src/eventloop.rs new file mode 100644 index 0000000..2cf8ecd --- /dev/null +++ b/src/eventloop.rs @@ -0,0 +1,144 @@ +/* This is a thin wrapper around mio for building state machines on a shared event loop. + * It's similar in spirit to Rotor, but with a bunch of simplifications and other differences. + * (Rotor is currently in too much flux and its ecosystem very incomplete/alpha) + * + * The biggest difference (I think) between Rotor and this is the way it deals with the problem of + * communicating actions back from the Machine to the EventLoop. Since the EventLoop is the owner + * of the Machine, it is not possible to give a mutable borrow of the EventLoop to a method in the + * Machine, as that what will result in a double mutable borrow. This problem is inherent in the + * architecture and Rust is correct to disallow the borrow; since it would be possible to mutate + * the Machine object through multiple aliases. + * + * Rotor solves this problem by having the method handlers return a "response", i.e. an action that + * it wants the EventLoop to perform. This implementation is more flexible but slightly more hacky: + * When calling a method in the Machine, the Machine object itself is temporarily removed from the + * EventLoop object, thus removing the alias and allowing both objects to be Mutably borrowed. + * Downside: This solution is more prone to logic errors that Rust can't catch, like removing or + * re-using the MToken while inside a Machine handler. + * + * TODO: Machines don't care on which thread they run, so as a scalability improvement it's + * possible to spawn a configurable number of threads on start-up, run a separate event loop on + * each, and assign each Machine to a different event loop. + */ +use mio; +use mio::{Poll,Events,Token}; +use slab::Slab; + + +// TODO: Upstream this to Slab crate? +fn slab_insert<T, I:Into<usize>+From<usize>>(s: &mut Slab<T,I>, t: T) -> I { + s.insert(t).or_else(|t| { + // Grow by some small fixed number. From what I can see the Slab implementation already + // does exponential growth internally. + s.reserve_exact(8); + s.insert(t) + }).unwrap_or_else(|_| { unreachable!() }) +} + + +pub trait Machine { + fn init(&mut self, &mut Context); + fn handle(&mut self, &mut Context, mio::Event); +} + + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MToken(pub usize); +impl From<usize> for MToken { fn from(val: usize) -> MToken { MToken(val) } } +impl From<MToken> for usize { fn from(val: MToken) -> usize { val.0 } } + + +pub struct EventLoop { + poll: Poll, + tokens: Slab<MToken, Token>, + // A machine entry is set to None during a method call on the Machine object. + machines: Slab<Option<Box<Machine>>, MToken> +} + +pub struct Context<'a> { + parent: &'a mut EventLoop, + machine: MToken, + removed: bool +} + + +impl<'a> Context<'a> { + pub fn assign(&mut self) -> Token { + slab_insert(&mut self.parent.tokens, self.machine) + } + + #[allow(dead_code)] + pub fn unassign(&mut self, t: Token) { + assert_eq!(self.parent.tokens[t], self.machine); + self.parent.tokens.remove(t); + } + + // This method is opinionated in always providing a PollOpt::level(). It's the only option that + // makes sense. :) + pub fn register<E: ?Sized>(&mut self, io: &E, token: Token, interest: mio::Ready) where E: mio::Evented { + self.parent.poll.register(io, token, interest, mio::PollOpt::level()).unwrap(); + } + + #[allow(dead_code)] + pub fn reregister<E: ?Sized>(&mut self, io: &E, token: Token, interest: mio::Ready) where E: mio::Evented { + self.parent.poll.reregister(io, token, interest, mio::PollOpt::level()).unwrap(); + } + + #[allow(dead_code)] + pub fn deregister<E: ?Sized>(&mut self, io: &E) where E: mio::Evented { + self.parent.poll.deregister(io).unwrap(); + } + + #[allow(dead_code)] + pub fn remove(&mut self) { + self.removed = true; + } + + #[allow(dead_code)] + pub fn spawn(&mut self, machine: Box<Machine>) { + self.parent.spawn(machine); + } +} + + +impl EventLoop { + pub fn new() -> EventLoop { + EventLoop { + poll: Poll::new().unwrap(), + tokens: Slab::with_capacity(16), + machines: Slab::with_capacity(32), + } + } + + pub fn spawn(&mut self, mut machine: Box<Machine>) { + let mtoken = slab_insert(&mut self.machines, None); + { + let mut ctx = Context{ parent: self, machine: mtoken, removed: false }; + machine.init(&mut ctx); + assert!(!ctx.removed); + } + self.machines[mtoken] = Some(machine); + } + + pub fn run(&mut self) { + let mut events = Events::with_capacity(64); + debug!("Entering event loop"); + loop { + self.poll.poll(&mut events, None).unwrap(); + trace!("Poll returned with {} events", events.len()); + for event in events.iter() { + let mtoken = self.tokens[event.token()]; + let mut machine = self.machines.entry(mtoken).unwrap().replace(None).unwrap(); + + let removed = { + let mut ctx = Context{ parent: self, machine: mtoken, removed: false }; + machine.handle(&mut ctx, event); + ctx.removed + }; + + if !removed { + self.machines[mtoken] = Some(machine); + } + } + } + } +} diff --git a/src/listener.rs b/src/listener.rs new file mode 100644 index 0000000..e6683f3 --- /dev/null +++ b/src/listener.rs @@ -0,0 +1,49 @@ +use mio::{Ready,Token,Event}; +use mio::tcp::TcpListener; +use std::net::SocketAddr; +use std::io::Result; +use config::Config; +use eventloop::{Machine,Context,EventLoop}; + + +struct Listener { + sock: TcpListener, + io: Token +} + + +impl Listener { + fn new(addr: &SocketAddr) -> Result<Listener> { + trace!("Binding {}", addr); + let sock = try!(TcpListener::bind(addr)); + Ok(Listener { + sock: sock, + io: Token(0) + }) + } +} + + +impl Machine for Listener { + fn init(&mut self, ctx: &mut Context) { + self.io = ctx.assign(); + ctx.register(&self.sock, self.io, Ready::readable()); + info!("Listening on {}", self.sock.local_addr().unwrap()); + } + + fn handle(&mut self, _: &mut Context, _: Event) { + let(_, addr) = self.sock.accept().unwrap(); // TODO: ERROR HANDLING! + debug!("New connection from {}", addr); + } +} + + +pub fn setup(ev: &mut EventLoop, cfg: &Config) -> Result<()> { + for itf in &cfg.itf { + for addr in &itf.addr { + let lst = try!(Listener::new(&addr)); + ev.spawn(Box::new(lst)); + } + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 504b7f7..9a90486 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,13 +9,16 @@ #[macro_use] extern crate log; extern crate env_logger; extern crate getopts; +extern crate mio; +extern crate slab; +mod config; +mod eventloop; +mod listener; use std::process::exit; use std::io::prelude::*; -mod config; - struct CliOpts { config: String, @@ -63,7 +66,11 @@ impl CliOpts { fn main() { let opts = CliOpts::new().parse(); - let conf = config::Config::parse(&opts.config); - println!("{:?}", conf); + let conf = config::Config::parse(&opts.config).unwrap(); env_logger::init().unwrap(); + trace!("Configuration read from {}: {:?}", opts.config, conf); + + let mut main = eventloop::EventLoop::new(); + listener::setup(&mut main, &conf).unwrap(); + main.run(); } |