Skip to content

Add ability to inject a tokio runtime as the backend for libevent. #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: rust
os: linux
dist: bionic
rust:
- 1.35.0 # msrv
- 1.54.0 # msrv
- stable

addons:
Expand All @@ -21,8 +21,8 @@ addons:

script:
# Try bundled build first
- cargo build --features "bundled,openssl_bundled"
- cargo run --manifest-path examples/hello/Cargo.toml --features "bundled,openssl_bundled" -- 5
- cargo build --features "tracing_subscriber,tokio_backend"
- cargo run --manifest-path examples/hello/Cargo.toml --features "tracing_subscriber,tokio_backend" -- 5
# Now pkg-config build
- sudo apt-get install -y libssl-dev libevent-dev
- cargo clean
Expand Down
47 changes: 33 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[package]
name = "libevent"
version = "0.1.0"
authors = ["Jon Magnuson <[email protected]>",
"Grant Elbert <[email protected]>"]
version = "0.2.0"
authors = [
"Jon Magnuson <[email protected]>",
"Grant Elbert <[email protected]>",
"Judge Maygarden <[email protected]>",
]
description = "Rust bindings to the libevent async I/O framework"
documentation = "https://docs.rs/libevent"
repository = "https://github.com/jmagnuson/libevent-rs"
Expand All @@ -12,22 +15,38 @@ edition = "2018"
categories = ["api-bindings", "asynchronous"]
keywords = ["libevent", "bindings", "async", "io"]

[lib]
crate-type = ["lib", "staticlib"]

[workspace]
members = ['examples/hello']
members = ["examples/hello"]

[features]
default = [ "pkgconfig", "openssl", "threading", "buildtime_bindgen" ]
static = [ "libevent-sys/static" ]
pkgconfig = [ "libevent-sys/pkgconfig" ]
bundled = [ "static", "libevent-sys/bundled" ]
buildtime_bindgen = [ "libevent-sys/buildtime_bindgen" ]
openssl = [ "libevent-sys/openssl" ]
openssl_bundled = [ "libevent-sys/openssl_bundled", "threading" ]
threading = [ "libevent-sys/threading" ]
default = ["pkgconfig", "openssl", "threading", "buildtime_bindgen"]
static = ["libevent-sys/static"]
pkgconfig = ["libevent-sys/pkgconfig"]
bundled = ["static", "libevent-sys/bundled"]
buildtime_bindgen = ["libevent-sys/buildtime_bindgen"]
openssl = ["libevent-sys/openssl"]
openssl_bundled = ["libevent-sys/openssl_bundled", "threading"]
threading = ["libevent-sys/threading"]
tokio_backend = ["bundled", "libevent-sys/tokio_backend", "tokio"]
tracing_subscriber = ["tracing-subscriber"]

# features for development
verbose_build = [ "libevent-sys/verbose_build" ]
verbose_build = ["libevent-sys/verbose_build"]

[dependencies]
bitflags = "1.2"
libevent-sys = { version = "0.2", path = "libevent-sys", default-features = false }
libevent-sys = { version = "0.3", path = "libevent-sys", default-features = false }
tokio = { version = "1", optional = true, features = [
"macros",
"net",
"rt",
"rt-multi-thread",
"signal",
"sync",
"time",
] }
tracing = { version = "0.1" }
tracing-subscriber = { version = "0.2", optional = true }
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
CARGO_FLAGS = --release --features tracing_subscriber,tokio_backend
CFLAGS = -Wall -Werror
SAMPLE_DIR = sample
OUT_DIR = target/release
LIBS = \
-L$(OUT_DIR) \
-llibevent
SAMPLES_SRC = \
$(SAMPLE_DIR)/bench.c \
$(SAMPLE_DIR)/dns-example.c \
$(SAMPLE_DIR)/event-read-fifo.c \
$(SAMPLE_DIR)/hello-world.c \
$(SAMPLE_DIR)/tokio-time-test.c
SAMPLES_BIN = $(patsubst %.c,%,$(SAMPLES_SRC))

all: $(SAMPLES_BIN)

%: %.c $(OUT_DIR)/liblibevent.a
$(CC) $(CFLAGS) -o $@ $(LIBS) $<

$(OUT_DIR)/liblibevent.a: FORCE
cargo build $(CARGO_FLAGS)

$(OUT_DIR)/bench-kqueue: $(SAMPLE_DIR)/bench.c $(OUT_DIR)/liblibevent.a
$(CC) $(CFLAGS) -o $(OUT_DIR)/bench-kqueue $(LIBS) $(SAMPLE_DIR)/bench.c

$(OUT_DIR)/bench-tokio: $(SAMPLE_DIR)/bench.c $(OUT_DIR)/liblibevent.a
$(CC) $(CFLAGS) -o $(OUT_DIR)/bench-tokio $(LIBS) $(SAMPLE_DIR)/bench.c -DUSE_TOKIO

