|
| 1 | +# libxev |
| 2 | + |
| 3 | +libxev is a cross-platform event loop. libxev provides a unified event loop |
| 4 | +abstraction for non-blocking IO, timers, signals, events, and more that |
| 5 | +works on macOS, Windows, Linux, and WebAssembly (browser and WASI). It is |
| 6 | +written in [Zig](https://ziglang.org/) but exports a C-compatible API (which |
| 7 | +further makes it compatible with any language out there that can communicate |
| 8 | +with C APIs). |
| 9 | + |
| 10 | +**Project Status: 🐲 Unstable, alpha-ish quality.** The feature list is quite |
| 11 | +good across multiple platforms, but there are plenty of missing features. |
| 12 | +The project hasn't been well tested in real-world environments and there |
| 13 | +are lots of low-hanging fruit for performance optimization. I'm not promising |
| 14 | +any API compatibility at this point, either. If you want a production ready, |
| 15 | +high quality, generalized event loop implementation check out |
| 16 | +[libuv](https://libuv.org/), libev, etc. |
| 17 | + |
| 18 | +**Why a new event loop library?** A few reasons. One, I think Zig lacks |
| 19 | +a generalized event loop comparable to libuv in features ("generalized" |
| 20 | +being a key word here). Two, I wanted to build a library like this around |
| 21 | +the design patterns of [io_uring](https://unixism.net/loti/what_is_io_uring.html), |
| 22 | +even mimicking its style on top of other OS primitives ( |
| 23 | +[credit to this awesome blog post](https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/)). |
| 24 | +Three, I wanted an event loop library that could build to WebAssembly |
| 25 | +(both WASI and freestanding) and that didn't really fit well |
| 26 | +into the goals of API style of existing libraries without bringing in |
| 27 | +something super heavy like Emscripten. The motivation for this library |
| 28 | +primarily though is scratching my own itch! |
| 29 | + |
| 30 | +## Features |
| 31 | + |
| 32 | +**Cross-platform.** Linux (`io_uring` and `epoll`), macOS (`kqueue`), |
| 33 | +WebAssembly + WASI (`poll_oneoff`, threaded and non-threaded runtimes). |
| 34 | +(Windows support is planned and coming soon) |
| 35 | + |
| 36 | +**[Proactor API](https://en.wikipedia.org/wiki/Proactor_pattern).** Work |
| 37 | +is submitted to the libxev event loop and the caller is notified of |
| 38 | +work _completion_, as opposed to work _readiness_. |
| 39 | + |
| 40 | +**Zero runtime allocations.** This helps make runtime performance more |
| 41 | +predictable and makes libxev well suited for embedded environments. |
| 42 | + |
| 43 | +**Timers, TCP, UDP, Files, Processes.** High-level platform-agnostic APIs for |
| 44 | +interacting with timers, TCP/UDP sockets, files, processes, and more. For |
| 45 | +platforms that don't support async IO, the file operations are automatically |
| 46 | +scheduled to a thread pool. |
| 47 | + |
| 48 | +**Generic Thread Pool (Optional).** You can create a generic thread pool, |
| 49 | +configure its resource utilization, and use this to perform custom background |
| 50 | +tasks. The thread pool is used by some backends to do non-blocking tasks that |
| 51 | +don't have reliable non-blocking APIs (such as local file operations with |
| 52 | +`kqueue`). The thread pool can be shared across multiple threads and event |
| 53 | +loops to optimize resource utilization. |
| 54 | + |
| 55 | +**Low-level and High-Level API.** The high-level API is platform-agnostic |
| 56 | +but has some opinionated behavior and limited flexibility. The high-level |
| 57 | +API is recommended but the low-level API is always an available escape hatch. |
| 58 | +The low-level API is platform-specific and provides a mechanism for libxev |
| 59 | +users to squeeze out maximum performance. The low-level API is _just enough |
| 60 | +abstraction_ above the OS interface to make it easier to use without |
| 61 | +sacrificing noticable performance. |
| 62 | + |
| 63 | +**Tree Shaking (Zig).** This is a feature of Zig, but substantially benefits |
| 64 | +libraries such as libxev. Zig will only include function calls and features |
| 65 | +that you actually use. If you don't use a particular kind of high-level |
| 66 | +watcher (such as UDP sockets), then the functionality related to that |
| 67 | +abstraction is not compiled into your final binary at all. This lets libxev |
| 68 | +support optional "nice-to-have" functionality that may be considered |
| 69 | +"bloat" in some cases, but the end user doesn't have to pay for it. |
| 70 | + |
| 71 | +**Dependency-free.** libxev has no dependencies other than the built-in |
| 72 | +OS APIs at runtime. The C library depends on libc. This makes it very |
| 73 | +easy to cross-compile. |
| 74 | + |
| 75 | +### Roadmap |
| 76 | + |
| 77 | +There are plenty of missing features that I still want to add: |
| 78 | + |
| 79 | + * Pipe high-level API |
| 80 | + * Signal handlers |
| 81 | + * Filesystem events |
| 82 | + * Windows backend |
| 83 | + * Freestanding WebAssembly support via an external event loop (i.e. the browser) |
| 84 | + |
| 85 | +And more... |
| 86 | + |
| 87 | +### Performance |
| 88 | + |
| 89 | +There is plenty of room for performance improvements, and I want to be |
| 90 | +fully clear that I haven't done a lot of optimization work. Still, |
| 91 | +performance is looking good. I've tried to port many of |
| 92 | +[libuv benchmarks](https://github.com/libuv/libuv) to use the libxev |
| 93 | +API. |
| 94 | + |
| 95 | +I won't post specific benchmark results until I have a better |
| 96 | +environment to run them in. As a _very broad generalization_, |
| 97 | +you shouldn't notice a slowdown using libxev compared to other |
| 98 | +major event loops. This may differ on a feature-by-feature basis, and |
| 99 | +if you can show really poor performance in an issue I'm interested |
| 100 | +in resolving it! |
| 101 | + |
| 102 | +## Example |
| 103 | + |
| 104 | +The example below shows an identical program written in Zig and in C |
| 105 | +that uses libxev to run a single 5s timer. This is almost silly how |
| 106 | +simple it is but is meant to just convey the overall feel of the library |
| 107 | +rather than a practical use case. |
| 108 | + |
| 109 | +<table> |
| 110 | +<tr> |
| 111 | +<td> Zig </td> <td> C </td> |
| 112 | +</tr> |
| 113 | +<tr> |
| 114 | +<td> |
| 115 | + |
| 116 | +```zig |
| 117 | +const xev = @import("xev"); |
| 118 | +
|
| 119 | +pub fn main() !void { |
| 120 | + var loop = try xev.Loop.init(.{}); |
| 121 | + defer loop.deinit(); |
| 122 | +
|
| 123 | + const w = try xev.Timer.init(); |
| 124 | + defer w.deinit(); |
| 125 | +
|
| 126 | + // 5s timer |
| 127 | + var c: xev.Completion = undefined; |
| 128 | + w.run(&loop, &c, 5000, void, null, &timerCallback); |
| 129 | +
|
| 130 | + try loop.run(.until_done); |
| 131 | +} |
| 132 | +
|
| 133 | +fn timerCallback( |
| 134 | + userdata: ?*void, |
| 135 | + loop: *xev.Loop, |
| 136 | + c: *xev.Completion, |
| 137 | + result: xev.Timer.RunError!void, |
| 138 | +) xev.CallbackAction { |
| 139 | + _ = userdata; |
| 140 | + _ = loop; |
| 141 | + _ = c; |
| 142 | + _ = result catch unreachable; |
| 143 | + return .disarm; |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +</td> |
| 148 | +<td> |
| 149 | + |
| 150 | +```zig |
| 151 | +#include <stddef.h> |
| 152 | +#include <stdio.h> |
| 153 | +#include <xev.h> |
| 154 | +
|
| 155 | +xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) { |
| 156 | + return XEV_DISARM; |
| 157 | +} |
| 158 | +
|
| 159 | +int main(void) { |
| 160 | + xev_loop loop; |
| 161 | + if (xev_loop_init(&loop) != 0) { |
| 162 | + printf("xev_loop_init failure\n"); |
| 163 | + return 1; |
| 164 | + } |
| 165 | +
|
| 166 | + xev_watcher w; |
| 167 | + if (xev_timer_init(&w) != 0) { |
| 168 | + printf("xev_timer_init failure\n"); |
| 169 | + return 1; |
| 170 | + } |
| 171 | +
|
| 172 | + xev_completion c; |
| 173 | + xev_timer_run(&w, &loop, &c, 5000, NULL, &timerCallback); |
| 174 | +
|
| 175 | + xev_loop_run(&loop, XEV_RUN_UNTIL_DONE); |
| 176 | +
|
| 177 | + xev_timer_deinit(&w); |
| 178 | + xev_loop_deinit(&loop); |
| 179 | + return 0; |
| 180 | +} |
| 181 | +``` |
| 182 | +</td> |
| 183 | +</tr> |
| 184 | +</table> |
| 185 | + |
| 186 | +## Installation (Zig) |
| 187 | + |
| 188 | +**These instructions are for Zig downstream users only.** If you are |
| 189 | +using the C API to libxev, see the "Build" section. |
| 190 | + |
| 191 | +This package works with the Zig package manager introduced in Zig 0.11. |
| 192 | +Create a `build.zig.zon` file like this: |
| 193 | + |
| 194 | +```zig |
| 195 | +.{ |
| 196 | + .name = "my-project", |
| 197 | + .version = "0.0.0", |
| 198 | + .dependencies = .{ |
| 199 | + .libxev = .{ |
| 200 | + .url = "https://github.com/mitchellh/libxev/archive/<git-ref-here>.tar.gz", |
| 201 | + .hash = "12208070233b17de6be05e32af096a6760682b48598323234824def41789e993432c", |
| 202 | + }, |
| 203 | + }, |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +And in your `build.zig`: |
| 208 | + |
| 209 | +```zig |
| 210 | +const xev = b.dependency("libxev", .{ .target = target, .optimize = optimize }); |
| 211 | +exe.addModule("xev", xev.module("xev")); |
| 212 | +``` |
| 213 | + |
| 214 | +## Documentation |
| 215 | + |
| 216 | +🚧 Documentation is a work-in-progress. 🚧 |
| 217 | + |
| 218 | +Currently, documentation is available in three forms: **man pages**, |
| 219 | +**examples**, and **code comments.** In the future, I plan on writing detailed |
| 220 | +guides and API documentation in website form, but that isn't currently |
| 221 | +available. |
| 222 | + |
| 223 | +### Man Pages |
| 224 | + |
| 225 | +The man pages are relatively detailed! `xev(7)` will |
| 226 | +give you a good overview of the entire library. `xev-zig(7)` and |
| 227 | +`xev-c(7)` will provide overviews of the Zig and C API, respectively. |
| 228 | +From there, API-specifc man pages such as `xev_loop_init(3)` are |
| 229 | +available. This is the best documentation currently. |
| 230 | + |
| 231 | +There are multiple ways to browse the man pages. The most immediately friendly |
| 232 | +is to just browse the raw man page sources in the `docs/` directory in |
| 233 | +your web browser. The man page source is a _markdown-like_ syntax so it |
| 234 | +renders _okay_ in your browser via GitHub. |
| 235 | + |
| 236 | +Another approach is to run `zig build -Dman-pages` and the man pages |
| 237 | +will be available in `zig-out`. This requires |
| 238 | +[scdoc](https://git.sr.ht/~sircmpwn/scdoc) |
| 239 | +to be installed (this is available in most package managers). |
| 240 | +Once you've built the man pages, you can render them by path: |
| 241 | + |
| 242 | +``` |
| 243 | +$ man zig-out/share/man/man7/xev.7 |
| 244 | +``` |
| 245 | + |
| 246 | +And the final approach is to install libxev via your favorite package |
| 247 | +manager (if and when available), which should hopefully put your man pages |
| 248 | +into your man path, so you can just do `man 7 xev`. |
| 249 | + |
| 250 | +### Examples |
| 251 | + |
| 252 | +There are examples available in the `examples/` folder. The examples are |
| 253 | +available in both C and Zig, and you can tell which one is which using |
| 254 | +the file extension. |
| 255 | + |
| 256 | +To build an example, use the following: |
| 257 | + |
| 258 | +``` |
| 259 | +$ zig build -Dexample-name=_basic.zig |
| 260 | +... |
| 261 | +$ zig-out/bin/example-basic |
| 262 | +... |
| 263 | +``` |
| 264 | + |
| 265 | +The `-Dexample-name` value should be the filename including the extension. |
| 266 | + |
| 267 | +### Code Comments |
| 268 | + |
| 269 | +The Zig code is well commented. If you're comfortable reading code comments |
| 270 | +you can find a lot of insight within them. The source is in the `src/` |
| 271 | +directory. |
| 272 | + |
| 273 | +# Build |
| 274 | + |
| 275 | +Build requires the installation of the latest [Zig nightly](https://ziglang.org/download/). |
| 276 | +**libxev has no other build dependencies.** |
| 277 | + |
| 278 | +Once installed, `zig build install` on its own will build the full library and output |
| 279 | +a [FHS-compatible](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard) |
| 280 | +directory in `zig-out`. You can customize the output directory with the |
| 281 | +`--prefix` flag. |
| 282 | + |
| 283 | +## Tests |
| 284 | + |
| 285 | +libxev has a large and growing test suite. To run the tests for the current |
| 286 | +platform: |
| 287 | + |
| 288 | +```sh |
| 289 | +$ zig build test |
| 290 | +... |
| 291 | +``` |
| 292 | + |
| 293 | +This will run all the tests for all the supported features for the current |
| 294 | +host platform. For example, on Linux this will run both the full io_uring |
| 295 | +and epoll test suite. |
| 296 | + |
| 297 | +**You can build and run tests for other platforms** by cross-compiling the |
| 298 | + test executable, copying it to a target machine and executing it. For example, |
| 299 | + the below shows how to cross-compile and build the tests for macOS from Linux: |
| 300 | + |
| 301 | + ```sh |
| 302 | + $ zig build -Dtarget=aarch64-macos -Dinstall-tests |
| 303 | + ... |
| 304 | + |
| 305 | + $ file zig-out/bin/xev-test |
| 306 | + zig-out/bin/xev-test: Mach-O 64-bit arm64 executable |
| 307 | + ``` |
| 308 | + |
| 309 | + **WASI is a special-case.** You can run tests for WASI if you have |
| 310 | + [wasmtime](https://wasmtime.dev/) installed: |
| 311 | + |
| 312 | + ``` |
| 313 | + $ zig build test -Dtarget=wasm32-wasi -Dwasmtime |
| 314 | + ... |
| 315 | + ``` |
0 commit comments