summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2016-09-04 13:23:55 +0200
committerYorhel <git@yorhel.nl>2016-09-04 13:23:55 +0200
commit72813e1af8aaeef36a22592b83204dcbb48afdd9 (patch)
tree3c6dbf93891b4310bfdc240f0828917173d109c4
parent8578695be143045453cad83436f395cd09873435 (diff)
Add generic event loop + bind+listen handling code
-rw-r--r--Cargo.lock113
-rw-r--r--Cargo.toml2
-rw-r--r--src/config.rs4
-rw-r--r--src/eventloop.rs144
-rw-r--r--src/listener.rs49
-rw-r--r--src/main.rs15
6 files changed, 321 insertions, 6 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 728b178..2244df6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 9a6c76d..19f4424 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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();
}