plot-bench: $(OUT_DIR)/bench-kqueue $(OUT_DIR)/bench-tokio
$(OUT_DIR)/bench-kqueue > $(OUT_DIR)/kqueue.csv
$(OUT_DIR)/bench-tokio > $(OUT_DIR)/tokio.csv
./plot-bench.py

prof-bench: $(OUT_DIR)/bench-kqueue $(OUT_DIR)/bench-tokio
sudo flamegraph -o kqueue-flamegraph.svg target/release/bench-kqueue
sudo flamegraph -o tokio-flamegraph.svg target/release/bench-tokio

clippy: FORCE
cargo clippy $(CARGO_FLAGS)

run-hello-world: $(SAMPLE_DIR)/hello-world
RUST_BACKTRACE=1 RUST_LOG=debug ./$<

FORCE: ;

clean:
$(RM) $(SAMPLES_BIN)
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ base.run();
when `buildtime_bindgen` is not enabled, and it is only applicable in this
case.

## Tokio Backend for libevent

A optional tokio backend for handling libevent I/O and signal readiness is
optionally provided. It is not patched into libevent directly, but is
substituted at run time with a call to `libevent::inject_tokio`. The primary
motivation for this feature is to allow native tokio and libevent tasks to
co-exist with a single event loop on the same thread. This feature is
especially useful when gradually migrating a C/libevent project to Rust/tokio
when use of FFI between the C and Rust code prevents running the event loops
on separate threads.

## Samples

Versions of libevent samples modified to make use of an injected tokio
backend are located in the ./sample directory. There is a Makefile provided
for building these C programs linked to the Rust libevent crate.

## Minimum Supported Rust Version (MSRV)

This crate is guaranteed to compile on stable Rust 1.35.0 and up. It might compile
Expand Down
25 changes: 20 additions & 5 deletions examples/hello/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
[package]
name = "hello"
version = "0.1.0"
authors = ["Jon Magnuson <[email protected]>"]
authors = [
"Jon Magnuson <[email protected]>",
"Judge Maygarden <[email protected]>",
]
edition = "2018"
build="build.rs"
build = "build.rs"

[features]
default = [ "bundled", "openssl", "buildtime_bindgen" ]
bundled = ["libevent/bundled", "libevent-sys/bundled" ]
default = [
"bundled",
"openssl",
"buildtime_bindgen",
"tokio_backend",
"tracing_subscriber",
]
bundled = ["libevent/bundled", "libevent-sys/bundled"]
openssl = ["libevent/openssl"]
openssl_bundled = [ "libevent/openssl_bundled" ]
openssl_bundled = ["libevent/openssl_bundled"]
pkgconfig = ["libevent/pkgconfig"]
buildtime_bindgen = ["libevent/buildtime_bindgen"]
tokio_backend = ["libevent/tokio_backend", "tokio"]
tracing_subscriber = ["tracing-subscriber"]

[dependencies]
tokio = { version = "1", optional = true, features = ["rt"] }
tracing-subscriber = { version = "0.2", optional = true }

[dependencies.libevent]
path = "../../"
Expand Down
27 changes: 26 additions & 1 deletion examples/hello/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::time::Duration;

pub mod ffi;

use libevent::{EventCallbackCtx, EventCallbackFlags, EvutilSocket};
use libevent::{tokio_backend::Runtime, EventCallbackCtx, EventCallbackFlags, EvutilSocket};

extern "C" fn hello_callback(
_fd: EvutilSocket,
Expand All @@ -13,7 +13,29 @@ extern "C" fn hello_callback(
println!("callback: rust fn (interval: 2s)");
}

#[cfg(feature = "tokio_backend")]
fn inject_tokio(base: &Base) {
let runtime =
libevent::tokio_backend::TokioRuntime::new().expect("failed to build a tokio runtime");

{
let _guard = runtime.enter();

tokio::spawn(async {
loop {
tokio::time::sleep(Duration::from_secs(3)).await;
println!("'Hello, world' from a tokio task!");
}
});
}

base.inject_tokio(Box::new(runtime));
}

fn main() {
#[cfg(feature = "tracing_subscriber")]
tracing_subscriber::fmt::init();

let run_duration = std::env::args().nth(1).map(|val_s| {
Duration::from_secs(
val_s
Expand All @@ -24,6 +46,9 @@ fn main() {

let mut base = Base::new().unwrap_or_else(|e| panic!("{:?}", e));

#[cfg(feature = "tokio_backend")]
inject_tokio(&base);

let ret = unsafe { ffi::helloc_init(base.as_raw().as_ptr()) };
assert_eq!(ret, 0);

Expand Down
26 changes: 15 additions & 11 deletions libevent-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
[package]
name = "libevent-sys"
version = "0.2.4"
authors = ["Steven vanZyl <[email protected]>",
"Jon Magnuson <[email protected]>",
"Grant Elbert <[email protected]>"]
version = "0.3.0"
authors = [
"Steven vanZyl <[email protected]>",
"Jon Magnuson <[email protected]>",
"Grant Elbert <[email protected]>",
"Judge Maygarden <[email protected]>",
]
repository = "https://github.com/jmagnuson/libevent-rs"
edition = "2018"
readme = "README.md"
Expand All @@ -16,14 +19,15 @@ links = "event"
build = "build.rs"

[features]
default = [ "pkgconfig", "openssl", "threading", "buildtime_bindgen" ]
default = ["pkgconfig", "openssl", "threading", "buildtime_bindgen"]
static = []
pkgconfig = [ "pkg-config" ]
bundled = [ "static", "cmake" ]
buildtime_bindgen = [ "bindgen" ]
openssl = [ "openssl-sys" ]
openssl_bundled = [ "openssl-sys/vendored", "threading" ]
pkgconfig = ["pkg-config"]
bundled = ["static", "cmake"]
buildtime_bindgen = ["bindgen"]
openssl = ["openssl-sys"]
openssl_bundled = ["openssl-sys/vendored", "threading"]
threading = []
tokio_backend = ["buildtime_bindgen"]

# features for development
verbose_build = []
Expand All @@ -33,6 +37,6 @@ version = "0.9"
optional = true

[build-dependencies]
bindgen = { version = "0.53", optional = true }
bindgen = { version = "0.59", optional = true }
cmake = { version = "0.1", optional = true }
pkg-config = { version = "0.3", optional = true }
19 changes: 16 additions & 3 deletions libevent-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ fn run_pkg_config() -> Option<Vec<String>> {

let mut include_paths = HashSet::new();

if let Ok(mut lib) = pkg.probe("libevent_core").or_else(|_| pkg.probe("libevent")) {
if let Ok(mut lib) = pkg
.probe("libevent_core")
.or_else(|_| pkg.probe("libevent"))
{
include_paths.extend(lib.include_paths.drain(..));
} else {
return None;
Expand Down Expand Up @@ -166,7 +169,6 @@ fn find_libevent() -> Option<Vec<String>> {

#[cfg(feature = "buildtime_bindgen")]
fn generate_bindings(include_paths: Vec<String>, out_path: impl AsRef<Path>) {
println!("cargo:rerun-if-changed=libevent");
println!("cargo:rerun-if-changed=wrapper.h");

let target = env::var("TARGET").unwrap();
Expand All @@ -188,12 +190,23 @@ fn generate_bindings(include_paths: Vec<String>, out_path: impl AsRef<Path>) {
builder = builder.clang_arg(format!("-I{}", path));
}

// Some of the libevent internals need to be exposed to inject a tokio backend.
if cfg!(feature = "tokio_backend") {
builder = builder
.clang_arg("-Ilibevent")
.clang_arg(format!("-I{}/build/include", out_path.as_ref().display()))
.header("libevent/event-internal.h")
.header("libevent/evmap-internal.h")
.blocklist_item(".*voucher.*")
.blocklist_item("strto.*");
}

let bindings = builder
.header("wrapper.h")
// Enable for more readable bindings
// .rustfmt_bindings(true)
// Fixes a bug with a duplicated const
.blacklist_item("IPPORT_RESERVED")
.blocklist_item("IPPORT_RESERVED")
.generate()
.expect("Failed to generate bindings");

Expand Down
11 changes: 7 additions & 4 deletions libevent-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
//! - Functions are named the same as the C code and don't follow Rust naming schemes.
//! - Uses C strings. See `CStr` in the Rust standard library.

#![allow(non_upper_case_globals)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::unreadable_literal)]
#![allow(deref_nullptr)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::unreadable_literal)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(non_upper_case_globals)]
#![allow(unaligned_references)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Expand All @@ -22,6 +25,6 @@ mod tests {
#[test]
fn constant_access() {
assert_eq!(EVENT_LOG_MSG, 1);
assert_eq!(IPPORT_RESERVED, 1024);
assert_eq!(IPV6PORT_RESERVED, 1024);
}
}
26 changes: 26 additions & 0 deletions plot-bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3

import matplotlib.pyplot as pyplot
import pandas


def main():
df = pandas.concat([pandas.read_csv("target/release/kqueue.csv", names=["kqueue"]),
pandas.read_csv("target/release/tokio.csv", names=["tokio"])],
axis=1)

kqueue_mean = df["kqueue"].mean()
tokio_mean = df["tokio"].mean()

print(
f"kqueue mean: {round(kqueue_mean)}, tokio mean: {round(tokio_mean)}, ratio: {round(100 * kqueue_mean / tokio_mean)}%")

df.plot()
pyplot.title("Libevent Backend Benchmark (kqueue vs tokio)")
pyplot.xlabel("Test Run")
pyplot.ylabel("Time (µs)")
pyplot.show()


if __name__ == "__main__":
main()
Loading