├── .envrc
├── .github
├── dependabot.yml
└── workflows
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.zig
├── build.zig.zon
├── docs
├── xev-c.7.scd
├── xev-faq.7.scd
├── xev-zig.7.scd
├── xev.7.scd
├── xev_completion_state.3.scd
├── xev_completion_zero.3.scd
└── xev_threadpool.3.scd
├── examples
├── _basic.c
├── _basic.zig
├── async.c
├── million-timers.c
└── threadpool.c
├── flake.lock
├── flake.nix
├── include
└── xev.h
├── nix
└── package.nix
├── shell.nix
├── src
├── ThreadPool.zig
├── backend
│ ├── epoll.zig
│ ├── io_uring.zig
│ ├── iocp.zig
│ ├── kqueue.zig
│ └── wasi_poll.zig
├── bench
│ ├── async1.zig
│ ├── async2.zig
│ ├── async4.zig
│ ├── async8.zig
│ ├── async_pummel_1.zig
│ ├── async_pummel_2.zig
│ ├── async_pummel_4.zig
│ ├── async_pummel_8.zig
│ ├── million-timers.zig
│ ├── ping-pongs.zig
│ ├── ping-udp1.zig
│ └── udp_pummel_1v1.zig
├── c_api.zig
├── darwin.zig
├── debug.zig
├── dynamic.zig
├── heap.zig
├── linux
│ └── timerfd.zig
├── loop.zig
├── main.zig
├── queue.zig
├── queue_mpsc.zig
├── watcher
│ ├── async.zig
│ ├── common.zig
│ ├── file.zig
│ ├── process.zig
│ ├── stream.zig
│ ├── tcp.zig
│ ├── timer.zig
│ └── udp.zig
└── windows.zig
└── website
├── .eslintrc.json
├── .gitignore
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
├── _meta.json
├── api-docs
│ ├── _meta.json
│ └── concepts.mdx
├── api
│ └── hello.ts
├── getting-started.mdx
├── getting-started
│ ├── _meta.json
│ └── concepts.mdx
└── index.mdx
├── public
├── favicon.ico
├── next.svg
├── thirteen.svg
└── vercel.svg
├── styles
├── Home.module.css
└── globals.css
├── theme.config.jsx
└── tsconfig.json
/.envrc:
--------------------------------------------------------------------------------
1 | # If we are a computer with nix-shell available, then use that to setup
2 | # the build environment with exactly what we need.
3 | if has nix; then
4 | use nix
5 | fi
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | # Check for updates to GitHub Actions every weekday
8 | interval: "daily"
9 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: Test
3 | jobs:
4 | build:
5 | strategy:
6 | matrix:
7 | os: [namespace-profile-mitchellh-sm]
8 |
9 | target: [
10 | aarch64-linux-gnu,
11 | aarch64-linux-musl,
12 | x86_64-linux-gnu,
13 | x86_64-linux-musl,
14 | aarch64-macos,
15 | x86_64-macos,
16 | # wasm32-wasi, - regressed in Zig 0.13
17 | x86_64-windows-gnu
18 |
19 | # Broken but not in any obvious way:
20 | # x86-linux-gnu,
21 | # x86-linux-musl,
22 | # x86-windows,
23 | ]
24 | runs-on: ${{ matrix.os }}
25 | needs: [test-x86_64-linux, test-x86_64-windows]
26 | env:
27 | ZIG_LOCAL_CACHE_DIR: /zig/local-cache
28 | ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
29 | steps:
30 | - name: Checkout code
31 | uses: actions/checkout@v4
32 |
33 | - name: Setup Cache
34 | uses: namespacelabs/nscloud-cache-action@v1.2.7
35 | with:
36 | path: |
37 | /nix
38 | /zig
39 |
40 | # Install Nix and use that to run our tests so our environment matches exactly.
41 | - uses: cachix/install-nix-action@v31
42 | with:
43 | nix_path: nixpkgs=channel:nixos-unstable
44 |
45 | # Run our checks to catch quick issues
46 | - run: nix flake check
47 |
48 | # Run our go tests within the context of the dev shell from the flake. This
49 | # will ensure we have all our dependencies.
50 | - name: test
51 | run: nix develop -c zig build --summary all -Dtarget=${{ matrix.target }}
52 |
53 | test-x86_64-linux:
54 | strategy:
55 | matrix:
56 | os: [namespace-profile-mitchellh-sm]
57 | runs-on: ${{ matrix.os }}
58 | env:
59 | ZIG_LOCAL_CACHE_DIR: /zig/local-cache
60 | ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
61 | steps:
62 | - name: Checkout code
63 | uses: actions/checkout@v4
64 |
65 | - name: Setup Cache
66 | uses: namespacelabs/nscloud-cache-action@v1.2.7
67 | with:
68 | path: |
69 | /nix
70 | /zig
71 |
72 | - uses: cachix/install-nix-action@v31
73 | with:
74 | nix_path: nixpkgs=channel:nixos-unstable
75 |
76 | # Run our checks to catch quick issues
77 | - run: nix flake check
78 |
79 | # Run our go tests within the context of the dev shell from the flake. This
80 | # will ensure we have all our dependencies.
81 | - name: test
82 | run: nix develop -c zig build test --summary all
83 | # WASI has regressed since Zig 0.13, we should fix it.
84 | # - name: test wasi
85 | # run: nix develop -c zig build test -Dtarget=wasm32-wasi -fwasmtime --summary all
86 |
87 | - name: build all benchmarks and examples
88 | run: nix develop -c zig build -Demit-example -Demit-bench --summary all
89 |
90 | # Run a full build to ensure that works
91 | - run: nix build
92 |
93 | test-x86_64-windows:
94 | strategy:
95 | matrix:
96 | os: [windows-latest]
97 | runs-on: ${{ matrix.os }}
98 | steps:
99 | - name: Checkout code
100 | uses: actions/checkout@v4
101 |
102 | - name: Install zig
103 | uses: goto-bus-stop/setup-zig@v2
104 | with:
105 | version: 0.14.0
106 |
107 | - name: test
108 | run: zig build test --summary all
109 |
110 | - name: build all benchmarks and examples
111 | run: zig build -Demit-example -Demit-bench --summary all
112 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .zig-cache
2 | zig-out
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Mitchell Hashimoto
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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: Stable for most use cases.** libxev is in daily use by
11 | large projects such as [Ghostty](https://ghostty.org),
12 | [zml](https://github.com/zml/zml), and more. For most use cases, libxev
13 | has been shown to be stable at scale. libxev has a broad featureset and
14 | there are likely less well-used corners of the library, but for most
15 | use cases libxev is already heavily used in production environments.
16 |
17 | **Why a new event loop library?** A few reasons. One, I think Zig lacks
18 | a generalized event loop comparable to libuv in features ("generalized"
19 | being a key word here). Two, I wanted to build a library like this around
20 | the design patterns of [io_uring](https://unixism.net/loti/what_is_io_uring.html),
21 | even mimicking its style on top of other OS primitives (
22 | [credit to this awesome blog post](https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue/)).
23 | Three, I wanted an event loop library that could build to WebAssembly
24 | (both WASI and freestanding) and that didn't really fit well
25 | into the goals of API style of existing libraries without bringing in
26 | something super heavy like Emscripten. The motivation for this library
27 | primarily though is scratching my own itch!
28 |
29 | ## Features
30 |
31 | **Cross-platform.** Linux (`io_uring` and `epoll`), macOS (`kqueue`),
32 | WebAssembly + WASI (`poll_oneoff`, threaded and non-threaded runtimes).
33 | (Windows support is planned and coming soon)
34 |
35 | **[Proactor API](https://en.wikipedia.org/wiki/Proactor_pattern).** Work
36 | is submitted to the libxev event loop and the caller is notified of
37 | work _completion_, as opposed to work _readiness_.
38 |
39 | **Zero runtime allocations.** This helps make runtime performance more
40 | predictable and makes libxev well suited for embedded environments.
41 |
42 | **Timers, TCP, UDP, Files, Processes.** High-level platform-agnostic APIs for
43 | interacting with timers, TCP/UDP sockets, files, processes, and more. For
44 | platforms that don't support async IO, the file operations are automatically
45 | scheduled to a thread pool.
46 |
47 | **Generic Thread Pool (Optional).** You can create a generic thread pool,
48 | configure its resource utilization, and use this to perform custom background
49 | tasks. The thread pool is used by some backends to do non-blocking tasks that
50 | don't have reliable non-blocking APIs (such as local file operations with
51 | `kqueue`). The thread pool can be shared across multiple threads and event
52 | loops to optimize resource utilization.
53 |
54 | **Low-level and High-Level API.** The high-level API is platform-agnostic
55 | but has some opinionated behavior and limited flexibility. The high-level
56 | API is recommended but the low-level API is always an available escape hatch.
57 | The low-level API is platform-specific and provides a mechanism for libxev
58 | users to squeeze out maximum performance. The low-level API is _just enough
59 | abstraction_ above the OS interface to make it easier to use without
60 | sacrificing noticable performance.
61 |
62 | **Tree Shaking (Zig).** This is a feature of Zig, but substantially benefits
63 | libraries such as libxev. Zig will only include function calls and features
64 | that you actually use. If you don't use a particular kind of high-level
65 | watcher (such as UDP sockets), then the functionality related to that
66 | abstraction is not compiled into your final binary at all. This lets libxev
67 | support optional "nice-to-have" functionality that may be considered
68 | "bloat" in some cases, but the end user doesn't have to pay for it.
69 |
70 | **Dependency-free.** libxev has no dependencies other than the built-in
71 | OS APIs at runtime. The C library depends on libc. This makes it very
72 | easy to cross-compile.
73 |
74 | ### Roadmap
75 |
76 | There are plenty of missing features that I still want to add:
77 |
78 | * Pipe high-level API
79 | * Signal handlers
80 | * Filesystem events
81 | * Windows backend
82 | * Freestanding WebAssembly support via an external event loop (i.e. the browser)
83 |
84 | And more...
85 |
86 | ### Performance
87 |
88 | There is plenty of room for performance improvements, and I want to be
89 | fully clear that I haven't done a lot of optimization work. Still,
90 | performance is looking good. I've tried to port many of
91 | [libuv benchmarks](https://github.com/libuv/libuv) to use the libxev
92 | API.
93 |
94 | I won't post specific benchmark results until I have a better
95 | environment to run them in. As a _very broad generalization_,
96 | you shouldn't notice a slowdown using libxev compared to other
97 | major event loops. This may differ on a feature-by-feature basis, and
98 | if you can show really poor performance in an issue I'm interested
99 | in resolving it!
100 |
101 | ## Example
102 |
103 | The example below shows an identical program written in Zig and in C
104 | that uses libxev to run a single 5s timer. This is almost silly how
105 | simple it is but is meant to just convey the overall feel of the library
106 | rather than a practical use case.
107 |
108 |
109 |
110 | Zig C
111 |
112 |
113 |
114 |
115 | ```zig
116 | const xev = @import("xev");
117 |
118 | pub fn main() !void {
119 | var loop = try xev.Loop.init(.{});
120 | defer loop.deinit();
121 |
122 | const w = try xev.Timer.init();
123 | defer w.deinit();
124 |
125 | // 5s timer
126 | var c: xev.Completion = undefined;
127 | w.run(&loop, &c, 5000, void, null, &timerCallback);
128 |
129 | try loop.run(.until_done);
130 | }
131 |
132 | fn timerCallback(
133 | userdata: ?*void,
134 | loop: *xev.Loop,
135 | c: *xev.Completion,
136 | result: xev.Timer.RunError!void,
137 | ) xev.CallbackAction {
138 | _ = userdata;
139 | _ = loop;
140 | _ = c;
141 | _ = result catch unreachable;
142 | return .disarm;
143 | }
144 | ```
145 |
146 |
147 |
148 |
149 | ```zig
150 | #include
151 | #include
152 | #include
153 |
154 | xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) {
155 | return XEV_DISARM;
156 | }
157 |
158 | int main(void) {
159 | xev_loop loop;
160 | if (xev_loop_init(&loop) != 0) {
161 | printf("xev_loop_init failure\n");
162 | return 1;
163 | }
164 |
165 | xev_watcher w;
166 | if (xev_timer_init(&w) != 0) {
167 | printf("xev_timer_init failure\n");
168 | return 1;
169 | }
170 |
171 | xev_completion c;
172 | xev_timer_run(&w, &loop, &c, 5000, NULL, &timerCallback);
173 |
174 | xev_loop_run(&loop, XEV_RUN_UNTIL_DONE);
175 |
176 | xev_timer_deinit(&w);
177 | xev_loop_deinit(&loop);
178 | return 0;
179 | }
180 | ```
181 |
182 |
183 |
184 |
185 | ## Installation (Zig)
186 |
187 | **These instructions are for Zig downstream users only.** If you are
188 | using the C API to libxev, see the "Build" section.
189 |
190 | This package works with the Zig package manager introduced in Zig 0.11.
191 | Create a `build.zig.zon` file like this:
192 |
193 | ```zig
194 | .{
195 | .name = "my-project",
196 | .version = "0.0.0",
197 | .dependencies = .{
198 | .libxev = .{
199 | .url = "https://github.com/mitchellh/libxev/archive/.tar.gz",
200 | .hash = "12208070233b17de6be05e32af096a6760682b48598323234824def41789e993432c",
201 | },
202 | },
203 | }
204 | ```
205 |
206 | And in your `build.zig`:
207 |
208 | ```zig
209 | const xev = b.dependency("libxev", .{ .target = target, .optimize = optimize });
210 | exe.addModule("xev", xev.module("xev"));
211 | ```
212 |
213 | ## Documentation
214 |
215 | 🚧 Documentation is a work-in-progress. 🚧
216 |
217 | Currently, documentation is available in three forms: **man pages**,
218 | **examples**, and **code comments.** In the future, I plan on writing detailed
219 | guides and API documentation in website form, but that isn't currently
220 | available.
221 |
222 | ### Man Pages
223 |
224 | The man pages are relatively detailed! `xev(7)` will
225 | give you a good overview of the entire library. `xev-zig(7)` and
226 | `xev-c(7)` will provide overviews of the Zig and C API, respectively.
227 | From there, API-specifc man pages such as `xev_loop_init(3)` are
228 | available. This is the best documentation currently.
229 |
230 | There are multiple ways to browse the man pages. The most immediately friendly
231 | is to just browse the raw man page sources in the `docs/` directory in
232 | your web browser. The man page source is a _markdown-like_ syntax so it
233 | renders _okay_ in your browser via GitHub.
234 |
235 | Another approach is to run `zig build -Dman-pages` and the man pages
236 | will be available in `zig-out`. This requires
237 | [scdoc](https://git.sr.ht/~sircmpwn/scdoc)
238 | to be installed (this is available in most package managers).
239 | Once you've built the man pages, you can render them by path:
240 |
241 | ```
242 | $ man zig-out/share/man/man7/xev.7
243 | ```
244 |
245 | And the final approach is to install libxev via your favorite package
246 | manager (if and when available), which should hopefully put your man pages
247 | into your man path, so you can just do `man 7 xev`.
248 |
249 | ### Examples
250 |
251 | There are examples available in the `examples/` folder. The examples are
252 | available in both C and Zig, and you can tell which one is which using
253 | the file extension.
254 |
255 | To build an example, use the following:
256 |
257 | ```
258 | $ zig build -Dexample-name=_basic.zig
259 | ...
260 | $ zig-out/bin/example-basic
261 | ...
262 | ```
263 |
264 | The `-Dexample-name` value should be the filename including the extension.
265 |
266 | ### Code Comments
267 |
268 | The Zig code is well commented. If you're comfortable reading code comments
269 | you can find a lot of insight within them. The source is in the `src/`
270 | directory.
271 |
272 | # Build
273 |
274 | Build requires the installation of the Zig 0.14.0. libxev follows stable
275 | Zig releases and generally does not support nightly builds. When a stable
276 | release is imminent we may have a branch that supports it.
277 | **libxev has no other build dependencies.**
278 |
279 | Once installed, `zig build install` on its own will build the full library and output
280 | a [FHS-compatible](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard)
281 | directory in `zig-out`. You can customize the output directory with the
282 | `--prefix` flag.
283 |
284 | ## Tests
285 |
286 | libxev has a large and growing test suite. To run the tests for the current
287 | platform:
288 |
289 | ```sh
290 | $ zig build test
291 | ...
292 | ```
293 |
294 | This will run all the tests for all the supported features for the current
295 | host platform. For example, on Linux this will run both the full io_uring
296 | and epoll test suite.
297 |
298 | **You can build and run tests for other platforms** by cross-compiling the
299 | test executable, copying it to a target machine and executing it. For example,
300 | the below shows how to cross-compile and build the tests for macOS from Linux:
301 |
302 | ```sh
303 | $ zig build -Dtarget=aarch64-macos -Dinstall-tests
304 | ...
305 |
306 | $ file zig-out/bin/xev-test
307 | zig-out/bin/xev-test: Mach-O 64-bit arm64 executable
308 | ```
309 |
310 | **WASI is a special-case.** You can run tests for WASI if you have
311 | [wasmtime](https://wasmtime.dev/) installed:
312 |
313 | ```
314 | $ zig build test -Dtarget=wasm32-wasi -Dwasmtime
315 | ...
316 | ```
317 |
--------------------------------------------------------------------------------
/build.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Step = std.Build.Step;
3 |
4 | /// A note on my build.zig style: I try to create all the artifacts first,
5 | /// unattached to any steps. At the end of the build() function, I create
6 | /// steps or attach unattached artifacts to predefined steps such as
7 | /// install. This means the only thing affecting the `zig build` user
8 | /// interaction is at the end of the build() file and makes it easier
9 | /// to reason about the structure.
10 | pub fn build(b: *std.Build) !void {
11 | const target = b.standardTargetOptions(.{});
12 | const optimize = b.standardOptimizeOption(.{});
13 |
14 | _ = b.addModule("xev", .{ .root_source_file = b.path("src/main.zig") });
15 |
16 | const emit_man = b.option(
17 | bool,
18 | "emit-man-pages",
19 | "Set to true to build man pages. Requires scdoc. Defaults to true if scdoc is found.",
20 | ) orelse if (b.findProgram(
21 | &[_][]const u8{"scdoc"},
22 | &[_][]const u8{},
23 | )) |_|
24 | true
25 | else |err| switch (err) {
26 | error.FileNotFound => false,
27 | else => return err,
28 | };
29 |
30 | const emit_bench = b.option(
31 | bool,
32 | "emit-bench",
33 | "Install the benchmark binaries to zig-out",
34 | ) orelse false;
35 |
36 | const emit_examples = b.option(
37 | bool,
38 | "emit-example",
39 | "Install the example binaries to zig-out",
40 | ) orelse false;
41 |
42 | // Static C lib
43 | const static_lib: ?*Step.Compile = lib: {
44 | if (target.result.os.tag == .wasi) break :lib null;
45 |
46 | const static_lib = b.addStaticLibrary(.{
47 | .name = "xev",
48 | .root_source_file = b.path("src/c_api.zig"),
49 | .target = target,
50 | .optimize = optimize,
51 | });
52 | static_lib.linkLibC();
53 | if (target.result.os.tag == .windows) {
54 | static_lib.linkSystemLibrary("ws2_32");
55 | static_lib.linkSystemLibrary("mswsock");
56 | }
57 | break :lib static_lib;
58 | };
59 |
60 | // Dynamic C lib
61 | const dynamic_lib: ?*Step.Compile = lib: {
62 | // We require native so we can link to libxml2
63 | if (!target.query.isNative()) break :lib null;
64 |
65 | const dynamic_lib = b.addSharedLibrary(.{
66 | .name = "xev",
67 | .root_source_file = b.path("src/c_api.zig"),
68 | .target = target,
69 | .optimize = optimize,
70 | });
71 | break :lib dynamic_lib;
72 | };
73 |
74 | // C Headers
75 | const c_header = b.addInstallFileWithDir(
76 | b.path("include/xev.h"),
77 | .header,
78 | "xev.h",
79 | );
80 |
81 | // pkg-config
82 | const pc: *Step.InstallFile = pc: {
83 | const file = b.addWriteFile("libxev.pc", b.fmt(
84 | \\prefix={s}
85 | \\includedir=${{prefix}}/include
86 | \\libdir=${{prefix}}/lib
87 | \\
88 | \\Name: libxev
89 | \\URL: https://github.com/mitchellh/libxev
90 | \\Description: High-performance, cross-platform event loop
91 | \\Version: 0.1.0
92 | \\Cflags: -I${{includedir}}
93 | \\Libs: -L${{libdir}} -lxev
94 | , .{b.install_prefix}));
95 | break :pc b.addInstallFileWithDir(
96 | file.getDirectory().path(b, "libxev.pc"),
97 | .prefix,
98 | "share/pkgconfig/libxev.pc",
99 | );
100 | };
101 |
102 | // Man pages
103 | const man = try manPages(b);
104 |
105 | // Benchmarks and examples
106 | const benchmarks = try buildBenchmarks(b, target);
107 | const examples = try buildExamples(b, target, optimize, static_lib);
108 |
109 | // Test Executable
110 | const test_exe: *Step.Compile = test_exe: {
111 | const test_filter = b.option(
112 | []const u8,
113 | "test-filter",
114 | "Filter for test",
115 | );
116 | const test_exe = b.addTest(.{
117 | .name = "xev-test",
118 | .root_source_file = b.path("src/main.zig"),
119 | .target = target,
120 | .optimize = optimize,
121 | .filter = test_filter,
122 | });
123 | switch (target.result.os.tag) {
124 | .linux, .macos => test_exe.linkLibC(),
125 | else => {},
126 | }
127 | break :test_exe test_exe;
128 | };
129 |
130 | // "test" Step
131 | {
132 | const tests_run = b.addRunArtifact(test_exe);
133 | const test_step = b.step("test", "Run tests");
134 | test_step.dependOn(&tests_run.step);
135 | }
136 |
137 | if (static_lib) |v| b.installArtifact(v);
138 | if (dynamic_lib) |v| b.installArtifact(v);
139 | b.getInstallStep().dependOn(&c_header.step);
140 | b.getInstallStep().dependOn(&pc.step);
141 | b.installArtifact(test_exe);
142 | if (emit_man) {
143 | for (man) |step| b.getInstallStep().dependOn(step);
144 | }
145 | if (emit_bench) for (benchmarks) |exe| {
146 | b.getInstallStep().dependOn(&b.addInstallArtifact(
147 | exe,
148 | .{ .dest_dir = .{ .override = .{
149 | .custom = "bin/bench",
150 | } } },
151 | ).step);
152 | };
153 | if (emit_examples) for (examples) |exe| {
154 | b.getInstallStep().dependOn(&b.addInstallArtifact(
155 | exe,
156 | .{ .dest_dir = .{ .override = .{
157 | .custom = "bin/example",
158 | } } },
159 | ).step);
160 | };
161 | }
162 |
163 | fn buildBenchmarks(
164 | b: *std.Build,
165 | target: std.Build.ResolvedTarget,
166 | ) ![]const *Step.Compile {
167 | var steps = std.ArrayList(*Step.Compile).init(b.allocator);
168 | defer steps.deinit();
169 |
170 | var dir = try std.fs.cwd().openDir(try b.build_root.join(
171 | b.allocator,
172 | &.{ "src", "bench" },
173 | ), .{ .iterate = true });
174 | defer dir.close();
175 |
176 | // Go through and add each as a step
177 | var it = dir.iterate();
178 | while (try it.next()) |entry| {
179 | // Get the index of the last '.' so we can strip the extension.
180 | const index = std.mem.lastIndexOfScalar(
181 | u8,
182 | entry.name,
183 | '.',
184 | ) orelse continue;
185 | if (index == 0) continue;
186 |
187 | // Name of the app and full path to the entrypoint.
188 | const name = entry.name[0..index];
189 |
190 | // Executable builder.
191 | const exe = b.addExecutable(.{
192 | .name = name,
193 | .root_source_file = b.path(b.fmt(
194 | "src/bench/{s}",
195 | .{entry.name},
196 | )),
197 | .target = target,
198 | .optimize = .ReleaseFast, // benchmarks are always release fast
199 | });
200 | exe.root_module.addImport("xev", b.modules.get("xev").?);
201 |
202 | // Store the mapping
203 | try steps.append(exe);
204 | }
205 |
206 | return try steps.toOwnedSlice();
207 | }
208 |
209 | fn buildExamples(
210 | b: *std.Build,
211 | target: std.Build.ResolvedTarget,
212 | optimize: std.builtin.OptimizeMode,
213 | c_lib_: ?*Step.Compile,
214 | ) ![]const *Step.Compile {
215 | var steps = std.ArrayList(*Step.Compile).init(b.allocator);
216 | defer steps.deinit();
217 |
218 | var dir = try std.fs.cwd().openDir(try b.build_root.join(
219 | b.allocator,
220 | &.{"examples"},
221 | ), .{ .iterate = true });
222 | defer dir.close();
223 |
224 | // Go through and add each as a step
225 | var it = dir.iterate();
226 | while (try it.next()) |entry| {
227 | // Get the index of the last '.' so we can strip the extension.
228 | const index = std.mem.lastIndexOfScalar(
229 | u8,
230 | entry.name,
231 | '.',
232 | ) orelse continue;
233 | if (index == 0) continue;
234 |
235 | // Name of the app and full path to the entrypoint.
236 | const name = entry.name[0..index];
237 |
238 | const is_zig = std.mem.eql(u8, entry.name[index + 1 ..], "zig");
239 | const exe: *Step.Compile = if (is_zig) exe: {
240 | const exe = b.addExecutable(.{
241 | .name = name,
242 | .root_source_file = b.path(b.fmt(
243 | "examples/{s}",
244 | .{entry.name},
245 | )),
246 | .target = target,
247 | .optimize = optimize,
248 | });
249 | exe.root_module.addImport("xev", b.modules.get("xev").?);
250 | break :exe exe;
251 | } else exe: {
252 | const c_lib = c_lib_ orelse return error.UnsupportedPlatform;
253 | const exe = b.addExecutable(.{
254 | .name = name,
255 | .target = target,
256 | .optimize = optimize,
257 | });
258 | exe.linkLibC();
259 | exe.addIncludePath(b.path("include"));
260 | exe.addCSourceFile(.{
261 | .file = b.path(b.fmt(
262 | "examples/{s}",
263 | .{entry.name},
264 | )),
265 | .flags = &[_][]const u8{
266 | "-Wall",
267 | "-Wextra",
268 | "-pedantic",
269 | "-std=c99",
270 | "-D_POSIX_C_SOURCE=199309L",
271 | },
272 | });
273 | exe.linkLibrary(c_lib);
274 | break :exe exe;
275 | };
276 |
277 | // Store the mapping
278 | try steps.append(exe);
279 | }
280 |
281 | return try steps.toOwnedSlice();
282 | }
283 |
284 | fn manPages(b: *std.Build) ![]const *Step {
285 | var steps = std.ArrayList(*Step).init(b.allocator);
286 | defer steps.deinit();
287 |
288 | var dir = try std.fs.cwd().openDir(try b.build_root.join(
289 | b.allocator,
290 | &.{"docs"},
291 | ), .{ .iterate = true });
292 | defer dir.close();
293 |
294 | var it = dir.iterate();
295 | while (try it.next()) |*entry| {
296 | // Filenames must end in "{section}.scd" and sections are
297 | // single numerals.
298 | const base = entry.name[0 .. entry.name.len - 4];
299 | const section = base[base.len - 1 ..];
300 |
301 | const cmd = b.addSystemCommand(&.{"scdoc"});
302 | cmd.setStdIn(.{ .lazy_path = b.path(
303 | b.fmt("docs/{s}", .{entry.name}),
304 | ) });
305 |
306 | try steps.append(&b.addInstallFile(
307 | cmd.captureStdOut(),
308 | b.fmt("share/man/man{s}/{s}", .{ section, base }),
309 | ).step);
310 | }
311 |
312 | return try steps.toOwnedSlice();
313 | }
314 |
--------------------------------------------------------------------------------
/build.zig.zon:
--------------------------------------------------------------------------------
1 | .{
2 | .name = .libxev,
3 | .minimum_zig_version = "0.14.0",
4 | .version = "0.0.0",
5 | .fingerprint = 0x30f7363573edabf3,
6 | .paths = .{""},
7 | }
8 |
--------------------------------------------------------------------------------
/docs/xev-c.7.scd:
--------------------------------------------------------------------------------
1 | xev-c(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual"
2 |
3 | # NAME
4 |
5 | libxev C API
6 |
7 | # DESCRIPTION
8 |
9 | See xev(7) for a general libxev overview. This man page will give a more
10 | specific overview of the C API for libxev.
11 |
12 | TODO -- This isn't written yet, sorry.
13 |
--------------------------------------------------------------------------------
/docs/xev-faq.7.scd:
--------------------------------------------------------------------------------
1 | xev-faq(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual"
2 |
3 | # NAME
4 |
5 | libxev Frequently Asked Questions (FAQ)
6 |
7 | # DESCRIPTION
8 |
9 | This manual page contains frequently asked questions around the design
10 | and usage of libxev. The goal of this page is to collect various tips or
11 | varios common challenges people run into. The information in this page
12 | may duplicate documentation that is available for a specific API or
13 | concept.
14 |
15 | # FAQ
16 |
17 | ## HOW CAN I CHECK IF A COMPLETION, TIMER, SOCKET, ETC. IS ACTIVE?
18 |
19 | Some other event loops (libuv, libev) have a function that can check if
20 | a given _thing_ (timer, socket, etc.) is "active." "Active" means that the
21 | thing in question is part of an event loop and _running_ (the definition of
22 | running is dependent on the type of the watcher).
23 |
24 | For libxev, the correct question to ask is: is this _completion_ active?
25 | A completion represents a single operation and can be used to determine
26 | if a corresponding watcher is active if the complation was used with
27 | the watcher.
28 |
29 | Completion state can be checked using the `c.state()` function (Zig) or
30 | the xev_completion_state(3) function (C). This will return an enum value
31 | of "dead" or "alive". More enum values may be added in the future if
32 | it is determined that finer grained state is useful.
33 |
34 | # SUGGEST A TOPIC
35 |
36 | There is still a lot of improvement to be made to the documentation. Please
37 | suggest any topics to cover at .
38 |
39 | # SEE ALSO
40 |
41 | xev(7)
42 |
43 |
44 |
45 | # AUTHORS
46 |
47 | Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors.
48 | See .
49 |
--------------------------------------------------------------------------------
/docs/xev-zig.7.scd:
--------------------------------------------------------------------------------
1 | xev-zig(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual"
2 |
3 | # NAME
4 |
5 | libxev Zig API
6 |
7 | # DESCRIPTION
8 |
9 | See xev(7) for a general libxev overview. This man page will give a more
10 | specific overview of the Zig API for libxev.
11 |
12 | libxev is written in Zig and exports a native Zig API. The Zig API
13 | takes advantage of first-class Zig concepts such as comptime parameters,
14 | error sets, etc. in order to provide an idiomatic Zig experience. Beyond
15 | basic idioms, these Zig features usually result in improved performance
16 | over the C API. For example, all callbacks in the Zig API must be available
17 | at comptime because the callback call is always inlined -- this results in
18 | a noticable performance improvement over equivalent C consumption of libxev.
19 |
20 | The primary Zig API is visible in `src/main.zig` in the libxev source. The main file uses a somewhat convoluted `usingnamespace` to setup the
21 | API so it isn't immediately obvious, but the API is provided by the `Xev`
22 | function. For example, available consts are: `xev.Loop`, `xev.Completion`,
23 | etc.
24 |
25 | # INSTALLATION
26 |
27 | libxev has no dependencies, making it easy to install into your Zig project
28 | using any available Zig package manager or Git submodules. The `build.zig`
29 | file exports a `module` constant that you can use with `addAnonymousModule`:
30 |
31 | ```
32 | // build.zig
33 |
34 | const libxev = @import("submodules/libxev/build.zig");
35 |
36 | pub fn build(b: *std.build.Builder) !void {
37 | // Your other build options...
38 |
39 | my_exe.addAnonymousModule("xev", libxev.module);
40 | }
41 | ```
42 |
43 | The package is then available in your source code as "xev":
44 |
45 | ```
46 | // Your main.zig
47 |
48 | const xev = @import("xev");
49 | ```
50 |
51 | # QUICK START
52 |
53 | ## INITIALIZING A LOOP
54 |
55 | After importing xev, the first thing you'll need is an event loop:
56 |
57 | ```
58 | var loop = try xev.Loop.init(.{});
59 | defer loop.deinit(); // or wherever your cleanup is
60 | ```
61 |
62 | This initializes the resources associated with an event loop. An event loop
63 | _can be copied_ until the first `run` is called. Once `run` is called,
64 | the loop *must be at a stable memory location* (pointer won't change).
65 |
66 | ## ADDING A COMPLETION
67 |
68 | An empty event loop does nothing. You must add one or more _completions_
69 | to request work to be done asynchronously. A completion is represented with
70 | the `xev.Completion` structure. A completion *must have a stable memory
71 | location when it is added to the loop* until the completion is finished.
72 | xev(7) talks more about completion lifetimes and memory allocation requirements.
73 |
74 | The example below adds a timer to the previously initialized event loop:
75 |
76 | ```
77 | var c_timer: xev.Completion = undefined;
78 | const timer = try xev.Timer.init();
79 | timer.run(&loop, &c_timer, 5_000, void, null, timerCallback);
80 | ```
81 |
82 | This uses the `xev.Timer` _high-level abstraction_. This is an abstraction
83 | that provides a common API on top of multiple operating system async APIs.
84 | You can also use the low-level API to manage completions with the loop directly,
85 | but these are not portable.
86 |
87 | **Important concept to notice:** The completion allocation is up to the
88 | program author. It can be stack allocated (such as in this case) or heap
89 | allocated. The pointer must remain stable until it is complete. This gives
90 | program authors substantial power over optimizing the performance of libxev.
91 | In fact, libxev doesn't perform _any_ dynamic memory allocation.
92 |
93 | ## RUNNING THE LOOP
94 |
95 | An added completion is a request for future work. The work does not start
96 | until the completion is submitted. A completion is submitted only during
97 | an event loop tick. To tick the event loop, use the `run` method:
98 |
99 | ```
100 | try loop.run(.until_done);
101 | ```
102 |
103 | The enum value is an `xev.RunMode` described in xev(7). This call will run
104 | until the loop has no more active completions (`until_done`). At
105 | some point (in about 5 seconds), this will call the registered `timerCallback`.
106 |
107 | # MEMORY ALLOCATIONS
108 |
109 | You'll notice that none of the Zig APIs take an allocator as an argument.
110 | libxev does not perform any dynamic memory allocations. All memory must be
111 | provided by the caller. This section will outline specific memory management
112 | rules you should be aware of when using libxev.
113 |
114 | *xev.Loop.* This can be copied and moved until `submit`, `tick`, or `run`
115 | is called. Once any of those loop functions are called, the loop memory
116 | must remain at a stable address. It is not safe to copy, move, or reuse the
117 | memory that loop is occupying until `deinit` completes.
118 |
119 | *xev.Completion.* This can be copied and moved until the completion is
120 | added to a loop. A completion is often added to a loop with any function
121 | call on a high-level abstraction that takes a completion pointer as a function
122 | argument such as `xev.Timer.run`. The completion memory can be reclaimed
123 | only when the callback associated with it is fired or the loop is deinitialized.
124 |
125 | # SUGGEST A TOPIC
126 |
127 | There is still a lot of improvement to be made to the documentation. Please
128 | suggest any topics to cover at .
129 |
130 | # SEE ALSO
131 |
132 | xev(7)
133 |
134 |
135 |
136 | # AUTHORS
137 |
138 | Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors.
139 | See .
140 |
--------------------------------------------------------------------------------
/docs/xev.7.scd:
--------------------------------------------------------------------------------
1 | xev(7) "github.com/mitchellh/libxev" "Miscellaneous Information Manual"
2 |
3 | # NAME
4 |
5 | libxev - high-performance, cross-platform event loop
6 |
7 | # DESCRIPTION
8 |
9 | *libxev* is a high-performance, cross-platform event loop. libxev provides a
10 | unified event loop abstraction for non-blocking IO, timers, signals, events,
11 | and more that works on macOS, Windows, Linux, and WebAssembly (browser and WASI).
12 | It is written in Zig but exports a C-compatible API (which makes it compatible
13 | with any language out there that can communicate with C APIs).
14 |
15 | This manual will focus on general libxev concepts. For details specific
16 | to the C API see xev-c(7) and for details specific to the Zig API see
17 | xev-zig(7).
18 |
19 | # FEATURES
20 |
21 | *Cross-platform.* Linux (io_uring(7) and epoll(7)), macOS (kqueue(2)),
22 | WebAssembly + WASI (poll_oneoff(2), threaded and non-threaded runtimes).
23 | (Windows support is planned and coming soon)
24 |
25 | *Proactor API.* Work is submitted to the libxev event loop and the caller
26 | is notified of work _completion_, as opposed to work _readiness_.
27 |
28 | *Zero runtime allocations.* This helps make runtime performance more
29 | predictable and makes libxev well suited for embedded environments.
30 |
31 | *Timers, TCP, UDP.* High-level platform-agnostic APIs for interacting
32 | with timers, TCP/UDP sockets, and more.
33 |
34 | *Generic Thread Pool (Optional).* You can create a generic thread pool,
35 | configure its resource utilization, and use this to perform custom background
36 | tasks. The thread pool is used by some backends to do non-blocking tasks that
37 | don't have reliable non-blocking APIs (such as local file operations with
38 | kqueue(7)). The thread pool can be shared across multiple threads and event
39 | loops to optimize resource utilization.
40 |
41 | *Low-level and High-Level API.* The high-level API is platform-agnostic
42 | but has some opinionated behavior and limited flexibility. The high-level
43 | API is recommended but the low-level API is always an available escape hatch.
44 | The low-level API is platform-specific and provides a mechanism for libxev
45 | users to squeeze out maximum performance. The low-level API is _just enough
46 | abstraction_ above the OS interface to make it easier to use without
47 | sacrificing noticable performance.
48 |
49 | *Tree Shaking (Zig).* This is a feature of Zig, but substantially benefits
50 | libraries such as libxev. Zig will only include function calls and features
51 | that you actually use. If you don't use a particular kind of high-level
52 | watcher (such as UDP sockets), then the functionality related to that
53 | abstraction is not compiled into your final binary at all. This lets libxev
54 | support optional "nice-to-have" functionality that may be considered
55 | "bloat" in some cases, but the end user doesn't have to pay for it.
56 |
57 | *Dependency-free.* libxev has no dependencies other than the built-in
58 | OS APIs at runtime. The C library depends on libc. This makes it very
59 | easy to cross-compile.
60 |
61 | # EVENT LOOP PROGRAMMING
62 |
63 | Event loop programming is a programming design pattern where a program
64 | registers multiple events and is notified when those events occur.
65 | Concretely, event loop programming is typically used as a foundational
66 | component of asynchronous programs. It is the core mechanism used to
67 | for asynchronous network IO, disk IO, timers, signals, and more.
68 |
69 | There are two popular event loop styles: _proactor_ and _reactor_.
70 | The reactor pattern notifies the event loop user of task _readiness_,
71 | whereas the proactor pattern notifies the event loop user of task _completion_.
72 | Examples of reactor APIs: POSIX poll(2), BSD kqueue(2). Examples of proactor
73 | APIs: Linux io_uring(7), Windows IOCP, and JavaScript IO callbacks.
74 | *libxev is a proactor event loop.*
75 |
76 | # LIBXEV GENERAL CONCEPTS
77 |
78 | ## TERMINOLOGY
79 |
80 | - *Loop*: An instance of an event loop.
81 |
82 | - *Completion*: A request to perform some work. A completion is _queued_
83 | in an event loop, and an associated callback is invoked when the work
84 | is completed.
85 |
86 | - *Watcher*: A slightly higher level abstraction to make it easier to
87 | work with common capabilities in a cross-platform way. For example,
88 | files, sockets, async/notify patterns, etc. These are just opinionated
89 | logic and sugar on top of completions.
90 |
91 | - *Disarm/Rearm*: A completion that is actively being worked on by the
92 | event loop is considered _armed_. When a completion is complete, the
93 | program can choose to _disarm_ or _rearm_ the completion. If a completion
94 | is disarmed, it is no longer in use. If a completion is rearmed, it will
95 | repeat its work and fire the associated callback again.
96 |
97 | ## MEMORY ALLOCATION
98 |
99 | libxev doesn't do any runtime memory allocation. The caller is expected
100 | to allocate memory and provide libxev with pointers. The caller is then
101 | free to allocate on the stack or heap in the way that is best suited for
102 | their program and lifecycle.
103 |
104 | The lifecycles of various resources in libxev are documented in their
105 | resource-specific documentation sections. libxev _never_ takes ownership
106 | of a programmer-provided memory location.
107 |
108 | ## LOOPS
109 |
110 | The `xev.Loop` (Zig) and `xev_loop` (C) types represent a single event
111 | loop. A program may have multiple event loops, but most typically there
112 | is at most one per thread. If you are just getting started, just use a
113 | single event loop per thread until you're more comfortable with the API.
114 |
115 | Completions are _added_ to the event loop. Completions are explained
116 | in more detail in the next section but at a high level represent a request
117 | to perform some work. The event loop does NOT begin performing any work
118 | until the loop is run. This is important, so to repeat it in another way:
119 | an _added_ completion does nothing until the next time a loop is run.
120 |
121 | An event loop is _run_ with `loop.run` (Zig) or xev_loop_run(3) (C).
122 | The first thing the event loop does is _submit_ any _added_ completions.
123 | Submitted completions begin their requested work in the background. If a
124 | completion doesn't complete and the loop run returns (such as with a "no wait"
125 | run mode -- covered later), the work will continue in the background and
126 | may be checked for completion with another loop tick.
127 |
128 | A loop can be run in multiple _run modes_:
129 |
130 | - *No Wait.* This runs through the loop without blocking on any completions.
131 | If a completion is ready, the callbacks will be fired, but otherwise
132 | the loop will return.
133 |
134 | - *Once.* This runs the loop and waits for at least one completion to become
135 | ready before returning.
136 |
137 | - *Until Done.* This runs the loop and waits until there are no more
138 | completions in the event loop. This can potentially run forever as
139 | completion callbacks rearm or register new completions. This is the
140 | most common run mode and is usually used to start up the "main" loop
141 | of a program. The loop can be stopped from the main thread using the
142 | `stop` API call.
143 |
144 | An event loop has to be allocated to a stable memory address (stable
145 | pointer) _once you have called `run`_ once. Prior to calling run, you
146 | can copy the loop value. However, once the loop has run any times
147 | (even a no wait run once), the loop pointer must remain stable.
148 |
149 | ## COMPLETIONS
150 |
151 | The `xev.Completion` (Zig) and `xev_completion` (C) types represent a
152 | single request to do some work, such as read or write a file, accept
153 | a network connection, sleep on a timer, etc.
154 |
155 | Completions do nothing until they are _added_ to an event loop (and
156 | even then, do nothing until the next event loop tick). Completions must
157 | only be added to one event loop at a time. After a completion is dead
158 | (see states below), it can be used with another event loop or the memory
159 | can be reclaimed.
160 |
161 | Completions have multiple states that are managed by libxev:
162 |
163 | - *Dead.* The completion can be configured for new work and added to
164 | an event loop. The completion is not actively being used by the loop
165 | so you can also free memory associated with it. This is its initial state.
166 |
167 | - *Added.* The completion is queued to be submitted to an event loop.
168 | The completion must no longer be modified.
169 |
170 | - *Active.* The completion is submitted as part of an event loop and actively
171 | performing some work. The completion must no longer be modified.
172 |
173 | The memory associated with a completion is always owned by the program
174 | author. libxev never takes ownership of memory and never dynamically
175 | allocates or free memory on its own. The memory associated with a completion
176 | cannot be freed unless the the completion is dead.
177 |
178 | A completion is dead only in the following scenarios:
179 |
180 | - The completion has never been added to an event loop.
181 |
182 | - The completion callback has been fired and the callback return action
183 | was "disarm." The completion memory is safe to free _during the callback_,
184 | in this case, too.
185 |
186 | - The event loop that a completion was added to has been deinitialized.
187 | Even if the completion never fired, all system resources registered by
188 | libxev for the completion are no longer in use.
189 |
190 | ## CALLBACKS
191 |
192 | When the work associated with a completion has been completed, libxev
193 | will invoke the registered callback. At a low-level, all callbacks have
194 | the same function signature. The Zig signature is shown below.
195 |
196 | ```
197 | pub const xev.Callback = *const fn (
198 | userdata: ?*anyopaque,
199 | loop: *xev.Loop,
200 | completion: *xev.Completion,
201 | result: xev.Result,
202 | ) xev.CallbackAction;
203 | ```
204 |
205 | *NOTE:* The "low-level" word above is important. In general, program authors
206 | will be using the _high-level_ APIs which have varied and more
207 | programmer-friendly callback signatures depending on the feature. For example,
208 | TCP connection accept will provide the new connection in the callback.
209 | Underneath these abstractions, however, this regular completion callback
210 | signature is used.
211 |
212 | Every callback gets access to some optional programmer-provided userdata,
213 | the loop where the completion was added, the completion itself, and a
214 | result union.
215 | The result of the callback is the action to take and is either "disarm"
216 | or "rearm" (covered in TERMINOLOGY above).
217 |
218 | Some callback tips for common behaviors:
219 |
220 | - You can reuse the completion for a different operation now as long as
221 | you return the disarm action. For example, after a TCP connection callback,
222 | you can reuse the same completion now to begin writing data. The "loop"
223 | parameter is specifically provided to make this easy.
224 |
225 | - You can free the memory associated with a completion from the callback
226 | if you no longer intend to use it. In fact, its unsafe to free memory
227 | for an active completion except after the callback is fired (or the
228 | event loop is deinitialized).
229 |
230 | # EXAMPLE (C)
231 |
232 | The example below shows how the C API can be used to initialize an event
233 | loop and run a 5 second timer. To learn more about the C API, see
234 | xev-c(7).
235 |
236 | ```
237 | #include
238 | #include
239 | #include
240 |
241 | xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) {
242 | return XEV_DISARM;
243 | }
244 |
245 | int main(void) {
246 | // Initialize the loop state. Notice we can use a stack-allocated
247 | // value here. We can even pass around the loop by value! The loop
248 | // will contain all of our "completions" (watches).
249 | xev_loop loop;
250 | if (xev_loop_init(&loop, 128) != 0) {
251 | printf("xev_loop_init failure\n");
252 | return 1;
253 | }
254 |
255 | // Initialize a completion and a watcher. A completion is the actual
256 | // thing a loop does for us, and a watcher is a high-level structure
257 | // to help make it easier to use completions.
258 | xev_completion c;
259 | xev_watcher w;
260 |
261 | // In this case, we initialize a timer watcher.
262 | if (xev_timer_init(&w) != 0) {
263 | printf("xev_timer_init failure\n");
264 | return 1;
265 | }
266 |
267 | // Configure the timer to run in 5s. This requires the completion that
268 | // we actually configure to become a timer, and the loop that the
269 | // completion should be registered with.
270 | xev_timer_run(&w, &loop, &c, 5000, NULL, &timerCallback);
271 |
272 | // Run the loop until there are no more completions.
273 | xev_loop_run(&loop, XEV_RUN_UNTIL_DONE);
274 |
275 | xev_timer_deinit(&w);
276 | xev_loop_deinit(&loop);
277 | return 0;
278 | }
279 | ```
280 |
281 | # EXAMPLE (ZIG)
282 |
283 | The example below shows how the Zig API can be used to initialize an event
284 | loop and run a 5 second timer. To learn more about the Zig API, see
285 | xev-zig(7).
286 |
287 | ```
288 | const xev = @import("xev");
289 |
290 | pub fn main() !void {
291 | // Initialize the loop state. Notice we can use a stack-allocated
292 | // value here. We can even pass around the loop by value! The loop
293 | // will contain all of our "completions" (watches).
294 | var loop = try xev.Loop.init(.{ .entries = 128 });
295 | defer loop.deinit();
296 |
297 | // Initialize a completion and a watcher. A completion is the actual
298 | // thing a loop does for us, and a watcher is a high-level structure
299 | // to help make it easier to use completions.
300 | var c: xev.Completion = undefined;
301 |
302 | // In this case, we initialize a timer watcher.
303 | const w = try xev.Timer.init();
304 | defer w.deinit();
305 |
306 | // Configure the timer to run in 5s. This requires the completion that
307 | // we actually configure to become a timer, and the loop that the
308 | // completion should be registered with.
309 | w.run(&loop, &c, 5000, void, null, &timerCallback);
310 |
311 | // Run the loop until there are no more completions.
312 | try loop.run(.until_done);
313 | }
314 |
315 | fn timerCallback(
316 | userdata: ?*void,
317 | loop: *xev.Loop,
318 | c: *xev.Completion,
319 | result: xev.Timer.RunError!void,
320 | ) xev.CallbackAction {
321 | _ = userdata;
322 | _ = loop;
323 | _ = c;
324 | _ = result catch unreachable;
325 | return .disarm;
326 | }
327 | ```
328 |
329 | # SEE ALSO
330 |
331 | xev-c(7), xev-zig(7)
332 |
333 |
334 |
335 | # AUTHORS
336 |
337 | Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors.
338 | See .
339 |
--------------------------------------------------------------------------------
/docs/xev_completion_state.3.scd:
--------------------------------------------------------------------------------
1 | xev_completion_state(3) "github.com/mitchellh/libxev" "Library Functions Manual"
2 |
3 | # NAME
4 |
5 | xev_completion_state - check the state of a completion
6 |
7 | # LIBRARY
8 |
9 | libxev (_-lxev_)
10 |
11 | # SYNOPSIS
12 |
13 | ```
14 | #include ;
15 |
16 | xev_completion_state_t state = xev_completion_state(&c);
17 | ```
18 |
19 | # DESCRIPTION
20 |
21 | *xev_completion_state* returns the current state of a completion: dead
22 | if the completion is not currently used or active if the completion is
23 | part of an event loop.
24 |
25 | The state is sometimes useful to determine if an operation has already
26 | started or not. For example, the completion state can be used to check if
27 | a timer is running or not.
28 |
29 | The completion must be initialized if there is any chance the program author
30 | may call this function prior to the completion being used. To initialize
31 | a completion use xev_completion_zero(3).
32 |
33 | This function can only be called from the main thread.
34 |
35 | # RETURN VALUES
36 |
37 | The return value is always a valid xev_completion_state_t enum value.
38 |
39 | # SEE ALSO
40 |
41 | xev_completion_state(3), xev(7), xev-c(7)
42 |
43 |
44 |
45 | # AUTHORS
46 |
47 | Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors.
48 | See .
49 |
--------------------------------------------------------------------------------
/docs/xev_completion_zero.3.scd:
--------------------------------------------------------------------------------
1 | xev_completion_zero(3) "github.com/mitchellh/libxev" "Library Functions Manual"
2 |
3 | # NAME
4 |
5 | xev_completion_zero - set a completion to the zero value
6 |
7 | # LIBRARY
8 |
9 | libxev (_-lxev_)
10 |
11 | # SYNOPSIS
12 |
13 | ```
14 | #include ;
15 |
16 | xev_completion_zero(&c);
17 | ```
18 |
19 | # DESCRIPTION
20 |
21 | *xev_completion_zero* sets default values for the given completion, rather
22 | than uninitialized memory state. This is particularly useful if you're using
23 | xev_completion_state(3). Otherwise, it isn't necessary.
24 |
25 | _You typically do NOT need to call this!_ You are allowed to pass uninitialized
26 | completions to almost all functions that start an operation. However, you
27 | _do need to call this_ if you are inspecting completion state (i.e.
28 | xev_completion_state(3)) before the completion is started.
29 |
30 | You do NOT need to call this after you are done using a completion. Once
31 | a completion has been used, the value of the completion is always initialized.
32 | The ONLY time you need to call this is if you're inspecting completion state
33 | prior to the completion ever being used.
34 |
35 | # SEE ALSO
36 |
37 | xev_completion_state(3), xev(7), xev-c(7)
38 |
39 |
40 |
41 | # AUTHORS
42 |
43 | Mitchell Hashimoto (xmitchx@gmail.com) and any open source contributors.
44 | See .
45 |
--------------------------------------------------------------------------------
/docs/xev_threadpool.3.scd:
--------------------------------------------------------------------------------
1 | xev_threadpool(3) "github.com/mitchellh/libxev" "Library Functions Manual"
2 |
3 | # NAME
4 |
5 | xev_threadpool - generic thread pool for scheduling work
6 |
7 | # LIBRARY
8 |
9 | libxev (_-lxev_)
10 |
11 | # SYNOPSIS
12 |
13 | ```
14 | #include ;
15 |
16 | xev_threadpool pool;
17 | ```
18 |
19 | # DESCRIPTION
20 |
21 | *xev_threadpool* is a generic thread pool that is lock-free, allocation-free
22 | (except spawning threads), supports batch scheduling, dynamically spawns
23 | threads, and handles thread spawn failure. It can be used to queue work that
24 | should be executed on other threads as resources permit.
25 |
26 | This man page focuses on the C API for the thread pool. The Zig API
27 | can be discovered by reading the `src/ThreadPool.zig` file. This page will
28 | provide a broad overview of using the thread pool API but won't cover
29 | specific details such as an exhaustive list of error return codes.
30 |
31 | There are multiple types that are important when working with thread pools.
32 |
33 | - *xev_threadpool* is the pool itself.
34 | - *xev_threadpool_config* is used to configure a new pool.
35 | - *xev_threadpool_task* is a single task to execute.
36 | - *xev_threadpool_batch* is a batch of zero or more tasks.
37 |
38 | All the types are expected to be allocated by the program author. They
39 | can be allocated on the stack or heap, but their pointer lifetimes must
40 | remain valid as documented throughout this manual.
41 |
42 | # POOL CREATION AND MANAGEMENT
43 |
44 | To start, a pool must be initialized. The easiest way to initialize a pool
45 | is with xev_threadpool_init(3) and no configuration. The default configuration
46 | will choose reasonable defaults.
47 |
48 | ```
49 | xev_threadpool pool;
50 | assert(xev_threadpool_init(&pool, null));
51 | ```
52 |
53 | The pool is now ready to have task batches scheduled to it. To shutdown
54 | a pool, you must call xev_shutdown(3) and xev_deinit(3).
55 |
56 | ```
57 | xev_shutdown(&pool);
58 | xev_deinit(&pool);
59 | ```
60 |
61 | The xev_shutdown(3) call notifies all threads that the pool is in a shutdown
62 | state. They complete their most recent tasks, shut down, and accept no new
63 | work. This function returns _immediately_. The xev_deinit(3) call waits for
64 | all the threads in the pool to exit, then cleans up any additional state.
65 | xev_deinit(3) and xev_shutdown(3) can be called in any order.
66 |
67 | The xev_threadpool(3) value must be pointer-stable until after
68 | xev_deinit(3) returns. After this point, no other xev_threadpool API
69 | calls can be called using the pointer. You may reinitialize and reuse the
70 | value.
71 |
72 | # TASK SCHEDULING
73 |
74 | A task is a single unit of work. A task is inserted into a _batch_.
75 | A batch is scheduled with a _pool_. The first step is to define a task:
76 |
77 | ```
78 | void do_expensive_work(xev_threadpool_task* t) {}
79 |
80 | xev_threadpool_task t;
81 | xev_threadpool_task_init(&t, &do_expensive_work);
82 | ```
83 |
84 | The task then must be added to a batch. The code below creates a batch
85 | with a single item "t". You can use xev_threadpool_batch_push_batch(3) to merge
86 | multiple batches.
87 |
88 | ```
89 | xev_threadpool_batch b;
90 | xev_threadpool_batch_init(&b);
91 | xev_threadpool_batch_push_task(&b, &t);
92 | ```
93 |
94 | Finally, the batch must be scheduled with the pool with
95 | xev_threadpool_schedule(3):
96 |
97 | ```
98 | xev_threadpool_schedule(&pool, &b);
99 | ```
100 |
101 | The scheduled work can be picked up immediately. The work is executed
102 | on a separate thread so if resources are available, the work may begin
103 | immediately. Otherwise, it is queued for execution later.
104 |
105 | You can call xev_threadpool_schedule(3) from multiple threads against
106 | the same pool to schedule work concurrently. You MUST NOT read or write
107 | batches concurrently; for concurrent scheduling each thread should build
108 | up its own batch.
109 |
110 | ## MEMORY LIFETIMES
111 |
112 | - The task "t" must be pointer-stable until the task has completed execution.
113 |
114 | - The task "t" can only be scheduled in one pool exactly one time until
115 | it has completed execution. You CAN NOT initialize a task and add it to
116 | multiple batches.
117 |
118 | - The batch "b" can be copied and doesn't need to be pointer-stable. The
119 | batch can be freed at anytime.
120 |
121 | ## TASK STATE
122 |
123 | The callback type only gives access to the `xev_threadpool_task` pointer.
124 | To associate state or userdata with a task, make the task a member of
125 | a struct and use the `container_of` macro (shown below) to access the
126 | parent container.
127 |
128 | An example is shown below:
129 |
130 | ```
131 | typedef struct {
132 | xev_threadpool_task task;
133 | bool state;
134 |
135 | // other state can be here, too.
136 | } work_t;
137 |
138 |
139 | #define container_of(ptr, type, member) \
140 | ((type *) ((char *) (ptr) - offsetof(type, member)))
141 |
142 | void do_expensive_work(xev_threadpool_task* t) {
143 | work_t *work = container_of(t, work_t, task);
144 | work->state = true;
145 | }
146 |
147 | work_t work;
148 | xev_threadpool_task_init(&work.task, &do_expensive_work);
149 | ```
150 |
151 | *IMPORTANT:* The `xev_threadpool_task` must be aligned to a power-of-2
152 | memory address. When using it in a struct, be careful that it is properly
153 | aligned.
154 |
155 | # SEE ALSO
156 |
157 | xev(7), xev-c(7)
158 |
159 |
160 |
161 |
162 |
163 | # AUTHORS
164 |
165 | King Protty (https://github.com/kprotty) is the author of the thread pool.
166 | Mitchell Hashimoto (xmitchx@gmail.com) is the author of the C API and
167 | documentation. Plus any open source contributors. See .
168 |
--------------------------------------------------------------------------------
/examples/_basic.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | xev_cb_action timerCallback(xev_loop* loop, xev_completion* c, int result, void *userdata) {
6 | return XEV_DISARM;
7 | }
8 |
9 | int main(void) {
10 | // Initialize the loop state. Notice we can use a stack-allocated
11 | // value here. We can even pass around the loop by value! The loop
12 | // will contain all of our "completions" (watches).
13 | xev_loop loop;
14 | if (xev_loop_init(&loop) != 0) {
15 | printf("xev_loop_init failure\n");
16 | return 1;
17 | }
18 |
19 | // Initialize a completion and a watcher. A completion is the actual
20 | // thing a loop does for us, and a watcher is a high-level structure
21 | // to help make it easier to use completions.
22 | xev_completion c;
23 | xev_watcher w;
24 |
25 | // In this case, we initialize a timer watcher.
26 | if (xev_timer_init(&w) != 0) {
27 | printf("xev_timer_init failure\n");
28 | return 1;
29 | }
30 |
31 | // Configure the timer to run in 1ms. This requires the completion that
32 | // we actually configure to become a timer, and the loop that the
33 | // completion should be registered with.
34 | xev_timer_run(&w, &loop, &c, 1, NULL, &timerCallback);
35 |
36 | // Run the loop until there are no more completions.
37 | xev_loop_run(&loop, XEV_RUN_UNTIL_DONE);
38 |
39 | xev_timer_deinit(&w);
40 | xev_loop_deinit(&loop);
41 | return 0;
42 | }
43 |
--------------------------------------------------------------------------------
/examples/_basic.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Instant = std.time.Instant;
3 | const xev = @import("xev");
4 |
5 | pub fn main() !void {
6 | // Initialize the loop state. Notice we can use a stack-allocated
7 | // value here. We can even pass around the loop by value! The loop
8 | // will contain all of our "completions" (watches).
9 | var loop = try xev.Loop.init(.{});
10 | defer loop.deinit();
11 |
12 | // Initialize a completion and a watcher. A completion is the actual
13 | // thing a loop does for us, and a watcher is a high-level structure
14 | // to help make it easier to use completions.
15 | var c: xev.Completion = undefined;
16 | const timer = try xev.Timer.init();
17 | timer.run(&loop, &c, 1, void, null, timerCallback);
18 |
19 | // Run the loop until there are no more completions.
20 | try loop.run(.until_done);
21 | }
22 |
23 | fn timerCallback(
24 | _: ?*void,
25 | _: *xev.Loop,
26 | _: *xev.Completion,
27 | result: xev.Timer.RunError!void,
28 | ) xev.CallbackAction {
29 | _ = result catch unreachable;
30 | return .disarm;
31 | }
32 |
--------------------------------------------------------------------------------
/examples/async.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #define UNUSED(v) ((void)v);
7 |
8 | xev_cb_action timer_callback(xev_loop* loop, xev_completion* c, int result, void *userdata) {
9 | UNUSED(loop); UNUSED(c); UNUSED(result);
10 |
11 | // Send the notification to our async which will wake up the loop and
12 | // call the waiter callback.
13 | xev_async_notify((xev_watcher *)userdata);
14 | return XEV_DISARM;
15 | }
16 |
17 | xev_cb_action async_callback(xev_loop* loop, xev_completion* c, int result, void *userdata) {
18 | UNUSED(loop); UNUSED(c); UNUSED(result);
19 |
20 | bool *notified = (bool *)userdata;
21 | *notified = true;
22 | return XEV_DISARM;
23 | }
24 |
25 | int main(void) {
26 | xev_loop loop;
27 | if (xev_loop_init(&loop) != 0) {
28 | printf("xev_loop_init failure\n");
29 | return 1;
30 | }
31 |
32 | // Initialize an async watcher. An async watcher can be used to wake up
33 | // the event loop from any thread.
34 | xev_completion async_c;
35 | xev_watcher async;
36 | if (xev_async_init(&async) != 0) {
37 | printf("xev_async_init failure\n");
38 | return 1;
39 | }
40 |
41 | // We start a "waiter" for the async watcher. Only one waiter can
42 | // ever be set at a time. This callback will be called when the async
43 | // is notified (via xev_async_notify).
44 | bool notified = false;
45 | xev_async_wait(&async, &loop, &async_c, ¬ified, &async_callback);
46 |
47 | // Initialize a timer. The timer will fire our async.
48 | xev_completion timer_c;
49 | xev_watcher timer;
50 | if (xev_timer_init(&timer) != 0) {
51 | printf("xev_timer_init failure\n");
52 | return 1;
53 | }
54 | xev_timer_run(&timer, &loop, &timer_c, 1, &async, &timer_callback);
55 |
56 | // Run the loop until there are no more completions. This means
57 | // that both the async watcher AND the timer have to complete.
58 | // Notice that if you comment out `xev_async_notify` in the timer
59 | // callback this blocks forever (because the async watcher is waiting).
60 | xev_loop_run(&loop, XEV_RUN_UNTIL_DONE);
61 |
62 | if (!notified) {
63 | printf("FAIL! async should've been notified!");
64 | return 1;
65 | }
66 |
67 | xev_timer_deinit(&timer);
68 | xev_async_deinit(&async);
69 | xev_loop_deinit(&loop);
70 | return 0;
71 | }
72 |
--------------------------------------------------------------------------------
/examples/million-timers.c:
--------------------------------------------------------------------------------
1 | // libuv million-timers benchmark ported to libxev
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #define NUM_TIMERS (10 * 1000 * 1000)
9 |
10 | static int timer_cb_called;
11 |
12 | xev_cb_action timer_cb(xev_loop* loop, xev_completion* c, int result, void *userdata) {
13 | timer_cb_called++;
14 | return XEV_DISARM;
15 | }
16 |
17 | #ifdef _WIN32
18 | #include
19 | uint64_t hrtime(void) {
20 | static int initialized = 0;
21 | static LARGE_INTEGER start_timestamp;
22 | static uint64_t qpc_tick_duration;
23 |
24 | if (!initialized) {
25 | initialized = 1;
26 |
27 | LARGE_INTEGER qpc_freq;
28 | QueryPerformanceFrequency(&qpc_freq);
29 | qpc_tick_duration = 1e9 / qpc_freq.QuadPart;
30 |
31 | QueryPerformanceCounter(&start_timestamp);
32 | }
33 |
34 | LARGE_INTEGER t;
35 | QueryPerformanceCounter(&t);
36 | t.QuadPart -= start_timestamp.QuadPart;
37 |
38 | return (uint64_t)t.QuadPart * qpc_tick_duration;
39 | }
40 | #else
41 | uint64_t hrtime(void) {
42 | struct timespec ts;
43 | clock_gettime(CLOCK_MONOTONIC, &ts);
44 | return ts.tv_nsec + (ts.tv_sec * 1e9);
45 | }
46 | #endif
47 |
48 | int main(void) {
49 | xev_watcher* timers;
50 | xev_completion* completions;
51 | xev_loop loop;
52 | uint64_t before_all;
53 | uint64_t before_run;
54 | uint64_t after_run;
55 | uint64_t after_all;
56 | int timeout;
57 | int i;
58 | int err;
59 |
60 | timers = malloc(NUM_TIMERS * sizeof(timers[0]));
61 | completions = malloc(NUM_TIMERS * sizeof(completions[0]));
62 |
63 | if ((err = xev_loop_init(&loop)) != 0) {
64 | fprintf(stderr, "xev_loop_init failure\n");
65 | return 1;
66 | }
67 | timeout = 1;
68 |
69 | before_all = hrtime();
70 | for (i = 0; i < NUM_TIMERS; i++) {
71 | if (i % 1000 == 0) timeout++;
72 | xev_timer_init(timers + i);
73 | xev_timer_run(timers + i, &loop, completions + i, timeout, NULL, &timer_cb);
74 | }
75 |
76 | before_run = hrtime();
77 | xev_loop_run(&loop, XEV_RUN_UNTIL_DONE);
78 | after_run = hrtime();
79 | after_all = hrtime();
80 |
81 | if (timer_cb_called != NUM_TIMERS) return 1;
82 | free(timers);
83 | free(completions);
84 |
85 | fprintf(stderr, "%.2f seconds total\n", (after_all - before_all) / 1e9);
86 | fprintf(stderr, "%.2f seconds init\n", (before_run - before_all) / 1e9);
87 | fprintf(stderr, "%.2f seconds dispatch\n", (after_run - before_run) / 1e9);
88 | fprintf(stderr, "%.2f seconds cleanup\n", (after_all - after_run) / 1e9);
89 | fflush(stderr);
90 | return 0;
91 | }
92 |
--------------------------------------------------------------------------------
/examples/threadpool.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | // We create a job struct that can contain additional state that we use
8 | // for our threadpool tasks. In this case, we only have a boolean to note
9 | // we're done but you can imagine any sort of user data here!
10 | typedef struct {
11 | xev_threadpool_task pool_task;
12 |
13 | bool done;
14 | } job_t;
15 |
16 | // We use the container_of trick to access our job_t from a threadpool task.
17 | // See task_callback.
18 | #define container_of(ptr, type, member) \
19 | ((type *) ((char *) (ptr) - offsetof(type, member)))
20 |
21 | // This is the callback that is invoked when the task is being worked on.
22 | void task_callback(xev_threadpool_task* t) {
23 | job_t *job = container_of(t, job_t, pool_task);
24 | job->done = true;
25 | }
26 |
27 | int main(void) {
28 | xev_threadpool pool;
29 | if (xev_threadpool_init(&pool, NULL) != 0) {
30 | printf("xev_threadpool_init failure\n");
31 | return 1;
32 | }
33 |
34 | // A "batch" is used to group together multiple tasks that we schedule
35 | // atomically into the thread pool. We initialize an empty batch that
36 | // we'll add our tasks to.
37 | xev_threadpool_batch batch;
38 | xev_threadpool_batch_init(&batch);
39 |
40 | // Create all our tasks we want to submit. The number here can be changed
41 | // to anything!
42 | const int TASK_COUNT = 128;
43 | job_t jobs[TASK_COUNT];
44 | for (int i = 0; i < TASK_COUNT; i++) {
45 | jobs[i].done = false;
46 | xev_threadpool_task_init(&jobs[i].pool_task, &task_callback);
47 | xev_threadpool_batch_push_task(&batch, &jobs[i].pool_task);
48 | }
49 |
50 | // Schedule our batch. This will immediately queue and start the tasks
51 | // if there are available threads. This will also automatically start
52 | // threads as needed. After this, you can reclaim the memory associated
53 | // with "batch".
54 | xev_threadpool_schedule(&pool, &batch);
55 |
56 | // We need a way to detect that our work is done. Normally here you'd
57 | // use some sort of waitgroup or signal the libxev loop or something.
58 | // Since this example is showing ONLY the threadpool, we just do a
59 | // somewhat unsafe thing and just race on done booleans...
60 | while (true) {
61 | bool done = true;
62 | for (int i = 0; i < TASK_COUNT; i++) {
63 | if (!jobs[i].done) {
64 | done = false;
65 | break;
66 | }
67 | }
68 | if (done) break;
69 | }
70 |
71 | // Shutdown notifies the threadpool to notify the threads it has launched
72 | // to start shutting down. This MUST be called.
73 | xev_threadpool_shutdown(&pool);
74 |
75 | // Deinit reclaims memory.
76 | xev_threadpool_deinit(&pool);
77 |
78 | printf("%d tasks completed!\n", TASK_COUNT);
79 | return 0;
80 | }
81 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-compat": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1673956053,
7 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
8 | "owner": "edolstra",
9 | "repo": "flake-compat",
10 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "edolstra",
15 | "repo": "flake-compat",
16 | "type": "github"
17 | }
18 | },
19 | "flake-compat_2": {
20 | "flake": false,
21 | "locked": {
22 | "lastModified": 1696426674,
23 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
24 | "owner": "edolstra",
25 | "repo": "flake-compat",
26 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
27 | "type": "github"
28 | },
29 | "original": {
30 | "owner": "edolstra",
31 | "repo": "flake-compat",
32 | "type": "github"
33 | }
34 | },
35 | "flake-utils": {
36 | "locked": {
37 | "lastModified": 1667395993,
38 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
39 | "owner": "numtide",
40 | "repo": "flake-utils",
41 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
42 | "type": "github"
43 | },
44 | "original": {
45 | "owner": "numtide",
46 | "repo": "flake-utils",
47 | "type": "github"
48 | }
49 | },
50 | "flake-utils_2": {
51 | "inputs": {
52 | "systems": "systems"
53 | },
54 | "locked": {
55 | "lastModified": 1705309234,
56 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
57 | "owner": "numtide",
58 | "repo": "flake-utils",
59 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
60 | "type": "github"
61 | },
62 | "original": {
63 | "owner": "numtide",
64 | "repo": "flake-utils",
65 | "type": "github"
66 | }
67 | },
68 | "nixpkgs": {
69 | "locked": {
70 | "lastModified": 1717895720,
71 | "narHash": "sha256-Dl6JKx1rIDEuv4q9rtlt9QwyerSQbrk1bUtNHzx9bIY=",
72 | "owner": "nixos",
73 | "repo": "nixpkgs",
74 | "rev": "0e0826ec06d2b3db8e28e280d68179f022b1d160",
75 | "type": "github"
76 | },
77 | "original": {
78 | "owner": "nixos",
79 | "ref": "release-24.05",
80 | "repo": "nixpkgs",
81 | "type": "github"
82 | }
83 | },
84 | "nixpkgs-unstable": {
85 | "locked": {
86 | "lastModified": 1675584158,
87 | "narHash": "sha256-SBkchaDzCHxnPNRDdtZ5ko5caHio9iS0Mbyn/xXbXxs=",
88 | "owner": "nixos",
89 | "repo": "nixpkgs",
90 | "rev": "d840126a0890621e7b220894d749132dd4bde6a0",
91 | "type": "github"
92 | },
93 | "original": {
94 | "owner": "nixos",
95 | "ref": "nixpkgs-unstable",
96 | "repo": "nixpkgs",
97 | "type": "github"
98 | }
99 | },
100 | "nixpkgs_2": {
101 | "locked": {
102 | "lastModified": 1708161998,
103 | "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=",
104 | "owner": "NixOS",
105 | "repo": "nixpkgs",
106 | "rev": "84d981bae8b5e783b3b548de505b22880559515f",
107 | "type": "github"
108 | },
109 | "original": {
110 | "owner": "NixOS",
111 | "ref": "nixos-23.11",
112 | "repo": "nixpkgs",
113 | "type": "github"
114 | }
115 | },
116 | "root": {
117 | "inputs": {
118 | "flake-compat": "flake-compat",
119 | "flake-utils": "flake-utils",
120 | "nixpkgs": "nixpkgs",
121 | "nixpkgs-unstable": "nixpkgs-unstable",
122 | "zig": "zig"
123 | }
124 | },
125 | "systems": {
126 | "locked": {
127 | "lastModified": 1681028828,
128 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
129 | "owner": "nix-systems",
130 | "repo": "default",
131 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
132 | "type": "github"
133 | },
134 | "original": {
135 | "owner": "nix-systems",
136 | "repo": "default",
137 | "type": "github"
138 | }
139 | },
140 | "zig": {
141 | "inputs": {
142 | "flake-compat": "flake-compat_2",
143 | "flake-utils": "flake-utils_2",
144 | "nixpkgs": "nixpkgs_2"
145 | },
146 | "locked": {
147 | "lastModified": 1741566430,
148 | "narHash": "sha256-BEKycplUAd9A0KBKIKrV2tw11+JmXEbVU8zMpe4AJ38=",
149 | "owner": "mitchellh",
150 | "repo": "zig-overlay",
151 | "rev": "b2897fe1ff741b627cbb8c6e41531fe9d5a4ed47",
152 | "type": "github"
153 | },
154 | "original": {
155 | "owner": "mitchellh",
156 | "repo": "zig-overlay",
157 | "type": "github"
158 | }
159 | }
160 | },
161 | "root": "root",
162 | "version": 7
163 | }
164 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "libxev is a high performance, cross-platform event loop.";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs/release-24.05";
6 | nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
7 | flake-utils.url = "github:numtide/flake-utils";
8 | zig.url = "github:mitchellh/zig-overlay";
9 |
10 | # Used for shell.nix
11 | flake-compat = {
12 | url = github:edolstra/flake-compat;
13 | flake = false;
14 | };
15 | };
16 |
17 | outputs = {
18 | self,
19 | nixpkgs,
20 | flake-utils,
21 | ...
22 | } @ inputs: let
23 | overlays = [
24 | # Other overlays
25 | (final: prev: rec {
26 | zigpkgs = inputs.zig.packages.${prev.system};
27 | zig = inputs.zig.packages.${prev.system}."0.14.0";
28 |
29 | # Latest versions
30 | wasmtime = inputs.nixpkgs-unstable.legacyPackages.${prev.system}.wasmtime;
31 | wasmer = inputs.nixpkgs-unstable.legacyPackages.${prev.system}.wasmer;
32 |
33 | # Our package
34 | libxev = prev.callPackage ./nix/package.nix {};
35 | })
36 | ];
37 |
38 | # Our supported systems are the same supported systems as the Zig binaries
39 | systems = builtins.attrNames inputs.zig.packages;
40 | in
41 | flake-utils.lib.eachSystem systems (
42 | system: let
43 | pkgs = import nixpkgs {inherit overlays system;};
44 | in rec {
45 | devShells.default = pkgs.mkShell {
46 | nativeBuildInputs = with pkgs; [
47 | mandoc
48 | scdoc
49 | zig
50 |
51 | # Wasm
52 | wabt
53 | wasmtime
54 | wasmer
55 |
56 | # Website
57 | nodejs
58 | ];
59 | };
60 |
61 | # For compatibility with older versions of the `nix` binary
62 | devShell = self.devShells.${system}.default;
63 |
64 | # Our package
65 | packages.libxev = pkgs.libxev;
66 | defaultPackage = packages.libxev;
67 | }
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/include/xev.h:
--------------------------------------------------------------------------------
1 | #ifndef XEV_H
2 | #define XEV_H
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #include
9 | #include
10 |
11 | /* TODO(mitchellh): we should use platform detection to set the correct
12 | * byte sizes here. We choose some overly large values for now so that
13 | * we can retain ABI compatibility. */
14 | const size_t XEV_SIZEOF_LOOP = 512;
15 | const size_t XEV_SIZEOF_COMPLETION = 320;
16 | const size_t XEV_SIZEOF_WATCHER = 256;
17 | const size_t XEV_SIZEOF_THREADPOOL = 64;
18 | const size_t XEV_SIZEOF_THREADPOOL_BATCH = 24;
19 | const size_t XEV_SIZEOF_THREADPOOL_TASK = 24;
20 | const size_t XEV_SIZEOF_THREADPOOL_CONFIG = 64;
21 |
22 | #if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L
23 | typedef max_align_t XEV_ALIGN_T;
24 | #else
25 | // max_align_t is usually synonymous with the largest scalar type, which is long double on most platforms, and its alignment requirement is either 8 or 16.
26 | typedef long double XEV_ALIGN_T;
27 | #endif
28 |
29 | /* There's a ton of preprocessor directives missing here for real cross-platform
30 | * compatibility. I'm going to defer to the community or future issues to help
31 | * plug those holes. For now, we get some stuff working we can test! */
32 |
33 | /* Opaque types. These types have a size defined so that they can be
34 | * statically allocated but they are not to be accessed. */
35 | // todo: give struct individual alignment, instead of max alignment
36 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_LOOP - sizeof(XEV_ALIGN_T)]; } xev_loop;
37 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_COMPLETION - sizeof(XEV_ALIGN_T)]; } xev_completion;
38 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_WATCHER - sizeof(XEV_ALIGN_T)]; } xev_watcher;
39 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL - sizeof(XEV_ALIGN_T)]; } xev_threadpool;
40 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_BATCH - sizeof(XEV_ALIGN_T)]; } xev_threadpool_batch;
41 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_TASK - sizeof(XEV_ALIGN_T)]; } xev_threadpool_task;
42 | typedef struct { XEV_ALIGN_T _pad; uint8_t data[XEV_SIZEOF_THREADPOOL_CONFIG - sizeof(XEV_ALIGN_T)]; } xev_threadpool_config;
43 |
44 | /* Callback types. */
45 | typedef enum { XEV_DISARM = 0, XEV_REARM = 1 } xev_cb_action;
46 | typedef void (*xev_task_cb)(xev_threadpool_task* t);
47 | typedef xev_cb_action (*xev_timer_cb)(xev_loop* l, xev_completion* c, int result, void* userdata);
48 | typedef xev_cb_action (*xev_async_cb)(xev_loop* l, xev_completion* c, int result, void* userdata);
49 |
50 | typedef enum {
51 | XEV_RUN_NO_WAIT = 0,
52 | XEV_RUN_ONCE = 1,
53 | XEV_RUN_UNTIL_DONE = 2,
54 | } xev_run_mode_t;
55 |
56 | typedef enum {
57 | XEV_COMPLETION_DEAD = 0,
58 | XEV_COMPLETION_ACTIVE = 1,
59 | } xev_completion_state_t;
60 |
61 |
62 | /* Documentation for functions can be found in man pages or online. I
63 | * purposely do not add docs to the header so that you can quickly scan
64 | * all exported functions. */
65 | int xev_loop_init(xev_loop* loop);
66 | void xev_loop_deinit(xev_loop* loop);
67 | int xev_loop_run(xev_loop* loop, xev_run_mode_t mode);
68 | int64_t xev_loop_now(xev_loop* loop);
69 | void xev_loop_update_now(xev_loop* loop);
70 |
71 | void xev_completion_zero(xev_completion* c);
72 | xev_completion_state_t xev_completion_state(xev_completion* c);
73 |
74 | void xev_threadpool_config_init(xev_threadpool_config* config);
75 | void xev_threadpool_config_set_stack_size(xev_threadpool_config* config, uint32_t v);
76 | void xev_threadpool_config_set_max_threads(xev_threadpool_config* config, uint32_t v);
77 |
78 | int xev_threadpool_init(xev_threadpool* pool, xev_threadpool_config* config);
79 | void xev_threadpool_deinit(xev_threadpool* pool);
80 | void xev_threadpool_shutdown(xev_threadpool* pool);
81 | void xev_threadpool_schedule(xev_threadpool* pool, xev_threadpool_batch *batch);
82 |
83 | void xev_threadpool_task_init(xev_threadpool_task* t, xev_task_cb cb);
84 | void xev_threadpool_batch_init(xev_threadpool_batch* b);
85 | void xev_threadpool_batch_push_task(xev_threadpool_batch* b, xev_threadpool_task *t);
86 | void xev_threadpool_batch_push_batch(xev_threadpool_batch* b, xev_threadpool_batch *other);
87 |
88 | int xev_timer_init(xev_watcher *w);
89 | void xev_timer_deinit(xev_watcher *w);
90 | void xev_timer_run(xev_watcher *w, xev_loop* loop, xev_completion* c, uint64_t next_ms, void* userdata, xev_timer_cb cb);
91 | void xev_timer_reset(xev_watcher *w, xev_loop* loop, xev_completion* c, xev_completion *c_cancel, uint64_t next_ms, void* userdata, xev_timer_cb cb);
92 | void xev_timer_cancel(xev_watcher *w, xev_loop* loop, xev_completion* c, xev_completion* c_cancel, void* userdata, xev_timer_cb cb);
93 |
94 | int xev_async_init(xev_watcher *w);
95 | void xev_async_deinit(xev_watcher *w);
96 | int xev_async_notify(xev_watcher *w);
97 | void xev_async_wait(xev_watcher *w, xev_loop* loop, xev_completion* c, void* userdata, xev_async_cb cb);
98 |
99 | #ifdef __cplusplus
100 | }
101 | #endif
102 |
103 | #endif /* XEV_H */
104 |
--------------------------------------------------------------------------------
/nix/package.nix:
--------------------------------------------------------------------------------
1 | { stdenv
2 | , lib
3 | , zig
4 | , pkg-config
5 | , scdoc
6 | }:
7 |
8 | stdenv.mkDerivation rec {
9 | pname = "libxev";
10 | version = "0.1.0";
11 |
12 | src = ./..;
13 |
14 | nativeBuildInputs = [ zig scdoc pkg-config ];
15 |
16 | buildInputs = [];
17 |
18 | dontConfigure = true;
19 |
20 | preBuild = ''
21 | # Necessary for zig cache to work
22 | export HOME=$TMPDIR
23 | '';
24 |
25 | installPhase = ''
26 | runHook preInstall
27 | zig build -Doptimize=ReleaseFast -Demit-man-pages --prefix $out install
28 | runHook postInstall
29 | '';
30 |
31 | outputs = [ "out" "dev" "man" ];
32 |
33 | meta = with lib; {
34 | description = "A high performance, cross-platform event loop.";
35 | homepage = "https://github.com/mitchellh/libxev";
36 | license = licenses.mit;
37 | platforms = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | (import
2 | (
3 | let
4 | flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat;
5 | in
6 | fetchTarball {
7 | url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz";
8 | sha256 = flake-compat.locked.narHash;
9 | }
10 | )
11 | {src = ./.;})
12 | .shellNix
13 |
--------------------------------------------------------------------------------
/src/bench/async1.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 | const Allocator = std.mem.Allocator;
4 | const Instant = std.time.Instant;
5 | const xev = @import("xev");
6 | //const xev = @import("xev").Dynamic;
7 |
8 | pub const std_options: std.Options = .{
9 | .log_level = .info,
10 | };
11 |
12 | // Tune-ables
13 | pub const NUM_PINGS = 1000 * 1000;
14 |
15 | pub fn main() !void {
16 | try run(1);
17 | }
18 |
19 | pub fn run(comptime thread_count: comptime_int) !void {
20 | if (comptime xev.dynamic) try xev.detect();
21 | var loop = try xev.Loop.init(.{});
22 | defer loop.deinit();
23 |
24 | // Initialize all our threads
25 | var contexts: [thread_count]Thread = undefined;
26 | var threads: [contexts.len]std.Thread = undefined;
27 | var comps: [contexts.len]xev.Completion = undefined;
28 | for (&contexts, 0..) |*ctx, i| {
29 | ctx.* = try Thread.init();
30 | ctx.main_async.wait(&loop, &comps[i], Thread, ctx, mainAsyncCallback);
31 | threads[i] = try std.Thread.spawn(.{}, Thread.threadMain, .{ctx});
32 | }
33 |
34 | const start_time = try Instant.now();
35 | try loop.run(.until_done);
36 | for (&threads) |thr| thr.join();
37 | const end_time = try Instant.now();
38 |
39 | const elapsed = @as(f64, @floatFromInt(end_time.since(start_time)));
40 | std.log.info("async{d}: {d:.2} seconds ({d:.2}/sec)", .{
41 | thread_count,
42 | elapsed / 1e9,
43 | NUM_PINGS / (elapsed / 1e9),
44 | });
45 | }
46 |
47 | fn mainAsyncCallback(
48 | ud: ?*Thread,
49 | _: *xev.Loop,
50 | _: *xev.Completion,
51 | r: xev.Async.WaitError!void,
52 | ) xev.CallbackAction {
53 | _ = r catch unreachable;
54 |
55 | const self = ud.?;
56 | self.worker_async.notify() catch unreachable;
57 | self.main_sent += 1;
58 | self.main_seen += 1;
59 |
60 | return if (self.main_sent >= NUM_PINGS) .disarm else .rearm;
61 | }
62 |
63 | /// The thread state
64 | const Thread = struct {
65 | loop: xev.Loop,
66 | worker_async: xev.Async,
67 | main_async: xev.Async,
68 | worker_sent: usize = 0,
69 | worker_seen: usize = 0,
70 | main_sent: usize = 0,
71 | main_seen: usize = 0,
72 |
73 | pub fn init() !Thread {
74 | return .{
75 | .loop = try xev.Loop.init(.{}),
76 | .worker_async = try xev.Async.init(),
77 | .main_async = try xev.Async.init(),
78 | };
79 | }
80 |
81 | pub fn threadMain(self: *Thread) !void {
82 | // Kick us off
83 | try self.main_async.notify();
84 |
85 | // Start our waiter
86 | var c: xev.Completion = undefined;
87 | self.worker_async.wait(&self.loop, &c, Thread, self, asyncCallback);
88 |
89 | // Run
90 | try self.loop.run(.until_done);
91 | if (self.worker_sent < NUM_PINGS) @panic("FAIL");
92 | }
93 |
94 | fn asyncCallback(
95 | ud: ?*Thread,
96 | _: *xev.Loop,
97 | _: *xev.Completion,
98 | r: xev.Async.WaitError!void,
99 | ) xev.CallbackAction {
100 | _ = r catch unreachable;
101 | const self = ud.?;
102 | self.main_async.notify() catch unreachable;
103 | self.worker_sent += 1;
104 | self.worker_seen += 1;
105 | return if (self.worker_sent >= NUM_PINGS) .disarm else .rearm;
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/src/bench/async2.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const run = @import("async1.zig").run;
3 |
4 | pub const std_options: std.Options = .{
5 | .log_level = .info,
6 | };
7 |
8 | pub fn main() !void {
9 | try run(2);
10 | }
11 |
--------------------------------------------------------------------------------
/src/bench/async4.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const run = @import("async1.zig").run;
3 |
4 | pub const std_options: std.Options = .{
5 | .log_level = .info,
6 | };
7 |
8 | pub fn main() !void {
9 | try run(4);
10 | }
11 |
--------------------------------------------------------------------------------
/src/bench/async8.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const run = @import("async1.zig").run;
3 |
4 | pub const std_options: std.Options = .{
5 | .log_level = .info,
6 | };
7 |
8 | pub fn main() !void {
9 | try run(8);
10 | }
11 |
--------------------------------------------------------------------------------
/src/bench/async_pummel_1.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 | const Allocator = std.mem.Allocator;
4 | const Instant = std.time.Instant;
5 | const xev = @import("xev");
6 | //const xev = @import("xev").Dynamic;
7 |
8 | pub const std_options: std.Options = .{
9 | .log_level = .info,
10 | };
11 |
12 | // Tune-ables
13 | pub const NUM_PINGS = 1000 * 1000;
14 |
15 | pub fn main() !void {
16 | try run(1);
17 | }
18 |
19 | pub fn run(comptime thread_count: comptime_int) !void {
20 | var thread_pool = xev.ThreadPool.init(.{});
21 | defer thread_pool.deinit();
22 | defer thread_pool.shutdown();
23 |
24 | if (xev.dynamic) try xev.detect();
25 | var loop = try xev.Loop.init(.{
26 | .entries = std.math.pow(u13, 2, 12),
27 | .thread_pool = &thread_pool,
28 | });
29 | defer loop.deinit();
30 |
31 | // Create our async
32 | notifier = try xev.Async.init();
33 | defer notifier.deinit();
34 |
35 | const userdata: ?*void = null;
36 | var c: xev.Completion = undefined;
37 | notifier.wait(&loop, &c, void, userdata, &asyncCallback);
38 |
39 | // Initialize all our threads
40 | var threads: [thread_count]std.Thread = undefined;
41 | for (&threads) |*thr| {
42 | thr.* = try std.Thread.spawn(.{}, threadMain, .{});
43 | }
44 |
45 | const start_time = try Instant.now();
46 | try loop.run(.until_done);
47 | for (&threads) |thr| thr.join();
48 | const end_time = try Instant.now();
49 |
50 | const elapsed = @as(f64, @floatFromInt(end_time.since(start_time)));
51 | std.log.info("async_pummel_{d}: {d} callbacks in {d:.2} seconds ({d:.2}/sec)", .{
52 | thread_count,
53 | callbacks,
54 | elapsed / 1e9,
55 | @as(f64, @floatFromInt(callbacks)) / (elapsed / 1e9),
56 | });
57 | }
58 |
59 | var callbacks: usize = 0;
60 | var notifier: xev.Async = undefined;
61 | var state: enum { running, stop, stopped } = .running;
62 |
63 | fn asyncCallback(
64 | _: ?*void,
65 | _: *xev.Loop,
66 | _: *xev.Completion,
67 | r: xev.Async.WaitError!void,
68 | ) xev.CallbackAction {
69 | _ = r catch unreachable;
70 |
71 | callbacks += 1;
72 | if (callbacks < NUM_PINGS) return .rearm;
73 |
74 | // We're done
75 | state = .stop;
76 | while (state != .stopped) std.time.sleep(0);
77 | return .disarm;
78 | }
79 |
80 | fn threadMain() !void {
81 | while (state == .running) try notifier.notify();
82 | state = .stopped;
83 | }
84 |
--------------------------------------------------------------------------------
/src/bench/async_pummel_2.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const run = @import("async_pummel_1.zig").run;
3 |
4 | pub const std_options: std.Options = .{
5 | .log_level = .info,
6 | };
7 |
8 | pub fn main() !void {
9 | try run(2);
10 | }
11 |
--------------------------------------------------------------------------------
/src/bench/async_pummel_4.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const run = @import("async_pummel_1.zig").run;
3 |
4 | pub const std_options: std.Options = .{
5 | .log_level = .info,
6 | };
7 |
8 | pub fn main() !void {
9 | try run(4);
10 | }
11 |
--------------------------------------------------------------------------------
/src/bench/async_pummel_8.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const run = @import("async_pummel_1.zig").run;
3 |
4 | pub const std_options: std.Options = .{
5 | .log_level = .info,
6 | };
7 |
8 | pub fn main() !void {
9 | try run(8);
10 | }
11 |
--------------------------------------------------------------------------------
/src/bench/million-timers.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Instant = std.time.Instant;
3 | const xev = @import("xev");
4 | //const xev = @import("xev").Dynamic;
5 |
6 | pub const NUM_TIMERS: usize = 10 * 1000 * 1000;
7 |
8 | pub fn main() !void {
9 | var thread_pool = xev.ThreadPool.init(.{});
10 | defer thread_pool.deinit();
11 | defer thread_pool.shutdown();
12 |
13 | if (xev.dynamic) try xev.detect();
14 | var loop = try xev.Loop.init(.{
15 | .entries = std.math.pow(u13, 2, 12),
16 | .thread_pool = &thread_pool,
17 | });
18 | defer loop.deinit();
19 |
20 | const GPA = std.heap.GeneralPurposeAllocator(.{});
21 | var gpa: GPA = .{};
22 | defer _ = gpa.deinit();
23 | const alloc = gpa.allocator();
24 |
25 | var cs = try alloc.alloc(xev.Completion, NUM_TIMERS);
26 | defer alloc.free(cs);
27 |
28 | const before_all = try Instant.now();
29 | var i: usize = 0;
30 | var timeout: u64 = 1;
31 | while (i < NUM_TIMERS) : (i += 1) {
32 | if (i % 1000 == 0) timeout += 1;
33 | const timer = try xev.Timer.init();
34 | timer.run(&loop, &cs[i], timeout, void, null, timerCallback);
35 | }
36 |
37 | const before_run = try Instant.now();
38 | try loop.run(.until_done);
39 | const after_run = try Instant.now();
40 | const after_all = try Instant.now();
41 |
42 | std.log.info("{d:.2} seconds total", .{@as(f64, @floatFromInt(after_all.since(before_all))) / 1e9});
43 | std.log.info("{d:.2} seconds init", .{@as(f64, @floatFromInt(before_run.since(before_all))) / 1e9});
44 | std.log.info("{d:.2} seconds dispatch", .{@as(f64, @floatFromInt(after_run.since(before_run))) / 1e9});
45 | std.log.info("{d:.2} seconds cleanup", .{@as(f64, @floatFromInt(after_all.since(after_run))) / 1e9});
46 | }
47 |
48 | pub const std_options: std.Options = .{
49 | .log_level = .info,
50 | };
51 |
52 | var timer_callback_count: usize = 0;
53 |
54 | fn timerCallback(
55 | _: ?*void,
56 | _: *xev.Loop,
57 | _: *xev.Completion,
58 | result: xev.Timer.RunError!void,
59 | ) xev.CallbackAction {
60 | _ = result catch unreachable;
61 | timer_callback_count += 1;
62 | return .disarm;
63 | }
64 |
--------------------------------------------------------------------------------
/src/bench/ping-pongs.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 | const Allocator = std.mem.Allocator;
4 | const Instant = std.time.Instant;
5 | const xev = @import("xev");
6 | //const xev = @import("xev").Dynamic;
7 |
8 | pub const std_options: std.Options = .{
9 | .log_level = .info,
10 | };
11 |
12 | pub fn main() !void {
13 | var thread_pool = xev.ThreadPool.init(.{});
14 | defer thread_pool.deinit();
15 | defer thread_pool.shutdown();
16 |
17 | if (xev.dynamic) try xev.detect();
18 | var loop = try xev.Loop.init(.{
19 | .entries = std.math.pow(u13, 2, 12),
20 | .thread_pool = &thread_pool,
21 | });
22 | defer loop.deinit();
23 |
24 | const GPA = std.heap.GeneralPurposeAllocator(.{});
25 | var gpa: GPA = .{};
26 | defer _ = gpa.deinit();
27 | const alloc = gpa.allocator();
28 |
29 | var server_loop = try xev.Loop.init(.{
30 | .entries = std.math.pow(u13, 2, 12),
31 | .thread_pool = &thread_pool,
32 | });
33 | defer server_loop.deinit();
34 |
35 | var server = try Server.init(alloc, &server_loop);
36 | defer server.deinit();
37 | try server.start();
38 |
39 | // Start our echo server
40 | const server_thr = try std.Thread.spawn(.{}, Server.threadMain, .{&server});
41 |
42 | // Start our client
43 | var client_loop = try xev.Loop.init(.{
44 | .entries = std.math.pow(u13, 2, 12),
45 | .thread_pool = &thread_pool,
46 | });
47 | defer client_loop.deinit();
48 |
49 | var client = try Client.init(alloc, &client_loop);
50 | defer client.deinit();
51 | try client.start();
52 |
53 | const start_time = try Instant.now();
54 | try client_loop.run(.until_done);
55 | server_thr.join();
56 | const end_time = try Instant.now();
57 |
58 | const elapsed = @as(f64, @floatFromInt(end_time.since(start_time)));
59 | std.log.info("{d:.2} roundtrips/s", .{@as(f64, @floatFromInt(client.pongs)) / (elapsed / 1e9)});
60 | std.log.info("{d:.2} seconds total", .{elapsed / 1e9});
61 | }
62 |
63 | /// Memory pools for things that need stable pointers
64 | const BufferPool = std.heap.MemoryPool([4096]u8);
65 | const CompletionPool = std.heap.MemoryPool(xev.Completion);
66 | const TCPPool = std.heap.MemoryPool(xev.TCP);
67 |
68 | /// The client state
69 | const Client = struct {
70 | loop: *xev.Loop,
71 | completion_pool: CompletionPool,
72 | read_buf: [1024]u8,
73 | pongs: u64,
74 | state: usize = 0,
75 | stop: bool = false,
76 |
77 | pub const PING = "PING\n";
78 |
79 | pub fn init(alloc: Allocator, loop: *xev.Loop) !Client {
80 | return .{
81 | .loop = loop,
82 | .completion_pool = CompletionPool.init(alloc),
83 | .read_buf = undefined,
84 | .pongs = 0,
85 | .state = 0,
86 | .stop = false,
87 | };
88 | }
89 |
90 | pub fn deinit(self: *Client) void {
91 | self.completion_pool.deinit();
92 | }
93 |
94 | /// Must be called with stable self pointer.
95 | pub fn start(self: *Client) !void {
96 | const addr = try std.net.Address.parseIp4("127.0.0.1", 3131);
97 | const socket = try xev.TCP.init(addr);
98 |
99 | const c = try self.completion_pool.create();
100 | socket.connect(self.loop, c, addr, Client, self, connectCallback);
101 | }
102 |
103 | fn connectCallback(
104 | self_: ?*Client,
105 | l: *xev.Loop,
106 | c: *xev.Completion,
107 | socket: xev.TCP,
108 | r: xev.ConnectError!void,
109 | ) xev.CallbackAction {
110 | _ = r catch unreachable;
111 |
112 | const self = self_.?;
113 |
114 | // Send message
115 | socket.write(l, c, .{ .slice = PING[0..PING.len] }, Client, self, writeCallback);
116 |
117 | // Read
118 | const c_read = self.completion_pool.create() catch unreachable;
119 | socket.read(l, c_read, .{ .slice = &self.read_buf }, Client, self, readCallback);
120 | return .disarm;
121 | }
122 |
123 | fn writeCallback(
124 | self_: ?*Client,
125 | l: *xev.Loop,
126 | c: *xev.Completion,
127 | s: xev.TCP,
128 | b: xev.WriteBuffer,
129 | r: xev.WriteError!usize,
130 | ) xev.CallbackAction {
131 | _ = r catch unreachable;
132 | _ = l;
133 | _ = s;
134 | _ = b;
135 |
136 | // Put back the completion.
137 | self_.?.completion_pool.destroy(c);
138 | return .disarm;
139 | }
140 |
141 | fn readCallback(
142 | self_: ?*Client,
143 | l: *xev.Loop,
144 | c: *xev.Completion,
145 | socket: xev.TCP,
146 | buf: xev.ReadBuffer,
147 | r: xev.ReadError!usize,
148 | ) xev.CallbackAction {
149 | const self = self_.?;
150 | const n = r catch unreachable;
151 | const data = buf.slice[0..n];
152 |
153 | // Count the number of pings in our message
154 | var i: usize = 0;
155 | while (i < n) : (i += 1) {
156 | assert(data[i] == PING[self.state]);
157 | self.state = (self.state + 1) % (PING.len);
158 | if (self.state == 0) {
159 | self.pongs += 1;
160 |
161 | // If we're done then exit
162 | if (self.pongs > 500_000) {
163 | socket.shutdown(l, c, Client, self, shutdownCallback);
164 | return .disarm;
165 | }
166 |
167 | // Send another ping
168 | const c_ping = self.completion_pool.create() catch unreachable;
169 | socket.write(l, c_ping, .{ .slice = PING[0..PING.len] }, Client, self, writeCallback);
170 | }
171 | }
172 |
173 | // Read again
174 | return .rearm;
175 | }
176 |
177 | fn shutdownCallback(
178 | self_: ?*Client,
179 | l: *xev.Loop,
180 | c: *xev.Completion,
181 | socket: xev.TCP,
182 | r: xev.ShutdownError!void,
183 | ) xev.CallbackAction {
184 | _ = r catch {};
185 |
186 | const self = self_.?;
187 | socket.close(l, c, Client, self, closeCallback);
188 | return .disarm;
189 | }
190 |
191 | fn closeCallback(
192 | self_: ?*Client,
193 | l: *xev.Loop,
194 | c: *xev.Completion,
195 | socket: xev.TCP,
196 | r: xev.CloseError!void,
197 | ) xev.CallbackAction {
198 | _ = l;
199 | _ = socket;
200 | _ = r catch unreachable;
201 |
202 | const self = self_.?;
203 | self.stop = true;
204 | self.completion_pool.destroy(c);
205 | return .disarm;
206 | }
207 | };
208 |
209 | /// The server state
210 | const Server = struct {
211 | loop: *xev.Loop,
212 | buffer_pool: BufferPool,
213 | completion_pool: CompletionPool,
214 | socket_pool: TCPPool,
215 | stop: bool,
216 |
217 | pub fn init(alloc: Allocator, loop: *xev.Loop) !Server {
218 | return .{
219 | .loop = loop,
220 | .buffer_pool = BufferPool.init(alloc),
221 | .completion_pool = CompletionPool.init(alloc),
222 | .socket_pool = TCPPool.init(alloc),
223 | .stop = false,
224 | };
225 | }
226 |
227 | pub fn deinit(self: *Server) void {
228 | self.buffer_pool.deinit();
229 | self.completion_pool.deinit();
230 | self.socket_pool.deinit();
231 | }
232 |
233 | /// Must be called with stable self pointer.
234 | pub fn start(self: *Server) !void {
235 | const addr = try std.net.Address.parseIp4("127.0.0.1", 3131);
236 | var socket = try xev.TCP.init(addr);
237 |
238 | const c = try self.completion_pool.create();
239 | try socket.bind(addr);
240 | try socket.listen(std.os.linux.SOMAXCONN);
241 | socket.accept(self.loop, c, Server, self, acceptCallback);
242 | }
243 |
244 | pub fn threadMain(self: *Server) !void {
245 | try self.loop.run(.until_done);
246 | }
247 |
248 | fn destroyBuf(self: *Server, buf: []const u8) void {
249 | self.buffer_pool.destroy(
250 | @alignCast(
251 | @as(*[4096]u8, @ptrFromInt(@intFromPtr(buf.ptr))),
252 | ),
253 | );
254 | }
255 |
256 | fn acceptCallback(
257 | self_: ?*Server,
258 | l: *xev.Loop,
259 | c: *xev.Completion,
260 | r: xev.AcceptError!xev.TCP,
261 | ) xev.CallbackAction {
262 | const self = self_.?;
263 |
264 | // Create our socket
265 | const socket = self.socket_pool.create() catch unreachable;
266 | socket.* = r catch unreachable;
267 |
268 | // Start reading -- we can reuse c here because its done.
269 | const buf = self.buffer_pool.create() catch unreachable;
270 | socket.read(l, c, .{ .slice = buf }, Server, self, readCallback);
271 | return .disarm;
272 | }
273 |
274 | fn readCallback(
275 | self_: ?*Server,
276 | loop: *xev.Loop,
277 | c: *xev.Completion,
278 | socket: xev.TCP,
279 | buf: xev.ReadBuffer,
280 | r: xev.ReadError!usize,
281 | ) xev.CallbackAction {
282 | const self = self_.?;
283 | const n = r catch |err| switch (err) {
284 | error.EOF => {
285 | self.destroyBuf(buf.slice);
286 | socket.shutdown(loop, c, Server, self, shutdownCallback);
287 | return .disarm;
288 | },
289 |
290 | else => {
291 | self.destroyBuf(buf.slice);
292 | self.completion_pool.destroy(c);
293 | std.log.warn("server read unexpected err={}", .{err});
294 | return .disarm;
295 | },
296 | };
297 |
298 | // Echo it back
299 | const c_echo = self.completion_pool.create() catch unreachable;
300 | const buf_write = self.buffer_pool.create() catch unreachable;
301 | @memcpy(buf_write, buf.slice[0..n]);
302 | socket.write(loop, c_echo, .{ .slice = buf_write[0..n] }, Server, self, writeCallback);
303 |
304 | // Read again
305 | return .rearm;
306 | }
307 |
308 | fn writeCallback(
309 | self_: ?*Server,
310 | l: *xev.Loop,
311 | c: *xev.Completion,
312 | s: xev.TCP,
313 | buf: xev.WriteBuffer,
314 | r: xev.WriteError!usize,
315 | ) xev.CallbackAction {
316 | _ = l;
317 | _ = s;
318 | _ = r catch unreachable;
319 |
320 | // We do nothing for write, just put back objects into the pool.
321 | const self = self_.?;
322 | self.completion_pool.destroy(c);
323 | self.buffer_pool.destroy(
324 | @alignCast(
325 | @as(*[4096]u8, @ptrFromInt(@intFromPtr(buf.slice.ptr))),
326 | ),
327 | );
328 | return .disarm;
329 | }
330 |
331 | fn shutdownCallback(
332 | self_: ?*Server,
333 | l: *xev.Loop,
334 | c: *xev.Completion,
335 | s: xev.TCP,
336 | r: xev.ShutdownError!void,
337 | ) xev.CallbackAction {
338 | _ = r catch {};
339 |
340 | const self = self_.?;
341 | s.close(l, c, Server, self, closeCallback);
342 | return .disarm;
343 | }
344 |
345 | fn closeCallback(
346 | self_: ?*Server,
347 | l: *xev.Loop,
348 | c: *xev.Completion,
349 | socket: xev.TCP,
350 | r: xev.CloseError!void,
351 | ) xev.CallbackAction {
352 | _ = l;
353 | _ = r catch unreachable;
354 | _ = socket;
355 |
356 | const self = self_.?;
357 | self.stop = true;
358 | self.completion_pool.destroy(c);
359 | return .disarm;
360 | }
361 | };
362 |
--------------------------------------------------------------------------------
/src/bench/ping-udp1.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 | const assert = std.debug.assert;
4 | const Allocator = std.mem.Allocator;
5 | const Instant = std.time.Instant;
6 | const xev = @import("xev");
7 | //const xev = @import("xev").Dynamic;
8 |
9 | pub const std_options: std.Options = .{
10 | .log_level = .info,
11 | };
12 |
13 | pub fn main() !void {
14 | try run(1);
15 | }
16 |
17 | pub fn run(comptime count: comptime_int) !void {
18 | var thread_pool = xev.ThreadPool.init(.{});
19 | defer thread_pool.deinit();
20 | defer thread_pool.shutdown();
21 |
22 | if (xev.dynamic) try xev.detect();
23 | var loop = try xev.Loop.init(.{
24 | .entries = std.math.pow(u13, 2, 12),
25 | .thread_pool = &thread_pool,
26 | });
27 | defer loop.deinit();
28 |
29 | const addr = try std.net.Address.parseIp4("127.0.0.1", 3131);
30 |
31 | var pingers: [count]Pinger = undefined;
32 | for (&pingers) |*p| {
33 | p.* = try Pinger.init(addr);
34 | try p.start(&loop);
35 | }
36 |
37 | const start_time = try Instant.now();
38 | try loop.run(.until_done);
39 | const end_time = try Instant.now();
40 |
41 | const total: usize = total: {
42 | var total: usize = 0;
43 | for (&pingers) |p| total += p.pongs;
44 | break :total total;
45 | };
46 |
47 | const elapsed = @as(f64, @floatFromInt(end_time.since(start_time)));
48 | std.log.info("ping_pongs: {d} pingers, ~{d:.0} roundtrips/s", .{
49 | count,
50 | @as(f64, @floatFromInt(total)) / (elapsed / 1e9),
51 | });
52 | }
53 |
54 | const Pinger = struct {
55 | udp: xev.UDP,
56 | addr: std.net.Address,
57 | state: usize = 0,
58 | pongs: u64 = 0,
59 | read_buf: [1024]u8 = undefined,
60 | c_read: xev.Completion = undefined,
61 | c_write: xev.Completion = undefined,
62 | state_read: xev.UDP.State = undefined,
63 | state_write: xev.UDP.State = undefined,
64 | op_count: u8 = 0,
65 |
66 | pub const PING = "PING\n";
67 |
68 | pub fn init(addr: std.net.Address) !Pinger {
69 | return .{
70 | .udp = try xev.UDP.init(addr),
71 | .state = 0,
72 | .pongs = 0,
73 | .addr = addr,
74 | };
75 | }
76 |
77 | pub fn start(self: *Pinger, loop: *xev.Loop) !void {
78 | try self.udp.bind(self.addr);
79 |
80 | self.udp.read(
81 | loop,
82 | &self.c_read,
83 | &self.state_read,
84 | .{ .slice = &self.read_buf },
85 | Pinger,
86 | self,
87 | Pinger.readCallback,
88 | );
89 |
90 | self.write(loop);
91 | }
92 |
93 | pub fn write(self: *Pinger, loop: *xev.Loop) void {
94 | self.udp.write(
95 | loop,
96 | &self.c_write,
97 | &self.state_write,
98 | self.addr,
99 | .{ .slice = PING[0..PING.len] },
100 | Pinger,
101 | self,
102 | writeCallback,
103 | );
104 | }
105 |
106 | pub fn readCallback(
107 | self_: ?*Pinger,
108 | loop: *xev.Loop,
109 | c: *xev.Completion,
110 | _: *xev.UDP.State,
111 | _: std.net.Address,
112 | socket: xev.UDP,
113 | buf: xev.ReadBuffer,
114 | r: xev.ReadError!usize,
115 | ) xev.CallbackAction {
116 | _ = c;
117 | _ = socket;
118 | const self = self_.?;
119 | const n = r catch unreachable;
120 | const data = buf.slice[0..n];
121 |
122 | var i: usize = 0;
123 | while (i < n) : (i += 1) {
124 | assert(data[i] == PING[self.state]);
125 | self.state = (self.state + 1) % (PING.len);
126 | if (self.state == 0) {
127 | self.pongs += 1;
128 |
129 | // If we're done then exit
130 | if (self.pongs > 500_000) {
131 | self.udp.close(loop, &self.c_read, Pinger, self, closeCallback);
132 | return .disarm;
133 | }
134 |
135 | self.op_count += 1;
136 | if (self.op_count == 2) {
137 | self.op_count = 0;
138 | // Send another ping
139 | self.write(loop);
140 | }
141 | }
142 | }
143 |
144 | return .rearm;
145 | }
146 |
147 | pub fn writeCallback(
148 | self_: ?*Pinger,
149 | loop: *xev.Loop,
150 | _: *xev.Completion,
151 | _: *xev.UDP.State,
152 | _: xev.UDP,
153 | _: xev.WriteBuffer,
154 | r: xev.WriteError!usize,
155 | ) xev.CallbackAction {
156 | const self = self_.?;
157 |
158 | self.op_count += 1;
159 | if (self.op_count == 2) {
160 | self.op_count = 0;
161 | // Send another ping
162 | self.write(loop);
163 | }
164 |
165 | _ = r catch unreachable;
166 | return .disarm;
167 | }
168 |
169 | pub fn closeCallback(
170 | _: ?*Pinger,
171 | _: *xev.Loop,
172 | _: *xev.Completion,
173 | _: xev.UDP,
174 | r: xev.CloseError!void,
175 | ) xev.CallbackAction {
176 | _ = r catch unreachable;
177 | return .disarm;
178 | }
179 | };
180 |
--------------------------------------------------------------------------------
/src/bench/udp_pummel_1v1.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 | const Allocator = std.mem.Allocator;
4 | const Instant = std.time.Instant;
5 | const xev = @import("xev");
6 | //const xev = @import("xev").Dynamic;
7 |
8 | const EXPECTED = "RANG TANG DING DONG I AM THE JAPANESE SANDMAN";
9 |
10 | /// This is a global var decremented for the test without any locks. That's
11 | /// how the original is written and that's how we're going to do it.
12 | var packet_counter: usize = 1e6;
13 | var send_cb_called: usize = 0;
14 | var recv_cb_called: usize = 0;
15 |
16 | pub const std_options: std.Options = .{
17 | .log_level = .info,
18 | };
19 |
20 | pub fn main() !void {
21 | try run(1, 1);
22 | }
23 |
24 | pub fn run(comptime n_senders: comptime_int, comptime n_receivers: comptime_int) !void {
25 | const base_port = 12345;
26 |
27 | var thread_pool = xev.ThreadPool.init(.{});
28 | defer thread_pool.deinit();
29 | defer thread_pool.shutdown();
30 |
31 | if (xev.dynamic) try xev.detect();
32 | var loop = try xev.Loop.init(.{
33 | .entries = std.math.pow(u13, 2, 12),
34 | .thread_pool = &thread_pool,
35 | });
36 | defer loop.deinit();
37 |
38 | var receivers: [n_receivers]Receiver = undefined;
39 | for (&receivers, 0..) |*r, i| {
40 | const addr = try std.net.Address.parseIp4("127.0.0.1", @as(u16, @intCast(base_port + i)));
41 | r.* = .{ .udp = try xev.UDP.init(addr) };
42 | try r.udp.bind(addr);
43 | r.udp.read(
44 | &loop,
45 | &r.c_recv,
46 | &r.udp_state,
47 | .{ .slice = &r.recv_buf },
48 | Receiver,
49 | r,
50 | Receiver.readCallback,
51 | );
52 | }
53 |
54 | var senders: [n_senders]Sender = undefined;
55 | for (&senders, 0..) |*s, i| {
56 | const addr = try std.net.Address.parseIp4(
57 | "127.0.0.1",
58 | @as(u16, @intCast(base_port + (i % n_receivers))),
59 | );
60 | s.* = .{ .udp = try xev.UDP.init(addr) };
61 | s.udp.write(
62 | &loop,
63 | &s.c_send,
64 | &s.udp_state,
65 | addr,
66 | .{ .slice = EXPECTED },
67 | Sender,
68 | s,
69 | Sender.writeCallback,
70 | );
71 | }
72 |
73 | const start_time = try Instant.now();
74 | try loop.run(.until_done);
75 | const end_time = try Instant.now();
76 |
77 | const elapsed = @as(f64, @floatFromInt(end_time.since(start_time)));
78 | std.log.info("udp_pummel_{d}v{d}: {d:.0}f/s received, {d:.0}f/s sent, {d} received, {d} sent in {d:.1} seconds", .{
79 | n_senders,
80 | n_receivers,
81 | @as(f64, @floatFromInt(recv_cb_called)) / (elapsed / std.time.ns_per_s),
82 | @as(f64, @floatFromInt(send_cb_called)) / (elapsed / std.time.ns_per_s),
83 | recv_cb_called,
84 | send_cb_called,
85 | elapsed / std.time.ns_per_s,
86 | });
87 | }
88 |
89 | const Sender = struct {
90 | udp: xev.UDP,
91 | udp_state: xev.UDP.State = undefined,
92 | c_send: xev.Completion = undefined,
93 |
94 | fn writeCallback(
95 | _: ?*Sender,
96 | l: *xev.Loop,
97 | _: *xev.Completion,
98 | _: *xev.UDP.State,
99 | _: xev.UDP,
100 | _: xev.WriteBuffer,
101 | r: xev.WriteError!usize,
102 | ) xev.CallbackAction {
103 | _ = r catch unreachable;
104 |
105 | if (packet_counter == 0) {
106 | l.stop();
107 | return .disarm;
108 | }
109 |
110 | packet_counter -|= 1;
111 | send_cb_called += 1;
112 |
113 | return .rearm;
114 | }
115 | };
116 |
117 | const Receiver = struct {
118 | udp: xev.UDP,
119 | udp_state: xev.UDP.State = undefined,
120 | c_recv: xev.Completion = undefined,
121 | recv_buf: [65536]u8 = undefined,
122 |
123 | fn readCallback(
124 | _: ?*Receiver,
125 | _: *xev.Loop,
126 | _: *xev.Completion,
127 | _: *xev.UDP.State,
128 | _: std.net.Address,
129 | _: xev.UDP,
130 | b: xev.ReadBuffer,
131 | r: xev.ReadError!usize,
132 | ) xev.CallbackAction {
133 | const n = r catch |err| {
134 | switch (err) {
135 | error.EOF => {},
136 | else => std.log.warn("err={}", .{err}),
137 | }
138 |
139 | return .disarm;
140 | };
141 |
142 | if (!std.mem.eql(u8, b.slice[0..n], EXPECTED)) {
143 | @panic("Unexpected data.");
144 | }
145 |
146 | recv_cb_called += 1;
147 | return .rearm;
148 | }
149 | };
150 |
--------------------------------------------------------------------------------
/src/c_api.zig:
--------------------------------------------------------------------------------
1 | // This file contains the C bindings that are exported when building
2 | // the system libraries.
3 | //
4 | // WHERE IS THE DOCUMENTATION? Note that all the documentation for the C
5 | // interface is in the man pages. The header file xev.h purposely has no
6 | // documentation so that its concise and easy to see the list of exported
7 | // functions.
8 |
9 | const std = @import("std");
10 | const builtin = @import("builtin");
11 | const assert = std.debug.assert;
12 | const xev = @import("main.zig");
13 |
14 | export fn xev_loop_init(loop: *xev.Loop) c_int {
15 | // TODO: overflow
16 | loop.* = xev.Loop.init(.{}) catch |err| return errorCode(err);
17 | return 0;
18 | }
19 |
20 | export fn xev_loop_deinit(loop: *xev.Loop) void {
21 | loop.deinit();
22 | }
23 |
24 | export fn xev_loop_run(loop: *xev.Loop, mode: xev.RunMode) c_int {
25 | loop.run(mode) catch |err| return errorCode(err);
26 | return 0;
27 | }
28 |
29 | export fn xev_loop_now(loop: *xev.Loop) i64 {
30 | return loop.now();
31 | }
32 |
33 | export fn xev_loop_update_now(loop: *xev.Loop) void {
34 | loop.update_now();
35 | }
36 |
37 | export fn xev_completion_zero(c: *xev.Completion) void {
38 | c.* = .{};
39 | }
40 |
41 | export fn xev_completion_state(c: *xev.Completion) xev.CompletionState {
42 | return c.state();
43 | }
44 |
45 | //-------------------------------------------------------------------
46 | // ThreadPool
47 |
48 | export fn xev_threadpool_config_init(cfg: *xev.ThreadPool.Config) void {
49 | cfg.* = .{};
50 | }
51 |
52 | export fn xev_threadpool_config_set_stack_size(
53 | cfg: *xev.ThreadPool.Config,
54 | v: u32,
55 | ) void {
56 | cfg.stack_size = v;
57 | }
58 |
59 | export fn xev_threadpool_config_set_max_threads(
60 | cfg: *xev.ThreadPool.Config,
61 | v: u32,
62 | ) void {
63 | cfg.max_threads = v;
64 | }
65 |
66 | export fn xev_threadpool_init(
67 | threadpool: *xev.ThreadPool,
68 | cfg_: ?*xev.ThreadPool.Config,
69 | ) c_int {
70 | const cfg: xev.ThreadPool.Config = if (cfg_) |v| v.* else .{};
71 | threadpool.* = xev.ThreadPool.init(cfg);
72 | return 0;
73 | }
74 |
75 | export fn xev_threadpool_deinit(threadpool: *xev.ThreadPool) void {
76 | threadpool.deinit();
77 | }
78 |
79 | export fn xev_threadpool_shutdown(threadpool: *xev.ThreadPool) void {
80 | threadpool.shutdown();
81 | }
82 |
83 | export fn xev_threadpool_schedule(
84 | pool: *xev.ThreadPool,
85 | batch: *xev.ThreadPool.Batch,
86 | ) void {
87 | pool.schedule(batch.*);
88 | }
89 |
90 | export fn xev_threadpool_task_init(
91 | t: *xev.ThreadPool.Task,
92 | cb: *const fn (*xev.ThreadPool.Task) callconv(.C) void,
93 | ) void {
94 | const extern_t = @as(*Task, @ptrCast(@alignCast(t)));
95 | extern_t.c_callback = cb;
96 |
97 | t.* = .{
98 | .callback = (struct {
99 | fn callback(inner_t: *xev.ThreadPool.Task) void {
100 | const outer_t: *Task = @alignCast(@fieldParentPtr(
101 | "data",
102 | @as(*Task.Data, @ptrCast(inner_t)),
103 | ));
104 | outer_t.c_callback(inner_t);
105 | }
106 | }).callback,
107 | };
108 | }
109 |
110 | export fn xev_threadpool_batch_init(b: *xev.ThreadPool.Batch) void {
111 | b.* = .{};
112 | }
113 |
114 | export fn xev_threadpool_batch_push_task(
115 | b: *xev.ThreadPool.Batch,
116 | t: *xev.ThreadPool.Task,
117 | ) void {
118 | b.push(xev.ThreadPool.Batch.from(t));
119 | }
120 |
121 | export fn xev_threadpool_batch_push_batch(
122 | b: *xev.ThreadPool.Batch,
123 | other: *xev.ThreadPool.Batch,
124 | ) void {
125 | b.push(other.*);
126 | }
127 |
128 | //-------------------------------------------------------------------
129 | // Timers
130 |
131 | export fn xev_timer_init(v: *xev.Timer) c_int {
132 | v.* = xev.Timer.init() catch |err| return errorCode(err);
133 | return 0;
134 | }
135 |
136 | export fn xev_timer_deinit(v: *xev.Timer) void {
137 | v.deinit();
138 | }
139 |
140 | export fn xev_timer_run(
141 | v: *xev.Timer,
142 | loop: *xev.Loop,
143 | c: *xev.Completion,
144 | next_ms: u64,
145 | userdata: ?*anyopaque,
146 | cb: *const fn (
147 | *xev.Loop,
148 | *xev.Completion,
149 | c_int,
150 | ?*anyopaque,
151 | ) callconv(.C) xev.CallbackAction,
152 | ) void {
153 | const Callback = @typeInfo(@TypeOf(cb)).pointer.child;
154 | const extern_c = @as(*Completion, @ptrCast(@alignCast(c)));
155 | extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb));
156 |
157 | v.run(loop, c, next_ms, anyopaque, userdata, (struct {
158 | fn callback(
159 | ud: ?*anyopaque,
160 | cb_loop: *xev.Loop,
161 | cb_c: *xev.Completion,
162 | r: xev.Timer.RunError!void,
163 | ) xev.CallbackAction {
164 | const cb_extern_c = @as(*Completion, @ptrCast(cb_c));
165 | const cb_c_callback = @as(
166 | *const Callback,
167 | @ptrCast(@alignCast(cb_extern_c.c_callback)),
168 | );
169 | return @call(.auto, cb_c_callback, .{
170 | cb_loop,
171 | cb_c,
172 | if (r) |_| 0 else |err| errorCode(err),
173 | ud,
174 | });
175 | }
176 | }).callback);
177 | }
178 |
179 | export fn xev_timer_reset(
180 | v: *xev.Timer,
181 | loop: *xev.Loop,
182 | c: *xev.Completion,
183 | c_cancel: *xev.Completion,
184 | next_ms: u64,
185 | userdata: ?*anyopaque,
186 | cb: *const fn (
187 | *xev.Loop,
188 | *xev.Completion,
189 | c_int,
190 | ?*anyopaque,
191 | ) callconv(.C) xev.CallbackAction,
192 | ) void {
193 | const Callback = @typeInfo(@TypeOf(cb)).pointer.child;
194 | const extern_c = @as(*Completion, @ptrCast(@alignCast(c)));
195 | extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb));
196 |
197 | v.reset(loop, c, c_cancel, next_ms, anyopaque, userdata, (struct {
198 | fn callback(
199 | ud: ?*anyopaque,
200 | cb_loop: *xev.Loop,
201 | cb_c: *xev.Completion,
202 | r: xev.Timer.RunError!void,
203 | ) xev.CallbackAction {
204 | const cb_extern_c = @as(*Completion, @ptrCast(cb_c));
205 | const cb_c_callback = @as(
206 | *const Callback,
207 | @ptrCast(@alignCast(cb_extern_c.c_callback)),
208 | );
209 | return @call(.auto, cb_c_callback, .{
210 | cb_loop,
211 | cb_c,
212 | if (r) |_| 0 else |err| errorCode(err),
213 | ud,
214 | });
215 | }
216 | }).callback);
217 | }
218 |
219 | export fn xev_timer_cancel(
220 | v: *xev.Timer,
221 | loop: *xev.Loop,
222 | c_timer: *xev.Completion,
223 | c_cancel: *xev.Completion,
224 | userdata: ?*anyopaque,
225 | cb: *const fn (
226 | *xev.Loop,
227 | *xev.Completion,
228 | c_int,
229 | ?*anyopaque,
230 | ) callconv(.C) xev.CallbackAction,
231 | ) void {
232 | const Callback = @typeInfo(@TypeOf(cb)).pointer.child;
233 | const extern_c = @as(*Completion, @ptrCast(@alignCast(c_cancel)));
234 | extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb));
235 |
236 | v.cancel(loop, c_timer, c_cancel, anyopaque, userdata, (struct {
237 | fn callback(
238 | ud: ?*anyopaque,
239 | cb_loop: *xev.Loop,
240 | cb_c: *xev.Completion,
241 | r: xev.Timer.CancelError!void,
242 | ) xev.CallbackAction {
243 | const cb_extern_c = @as(*Completion, @ptrCast(cb_c));
244 | const cb_c_callback = @as(
245 | *const Callback,
246 | @ptrCast(@alignCast(cb_extern_c.c_callback)),
247 | );
248 | return @call(.auto, cb_c_callback, .{
249 | cb_loop,
250 | cb_c,
251 | if (r) |_| 0 else |err| errorCode(err),
252 | ud,
253 | });
254 | }
255 | }).callback);
256 | }
257 |
258 | //-------------------------------------------------------------------
259 | // Async
260 |
261 | export fn xev_async_init(v: *xev.Async) c_int {
262 | v.* = xev.Async.init() catch |err| return errorCode(err);
263 | return 0;
264 | }
265 |
266 | export fn xev_async_deinit(v: *xev.Async) void {
267 | v.deinit();
268 | }
269 |
270 | export fn xev_async_notify(v: *xev.Async) c_int {
271 | v.notify() catch |err| return errorCode(err);
272 | return 0;
273 | }
274 |
275 | export fn xev_async_wait(
276 | v: *xev.Async,
277 | loop: *xev.Loop,
278 | c: *xev.Completion,
279 | userdata: ?*anyopaque,
280 | cb: *const fn (
281 | *xev.Loop,
282 | *xev.Completion,
283 | c_int,
284 | ?*anyopaque,
285 | ) callconv(.C) xev.CallbackAction,
286 | ) void {
287 | const Callback = @typeInfo(@TypeOf(cb)).pointer.child;
288 | const extern_c = @as(*Completion, @ptrCast(@alignCast(c)));
289 | extern_c.c_callback = @as(*const anyopaque, @ptrCast(cb));
290 |
291 | v.wait(loop, c, anyopaque, userdata, (struct {
292 | fn callback(
293 | ud: ?*anyopaque,
294 | cb_loop: *xev.Loop,
295 | cb_c: *xev.Completion,
296 | r: xev.Async.WaitError!void,
297 | ) xev.CallbackAction {
298 | const cb_extern_c = @as(*Completion, @ptrCast(cb_c));
299 | const cb_c_callback = @as(
300 | *const Callback,
301 | @ptrCast(@alignCast(cb_extern_c.c_callback)),
302 | );
303 | return @call(.auto, cb_c_callback, .{
304 | cb_loop,
305 | cb_c,
306 | if (r) |_| 0 else |err| errorCode(err),
307 | ud,
308 | });
309 | }
310 | }).callback);
311 | }
312 |
313 | //-------------------------------------------------------------------
314 | // Sync with xev.h
315 |
316 | /// Since we can't pass the callback at comptime with C, we have to
317 | /// have an additional field on completions to store our callback pointer.
318 | /// We just tack it onto the end of the memory chunk that C programs allocate
319 | /// for completions.
320 | const Completion = extern struct {
321 | const Data = [@sizeOf(xev.Completion)]u8;
322 | data: Data,
323 | c_callback: *const anyopaque,
324 | };
325 |
326 | const Task = extern struct {
327 | const Data = [@sizeOf(xev.ThreadPool.Task)]u8;
328 | data: Data,
329 | c_callback: *const fn (*xev.ThreadPool.Task) callconv(.C) void,
330 | };
331 |
332 | /// Returns the unique error code for an error.
333 | fn errorCode(err: anyerror) c_int {
334 | // TODO(mitchellh): This is a bad idea because its not stable across
335 | // code changes. For now we just document that error codes are not
336 | // stable but that is not useful at all!
337 | return @intFromError(err);
338 | }
339 |
340 | test "c-api sizes" {
341 | // This tests the sizes that are defined in the C API. We must ensure
342 | // that our main structure sizes never exceed these so that the C ABI
343 | // is maintained.
344 | //
345 | // THE MAGIC NUMBERS ARE KEPT IN SYNC WITH "include/xev.h"
346 | const testing = std.testing;
347 | try testing.expect(@sizeOf(xev.Loop) <= 512);
348 | try testing.expect(@sizeOf(Completion) <= 320);
349 | try testing.expect(@sizeOf(xev.Async) <= 256);
350 | try testing.expect(@sizeOf(xev.Timer) <= 256);
351 | try testing.expectEqual(@as(usize, 48), @sizeOf(xev.ThreadPool));
352 | try testing.expectEqual(@as(usize, 24), @sizeOf(xev.ThreadPool.Batch));
353 | try testing.expectEqual(@as(usize, 24), @sizeOf(Task));
354 | try testing.expectEqual(@as(usize, 8), @sizeOf(xev.ThreadPool.Config));
355 | }
356 |
--------------------------------------------------------------------------------
/src/darwin.zig:
--------------------------------------------------------------------------------
1 | //! These are copied from Zig's stdlib as of Zig 0.14 because
2 | //! they are no longer exported. This is probably going to be
3 | //! fixed in the future: https://github.com/ziglang/zig/pull/21218
4 | const std = @import("std");
5 |
6 | pub const MACH_SEND_MSG = 0x00000001;
7 | pub const MACH_RCV_MSG = 0x00000002;
8 |
9 | pub const MACH_SEND_TIMEOUT = 0x00000010;
10 | pub const MACH_SEND_OVERRIDE = 0x00000020;
11 | pub const MACH_SEND_INTERRUPT = 0x00000040;
12 | pub const MACH_SEND_NOTIFY = 0x00000080;
13 | pub const MACH_SEND_ALWAYS = 0x00010000;
14 | pub const MACH_SEND_FILTER_NONFATAL = 0x00010000;
15 | pub const MACH_SEND_TRAILER = 0x00020000;
16 | pub const MACH_SEND_NOIMPORTANCE = 0x00040000;
17 | pub const MACH_SEND_NODENAP = MACH_SEND_NOIMPORTANCE;
18 | pub const MACH_SEND_IMPORTANCE = 0x00080000;
19 | pub const MACH_SEND_SYNC_OVERRIDE = 0x00100000;
20 | pub const MACH_SEND_PROPAGATE_QOS = 0x00200000;
21 | pub const MACH_SEND_SYNC_USE_THRPRI = MACH_SEND_PROPAGATE_QOS;
22 | pub const MACH_SEND_KERNEL = 0x00400000;
23 | pub const MACH_SEND_SYNC_BOOTSTRAP_CHECKIN = 0x00800000;
24 |
25 | pub const MACH_RCV_TIMEOUT = 0x00000100;
26 | pub const MACH_RCV_NOTIFY = 0x00000000;
27 | pub const MACH_RCV_INTERRUPT = 0x00000400;
28 | pub const MACH_RCV_VOUCHER = 0x00000800;
29 | pub const MACH_RCV_OVERWRITE = 0x00000000;
30 | pub const MACH_RCV_GUARDED_DESC = 0x00001000;
31 | pub const MACH_RCV_SYNC_WAIT = 0x00004000;
32 | pub const MACH_RCV_SYNC_PEEK = 0x00008000;
33 |
34 | pub const MACH_PORT_NULL: mach_port_t = 0;
35 | pub const MACH_MSG_TIMEOUT_NONE: mach_msg_timeout_t = 0;
36 |
37 | pub const natural_t = c_uint;
38 | pub const integer_t = c_int;
39 | pub const mach_port_t = c_uint;
40 | pub const mach_port_name_t = natural_t;
41 | pub const mach_msg_bits_t = c_uint;
42 | pub const mach_msg_id_t = integer_t;
43 | pub const mach_msg_type_number_t = natural_t;
44 | pub const mach_msg_type_name_t = c_uint;
45 | pub const mach_msg_option_t = integer_t;
46 | pub const mach_msg_size_t = natural_t;
47 | pub const mach_msg_timeout_t = natural_t;
48 | pub const mach_msg_return_t = std.c.kern_return_t;
49 |
50 | pub const mach_msg_header_t = extern struct {
51 | msgh_bits: mach_msg_bits_t,
52 | msgh_size: mach_msg_size_t,
53 | msgh_remote_port: mach_port_t,
54 | msgh_local_port: mach_port_t,
55 | msgh_voucher_port: mach_port_name_t,
56 | msgh_id: mach_msg_id_t,
57 | };
58 |
59 | pub extern "c" fn mach_msg(
60 | msg: ?*mach_msg_header_t,
61 | option: mach_msg_option_t,
62 | send_size: mach_msg_size_t,
63 | rcv_size: mach_msg_size_t,
64 | rcv_name: mach_port_name_t,
65 | timeout: mach_msg_timeout_t,
66 | notify: mach_port_name_t,
67 | ) std.c.kern_return_t;
68 |
69 | pub fn getKernError(err: std.c.kern_return_t) KernE {
70 | return @as(KernE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err))))));
71 | }
72 |
73 | /// Kernel return values
74 | pub const KernE = enum(u32) {
75 | SUCCESS = 0,
76 |
77 | /// Specified address is not currently valid
78 | INVALID_ADDRESS = 1,
79 |
80 | /// Specified memory is valid, but does not permit the
81 | /// required forms of access.
82 | PROTECTION_FAILURE = 2,
83 |
84 | /// The address range specified is already in use, or
85 | /// no address range of the size specified could be
86 | /// found.
87 | NO_SPACE = 3,
88 |
89 | /// The function requested was not applicable to this
90 | /// type of argument, or an argument is invalid
91 | INVALID_ARGUMENT = 4,
92 |
93 | /// The function could not be performed. A catch-all.
94 | FAILURE = 5,
95 |
96 | /// A system resource could not be allocated to fulfill
97 | /// this request. This failure may not be permanent.
98 | RESOURCE_SHORTAGE = 6,
99 |
100 | /// The task in question does not hold receive rights
101 | /// for the port argument.
102 | NOT_RECEIVER = 7,
103 |
104 | /// Bogus access restriction.
105 | NO_ACCESS = 8,
106 |
107 | /// During a page fault, the target address refers to a
108 | /// memory object that has been destroyed. This
109 | /// failure is permanent.
110 | MEMORY_FAILURE = 9,
111 |
112 | /// During a page fault, the memory object indicated
113 | /// that the data could not be returned. This failure
114 | /// may be temporary; future attempts to access this
115 | /// same data may succeed, as defined by the memory
116 | /// object.
117 | MEMORY_ERROR = 10,
118 |
119 | /// The receive right is already a member of the portset.
120 | ALREADY_IN_SET = 11,
121 |
122 | /// The receive right is not a member of a port set.
123 | NOT_IN_SET = 12,
124 |
125 | /// The name already denotes a right in the task.
126 | NAME_EXISTS = 13,
127 |
128 | /// The operation was aborted. Ipc code will
129 | /// catch this and reflect it as a message error.
130 | ABORTED = 14,
131 |
132 | /// The name doesn't denote a right in the task.
133 | INVALID_NAME = 15,
134 |
135 | /// Target task isn't an active task.
136 | INVALID_TASK = 16,
137 |
138 | /// The name denotes a right, but not an appropriate right.
139 | INVALID_RIGHT = 17,
140 |
141 | /// A blatant range error.
142 | INVALID_VALUE = 18,
143 |
144 | /// Operation would overflow limit on user-references.
145 | UREFS_OVERFLOW = 19,
146 |
147 | /// The supplied (port) capability is improper.
148 | INVALID_CAPABILITY = 20,
149 |
150 | /// The task already has send or receive rights
151 | /// for the port under another name.
152 | RIGHT_EXISTS = 21,
153 |
154 | /// Target host isn't actually a host.
155 | INVALID_HOST = 22,
156 |
157 | /// An attempt was made to supply "precious" data
158 | /// for memory that is already present in a
159 | /// memory object.
160 | MEMORY_PRESENT = 23,
161 |
162 | /// A page was requested of a memory manager via
163 | /// memory_object_data_request for an object using
164 | /// a MEMORY_OBJECT_COPY_CALL strategy, with the
165 | /// VM_PROT_WANTS_COPY flag being used to specify
166 | /// that the page desired is for a copy of the
167 | /// object, and the memory manager has detected
168 | /// the page was pushed into a copy of the object
169 | /// while the kernel was walking the shadow chain
170 | /// from the copy to the object. This error code
171 | /// is delivered via memory_object_data_error
172 | /// and is handled by the kernel (it forces the
173 | /// kernel to restart the fault). It will not be
174 | /// seen by users.
175 | MEMORY_DATA_MOVED = 24,
176 |
177 | /// A strategic copy was attempted of an object
178 | /// upon which a quicker copy is now possible.
179 | /// The caller should retry the copy using
180 | /// vm_object_copy_quickly. This error code
181 | /// is seen only by the kernel.
182 | MEMORY_RESTART_COPY = 25,
183 |
184 | /// An argument applied to assert processor set privilege
185 | /// was not a processor set control port.
186 | INVALID_PROCESSOR_SET = 26,
187 |
188 | /// The specified scheduling attributes exceed the thread's
189 | /// limits.
190 | POLICY_LIMIT = 27,
191 |
192 | /// The specified scheduling policy is not currently
193 | /// enabled for the processor set.
194 | INVALID_POLICY = 28,
195 |
196 | /// The external memory manager failed to initialize the
197 | /// memory object.
198 | INVALID_OBJECT = 29,
199 |
200 | /// A thread is attempting to wait for an event for which
201 | /// there is already a waiting thread.
202 | ALREADY_WAITING = 30,
203 |
204 | /// An attempt was made to destroy the default processor
205 | /// set.
206 | DEFAULT_SET = 31,
207 |
208 | /// An attempt was made to fetch an exception port that is
209 | /// protected, or to abort a thread while processing a
210 | /// protected exception.
211 | EXCEPTION_PROTECTED = 32,
212 |
213 | /// A ledger was required but not supplied.
214 | INVALID_LEDGER = 33,
215 |
216 | /// The port was not a memory cache control port.
217 | INVALID_MEMORY_CONTROL = 34,
218 |
219 | /// An argument supplied to assert security privilege
220 | /// was not a host security port.
221 | INVALID_SECURITY = 35,
222 |
223 | /// thread_depress_abort was called on a thread which
224 | /// was not currently depressed.
225 | NOT_DEPRESSED = 36,
226 |
227 | /// Object has been terminated and is no longer available
228 | TERMINATED = 37,
229 |
230 | /// Lock set has been destroyed and is no longer available.
231 | LOCK_SET_DESTROYED = 38,
232 |
233 | /// The thread holding the lock terminated before releasing
234 | /// the lock
235 | LOCK_UNSTABLE = 39,
236 |
237 | /// The lock is already owned by another thread
238 | LOCK_OWNED = 40,
239 |
240 | /// The lock is already owned by the calling thread
241 | LOCK_OWNED_SELF = 41,
242 |
243 | /// Semaphore has been destroyed and is no longer available.
244 | SEMAPHORE_DESTROYED = 42,
245 |
246 | /// Return from RPC indicating the target server was
247 | /// terminated before it successfully replied
248 | RPC_SERVER_TERMINATED = 43,
249 |
250 | /// Terminate an orphaned activation.
251 | RPC_TERMINATE_ORPHAN = 44,
252 |
253 | /// Allow an orphaned activation to continue executing.
254 | RPC_CONTINUE_ORPHAN = 45,
255 |
256 | /// Empty thread activation (No thread linked to it)
257 | NOT_SUPPORTED = 46,
258 |
259 | /// Remote node down or inaccessible.
260 | NODE_DOWN = 47,
261 |
262 | /// A signalled thread was not actually waiting.
263 | NOT_WAITING = 48,
264 |
265 | /// Some thread-oriented operation (semaphore_wait) timed out
266 | OPERATION_TIMED_OUT = 49,
267 |
268 | /// During a page fault, indicates that the page was rejected
269 | /// as a result of a signature check.
270 | CODESIGN_ERROR = 50,
271 |
272 | /// The requested property cannot be changed at this time.
273 | POLICY_STATIC = 51,
274 |
275 | /// The provided buffer is of insufficient size for the requested data.
276 | INSUFFICIENT_BUFFER_SIZE = 52,
277 |
278 | /// Denied by security policy
279 | DENIED = 53,
280 |
281 | /// The KC on which the function is operating is missing
282 | MISSING_KC = 54,
283 |
284 | /// The KC on which the function is operating is invalid
285 | INVALID_KC = 55,
286 |
287 | /// A search or query operation did not return a result
288 | NOT_FOUND = 56,
289 |
290 | _,
291 | };
292 |
293 | pub fn getMachMsgError(err: mach_msg_return_t) MachMsgE {
294 | return @as(MachMsgE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err))))));
295 | }
296 |
297 | /// Mach msg return values
298 | pub const MachMsgE = enum(u32) {
299 | SUCCESS = 0x00000000,
300 |
301 | /// Thread is waiting to send. (Internal use only.)
302 | SEND_IN_PROGRESS = 0x10000001,
303 | /// Bogus in-line data.
304 | SEND_INVALID_DATA = 0x10000002,
305 | /// Bogus destination port.
306 | SEND_INVALID_DEST = 0x10000003,
307 | /// Message not sent before timeout expired.
308 | SEND_TIMED_OUT = 0x10000004,
309 | /// Bogus voucher port.
310 | SEND_INVALID_VOUCHER = 0x10000005,
311 | /// Software interrupt.
312 | SEND_INTERRUPTED = 0x10000007,
313 | /// Data doesn't contain a complete message.
314 | SEND_MSG_TOO_SMALL = 0x10000008,
315 | /// Bogus reply port.
316 | SEND_INVALID_REPLY = 0x10000009,
317 | /// Bogus port rights in the message body.
318 | SEND_INVALID_RIGHT = 0x1000000a,
319 | /// Bogus notify port argument.
320 | SEND_INVALID_NOTIFY = 0x1000000b,
321 | /// Invalid out-of-line memory pointer.
322 | SEND_INVALID_MEMORY = 0x1000000c,
323 | /// No message buffer is available.
324 | SEND_NO_BUFFER = 0x1000000d,
325 | /// Send is too large for port
326 | SEND_TOO_LARGE = 0x1000000e,
327 | /// Invalid msg-type specification.
328 | SEND_INVALID_TYPE = 0x1000000f,
329 | /// A field in the header had a bad value.
330 | SEND_INVALID_HEADER = 0x10000010,
331 | /// The trailer to be sent does not match kernel format.
332 | SEND_INVALID_TRAILER = 0x10000011,
333 | /// The sending thread context did not match the context on the dest port
334 | SEND_INVALID_CONTEXT = 0x10000012,
335 | /// compatibility: no longer a returned error
336 | SEND_INVALID_RT_OOL_SIZE = 0x10000015,
337 | /// The destination port doesn't accept ports in body
338 | SEND_NO_GRANT_DEST = 0x10000016,
339 | /// Message send was rejected by message filter
340 | SEND_MSG_FILTERED = 0x10000017,
341 |
342 | /// Thread is waiting for receive. (Internal use only.)
343 | RCV_IN_PROGRESS = 0x10004001,
344 | /// Bogus name for receive port/port-set.
345 | RCV_INVALID_NAME = 0x10004002,
346 | /// Didn't get a message within the timeout value.
347 | RCV_TIMED_OUT = 0x10004003,
348 | /// Message buffer is not large enough for inline data.
349 | RCV_TOO_LARGE = 0x10004004,
350 | /// Software interrupt.
351 | RCV_INTERRUPTED = 0x10004005,
352 | /// compatibility: no longer a returned error
353 | RCV_PORT_CHANGED = 0x10004006,
354 | /// Bogus notify port argument.
355 | RCV_INVALID_NOTIFY = 0x10004007,
356 | /// Bogus message buffer for inline data.
357 | RCV_INVALID_DATA = 0x10004008,
358 | /// Port/set was sent away/died during receive.
359 | RCV_PORT_DIED = 0x10004009,
360 | /// compatibility: no longer a returned error
361 | RCV_IN_SET = 0x1000400a,
362 | /// Error receiving message header. See special bits.
363 | RCV_HEADER_ERROR = 0x1000400b,
364 | /// Error receiving message body. See special bits.
365 | RCV_BODY_ERROR = 0x1000400c,
366 | /// Invalid msg-type specification in scatter list.
367 | RCV_INVALID_TYPE = 0x1000400d,
368 | /// Out-of-line overwrite region is not large enough
369 | RCV_SCATTER_SMALL = 0x1000400e,
370 | /// trailer type or number of trailer elements not supported
371 | RCV_INVALID_TRAILER = 0x1000400f,
372 | /// Waiting for receive with timeout. (Internal use only.)
373 | RCV_IN_PROGRESS_TIMED = 0x10004011,
374 | /// invalid reply port used in a STRICT_REPLY message
375 | RCV_INVALID_REPLY = 0x10004012,
376 | };
377 |
--------------------------------------------------------------------------------
/src/debug.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | inline fn indent(depth: usize, writer: anytype) !void {
4 | for (0..depth) |_| try writer.writeByte(' ');
5 | }
6 |
7 | pub fn describe(comptime T: type, writer: anytype, depth: usize) !void {
8 | const type_info = @typeInfo(T);
9 | switch (type_info) {
10 | .Type,
11 | .Void,
12 | .Bool,
13 | .NoReturn,
14 | .Int,
15 | .Float,
16 | .Pointer,
17 | .Array,
18 | .ComptimeFloat,
19 | .ComptimeInt,
20 | .Undefined,
21 | .Null,
22 | .Optional,
23 | .ErrorUnion,
24 | .ErrorSet,
25 | .Enum,
26 | .Fn,
27 | .Opaque,
28 | .Frame,
29 | .AnyFrame,
30 | .Vector,
31 | .EnumLiteral,
32 | => {
33 | try writer.print("{s} ({d} bytes)", .{ @typeName(T), @sizeOf(T) });
34 | },
35 | .Union => |s| {
36 | try writer.print("{s} ({d} bytes) {{\n", .{ @typeName(T), @sizeOf(T) });
37 | inline for (s.fields) |f| {
38 | try indent(depth + 4, writer);
39 | try writer.print("{s}: ", .{f.name});
40 | try describe(f.type, writer, depth + 4);
41 | try writer.writeByte('\n');
42 | }
43 | try indent(depth, writer);
44 | try writer.writeByte('}');
45 | },
46 | .Struct => |s| {
47 | try writer.print("{s} ({d} bytes) {{\n", .{ @typeName(T), @sizeOf(T) });
48 | inline for (s.fields) |f| {
49 | try indent(depth + 4, writer);
50 | try writer.print("{s}: ", .{f.name});
51 | try describe(f.type, writer, depth + 4);
52 | try writer.writeByte('\n');
53 | }
54 | try indent(depth, writer);
55 | try writer.writeByte('}');
56 | },
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/dynamic.zig:
--------------------------------------------------------------------------------
1 | const dynamicpkg = @This();
2 | const std = @import("std");
3 | const builtin = @import("builtin");
4 | const posix = std.posix;
5 | const main = @import("main.zig");
6 | const AllBackend = main.Backend;
7 | const looppkg = @import("loop.zig");
8 |
9 | /// Creates the Xev API based on a set of backend types that allows
10 | /// for dynamic choice between the various candidate backends (assuming
11 | /// they're available). For example, on Linux, this allows a consumer to
12 | /// choose between io_uring and epoll, or for automatic fallback to epoll
13 | /// to occur.
14 | ///
15 | /// If only one backend is given, the returned type is equivalent to
16 | /// the static API with no runtime conditional logic, so there is no downside
17 | /// to always using the dynamic choice variant if you wish to support
18 | /// dynamic choice.
19 | ///
20 | /// The goal of this API is to match the static Xev() (in main.zig)
21 | /// API as closely as possible. It can't be exact since this is an abstraction
22 | /// and therefore must be limited to the least common denominator of all
23 | /// backends.
24 | ///
25 | /// Since the static Xev API exposes types that can be initialized on their
26 | /// own (i.e. xev.Async.init()), this API does the same. Since we need to
27 | /// know the backend to use, this uses a global variable. The backend to use
28 | /// defaults to the first candidate backend and DOES NOT test that it is
29 | /// available. The API user should call `detect()` to set the backend to
30 | /// the first available backend.
31 | ///
32 | /// Additionally, a caller can manually set the backend to use, but this
33 | /// must be done before initializing any other types.
34 | pub fn Xev(comptime bes: []const AllBackend) type {
35 | if (bes.len == 0) @compileError("no backends provided");
36 |
37 | // Ensure that if we only have one candidate that we have no
38 | // overhead to use the dynamic API.
39 | if (bes.len == 1) return bes[0].Api();
40 |
41 | return struct {
42 | const Dynamic = @This();
43 |
44 | /// This is a flag that can be used to detect that you're using
45 | /// a dynamic API and not the static API via `hasDecl`.
46 | pub const dynamic = true;
47 |
48 | pub const candidates = bes;
49 |
50 | /// Backend becomes the subset of the full xev.Backend that
51 | /// is available to this dynamic API.
52 | pub const Backend = EnumSubset(AllBackend, bes);
53 |
54 | /// Forward some global types so that users can replace
55 | /// @import("xev") with the dynamic package and everything
56 | /// works.
57 | pub const ThreadPool = main.ThreadPool;
58 |
59 | /// The shared structures
60 | pub const Options = looppkg.Options;
61 | pub const RunMode = looppkg.RunMode;
62 | pub const CallbackAction = looppkg.CallbackAction;
63 | pub const CompletionState = looppkg.CompletionState;
64 | pub const Completion = DynamicCompletion(Dynamic);
65 | pub const PollEvent = DynamicPollEvent(Dynamic);
66 | pub const ReadBuffer = DynamicReadBuffer(Dynamic);
67 | pub const WriteBuffer = DynamicWriteBuffer(Dynamic);
68 | pub const WriteQueue = DynamicWriteQueue(Dynamic);
69 | pub const WriteRequest = Dynamic.Union(&.{"WriteRequest"});
70 |
71 | /// Error types
72 | pub const AcceptError = Dynamic.ErrorSet(&.{"AcceptError"});
73 | pub const CancelError = Dynamic.ErrorSet(&.{"CancelError"});
74 | pub const CloseError = Dynamic.ErrorSet(&.{"CloseError"});
75 | pub const ConnectError = Dynamic.ErrorSet(&.{"ConnectError"});
76 | pub const PollError = Dynamic.ErrorSet(&.{"PollError"});
77 | pub const ShutdownError = Dynamic.ErrorSet(&.{"ShutdownError"});
78 | pub const WriteError = Dynamic.ErrorSet(&.{"WriteError"});
79 | pub const ReadError = Dynamic.ErrorSet(&.{"ReadError"});
80 |
81 | /// Core types
82 | pub const Async = @import("watcher/async.zig").Async(Dynamic);
83 | pub const File = @import("watcher/file.zig").File(Dynamic);
84 | pub const Process = @import("watcher/process.zig").Process(Dynamic);
85 | pub const Stream = @import("watcher/stream.zig").GenericStream(Dynamic);
86 | pub const Timer = @import("watcher/timer.zig").Timer(Dynamic);
87 | pub const TCP = @import("watcher/tcp.zig").TCP(Dynamic);
88 | pub const UDP = @import("watcher/udp.zig").UDP(Dynamic);
89 |
90 | /// The backend that is in use.
91 | pub var backend: Backend = subset(bes[bes.len - 1]);
92 |
93 | /// Detect the preferred backend based on the system and set it
94 | /// for use. "Preferred" is the first available (API exists) candidate
95 | /// backend in the list.
96 | pub fn detect() error{NoAvailableBackends}!void {
97 | inline for (bes) |be| {
98 | if (be.Api().available()) {
99 | backend = subset(be);
100 | return;
101 | }
102 | }
103 |
104 | return error.NoAvailableBackends;
105 | }
106 |
107 | /// Manually set the backend to use, but if the backend is not
108 | /// available, this will not change the backend in use.
109 | pub fn prefer(be: AllBackend) bool {
110 | inline for (bes) |candidate| {
111 | if (candidate == be) {
112 | const api = candidate.Api();
113 | if (!api.available()) return false;
114 | backend = subset(candidate);
115 | return true;
116 | }
117 | }
118 |
119 | return false;
120 | }
121 |
122 | pub const Loop = struct {
123 | backend: Loop.Union,
124 |
125 | pub const Union = Dynamic.Union(&.{"Loop"});
126 |
127 | pub fn init(opts: Options) !Loop {
128 | return .{ .backend = switch (backend) {
129 | inline else => |tag| backend: {
130 | const api = (comptime superset(tag)).Api();
131 | break :backend @unionInit(
132 | Loop.Union,
133 | @tagName(tag),
134 | try api.Loop.init(opts),
135 | );
136 | },
137 | } };
138 | }
139 |
140 | pub fn deinit(self: *Loop) void {
141 | switch (backend) {
142 | inline else => |tag| @field(
143 | self.backend,
144 | @tagName(tag),
145 | ).deinit(),
146 | }
147 | }
148 |
149 | pub fn stop(self: *Loop) void {
150 | switch (backend) {
151 | inline else => |tag| @field(
152 | self.backend,
153 | @tagName(tag),
154 | ).stop(),
155 | }
156 | }
157 |
158 | pub fn stopped(self: *Loop) bool {
159 | return switch (backend) {
160 | inline else => |tag| @field(
161 | self.backend,
162 | @tagName(tag),
163 | ).stopped(),
164 | };
165 | }
166 |
167 | pub fn run(self: *Loop, mode: RunMode) !void {
168 | switch (backend) {
169 | inline else => |tag| try @field(
170 | self.backend,
171 | @tagName(tag),
172 | ).run(mode),
173 | }
174 | }
175 | };
176 |
177 | /// Helpers to convert between the subset/superset of backends.
178 | pub fn subset(comptime be: AllBackend) Backend {
179 | return @enumFromInt(@intFromEnum(be));
180 | }
181 |
182 | pub fn superset(comptime be: Backend) AllBackend {
183 | return @enumFromInt(@intFromEnum(be));
184 | }
185 |
186 | pub fn Union(comptime field: []const []const u8) type {
187 | return dynamicpkg.Union(bes, field, false);
188 | }
189 |
190 | pub fn TaggedUnion(comptime field: []const []const u8) type {
191 | return dynamicpkg.Union(bes, field, true);
192 | }
193 |
194 | pub fn ErrorSet(comptime field: []const []const u8) type {
195 | return dynamicpkg.ErrorSet(bes, field);
196 | }
197 |
198 | test {
199 | @import("std").testing.refAllDecls(@This());
200 | }
201 |
202 | test "completion is zero-able" {
203 | const c: Completion = .{};
204 | _ = c;
205 | }
206 |
207 | test "detect" {
208 | const testing = std.testing;
209 | try detect();
210 | inline for (bes) |be| {
211 | if (@intFromEnum(be) == @intFromEnum(backend)) {
212 | try testing.expect(be.Api().available());
213 | break;
214 | }
215 | } else try testing.expect(false);
216 | }
217 |
218 | test "prefer" {
219 | const testing = std.testing;
220 | try testing.expect(prefer(bes[0]));
221 | inline for (bes) |be| {
222 | if (@intFromEnum(be) == @intFromEnum(backend)) {
223 | try testing.expect(be.Api().available());
224 | break;
225 | }
226 | } else try testing.expect(false);
227 | }
228 |
229 | test "loop basics" {
230 | try detect();
231 | var l = try Loop.init(.{});
232 | defer l.deinit();
233 | try l.run(.until_done);
234 | l.stop();
235 | try std.testing.expect(l.stopped());
236 | }
237 | };
238 | }
239 |
240 | fn DynamicCompletion(comptime dynamic: type) type {
241 | return struct {
242 | const Self = @This();
243 |
244 | // Completions, unlike almost any other dynamic type in this
245 | // file, are tagged unions. This adds a minimal overhead to
246 | // the completion but is necessary to ensure that we have
247 | // zero-initialized the correct type for where it matters
248 | // (timers).
249 | pub const Union = dynamic.TaggedUnion(&.{"Completion"});
250 |
251 | value: Self.Union = @unionInit(
252 | Self.Union,
253 | @tagName(dynamic.candidates[dynamic.candidates.len - 1]),
254 | .{},
255 | ),
256 |
257 | pub fn init() Self {
258 | return .{ .value = switch (dynamic.backend) {
259 | inline else => |tag| value: {
260 | const api = (comptime dynamic.superset(tag)).Api();
261 | break :value @unionInit(
262 | Self.Union,
263 | @tagName(tag),
264 | api.Completion.init(),
265 | );
266 | },
267 | } };
268 | }
269 |
270 | pub fn state(self: *Self) dynamic.CompletionState {
271 | return switch (self.value) {
272 | inline else => |*v| v.state(),
273 | };
274 | }
275 |
276 | /// This ensures that the tag is currently set for this
277 | /// completion. If it is not, it will be set to the given
278 | /// tag with a zero-initialized value.
279 | ///
280 | /// This lets users zero-intialize the completion and then
281 | /// call any watcher API on it without having to worry about
282 | /// the correct detected backend tag being set.
283 | pub fn ensureTag(self: *Self, comptime tag: dynamic.Backend) void {
284 | if (self.value == tag) return;
285 | self.value = @unionInit(
286 | Self.Union,
287 | @tagName(tag),
288 | .{},
289 | );
290 | }
291 | };
292 | }
293 |
294 | fn DynamicReadBuffer(comptime dynamic: type) type {
295 | // Our read buffer supports a least common denominator of
296 | // read targets available by all backends. The direct backend
297 | // may support more read targets but if you need that you should
298 | // use the backend directly and not through the dynamic API.
299 | return union(enum) {
300 | const Self = @This();
301 |
302 | slice: []u8,
303 | array: [32]u8,
304 |
305 | /// Convert a backend-specific read buffer to this.
306 | pub fn fromBackend(
307 | comptime be: dynamic.Backend,
308 | buf: dynamic.superset(be).Api().ReadBuffer,
309 | ) Self {
310 | return switch (buf) {
311 | inline else => |data, tag| @unionInit(
312 | Self,
313 | @tagName(tag),
314 | data,
315 | ),
316 | };
317 | }
318 |
319 | /// Convert this read buffer to the backend-specific
320 | /// buffer.
321 | pub fn toBackend(
322 | self: Self,
323 | comptime be: dynamic.Backend,
324 | ) dynamic.superset(be).Api().ReadBuffer {
325 | return switch (self) {
326 | inline else => |data, tag| @unionInit(
327 | dynamic.superset(be).Api().ReadBuffer,
328 | @tagName(tag),
329 | data,
330 | ),
331 | };
332 | }
333 | };
334 | }
335 |
336 | fn DynamicWriteBuffer(comptime dynamic: type) type {
337 | // Our write buffer supports a least common denominator of
338 | // write targets available by all backends. The direct backend
339 | // may support more write targets but if you need that you should
340 | // use the backend directly and not through the dynamic API.
341 | return union(enum) {
342 | const Self = @This();
343 |
344 | slice: []const u8,
345 | array: struct { array: [32]u8, len: usize },
346 |
347 | /// Convert a backend-specific write buffer to this.
348 | pub fn fromBackend(
349 | comptime be: dynamic.Backend,
350 | buf: dynamic.superset(be).Api().WriteBuffer,
351 | ) Self {
352 | return switch (buf) {
353 | .slice => |v| .{ .slice = v },
354 | .array => |v| .{ .array = .{ .array = v.array, .len = v.len } },
355 | };
356 | }
357 |
358 | /// Convert this write buffer to the backend-specific
359 | /// buffer.
360 | pub fn toBackend(
361 | self: Self,
362 | comptime be: dynamic.Backend,
363 | ) dynamic.superset(be).Api().WriteBuffer {
364 | return switch (self) {
365 | .slice => |v| .{ .slice = v },
366 | .array => |v| .{ .array = .{ .array = v.array, .len = v.len } },
367 | };
368 | }
369 | };
370 | }
371 |
372 | fn DynamicWriteQueue(comptime xev: type) type {
373 | return struct {
374 | const Self = @This();
375 | pub const Union = xev.TaggedUnion(&.{"WriteQueue"});
376 |
377 | value: Self.Union = @unionInit(
378 | Self.Union,
379 | @tagName(xev.candidates[xev.candidates.len - 1]),
380 | .{},
381 | ),
382 |
383 | pub fn ensureTag(self: *Self, comptime tag: xev.Backend) void {
384 | if (self.value == tag) return;
385 | self.value = @unionInit(
386 | Self.Union,
387 | @tagName(tag),
388 | .{},
389 | );
390 | }
391 | };
392 | }
393 |
394 | fn DynamicPollEvent(comptime xev: type) type {
395 | return enum {
396 | const Self = @This();
397 |
398 | read,
399 |
400 | pub fn fromBackend(
401 | comptime tag: xev.Backend,
402 | event: xev.superset(tag).Api().PollEvent,
403 | ) Self {
404 | return switch (event) {
405 | inline else => |event_tag| @field(
406 | Self,
407 | @tagName(event_tag),
408 | ),
409 | };
410 | }
411 |
412 | pub fn toBackend(
413 | self: Self,
414 | comptime tag: xev.Backend,
415 | ) xev.superset(tag).Api().PollEvent {
416 | return switch (self) {
417 | inline else => |event_tag| @field(
418 | (comptime xev.superset(tag)).Api().PollEvent,
419 | @tagName(event_tag),
420 | ),
421 | };
422 | }
423 | };
424 | }
425 |
426 | /// Create an exhaustive enum that is a subset of another enum.
427 | /// Preserves the same backing type and integer values for the
428 | /// subset making it easy to convert between the two.
429 | fn EnumSubset(comptime T: type, comptime values: []const T) type {
430 | var fields: [values.len]std.builtin.Type.EnumField = undefined;
431 | for (values, 0..) |value, i| fields[i] = .{
432 | .name = @tagName(value),
433 | .value = @intFromEnum(value),
434 | };
435 |
436 | return @Type(.{ .@"enum" = .{
437 | .tag_type = @typeInfo(T).@"enum".tag_type,
438 | .fields = &fields,
439 | .decls = &.{},
440 | .is_exhaustive = true,
441 | } });
442 | }
443 |
444 | /// Creates a union type that can hold the implementation of a given
445 | /// backend by common field name. Example, for Async: Union(bes, "Async")
446 | /// produces:
447 | ///
448 | /// union {
449 | /// io_uring: IO_Uring.Async,
450 | /// epoll: Epoll.Async,
451 | /// ...
452 | /// }
453 | ///
454 | /// The union is untagged to save an extra bit of memory per
455 | /// instance since we have the active backend from the outer struct.
456 | fn Union(
457 | comptime bes: []const AllBackend,
458 | comptime field: []const []const u8,
459 | comptime tagged: bool,
460 | ) type {
461 | // Keep track of the largest field size, see the conditional
462 | // below using this variable for more info.
463 | var largest: usize = 0;
464 |
465 | var fields: [bes.len + 1]std.builtin.Type.UnionField = undefined;
466 | for (bes, 0..) |be, i| {
467 | var T: type = be.Api();
468 | for (field) |f| T = @field(T, f);
469 | largest = @max(largest, @sizeOf(T));
470 | fields[i] = .{
471 | .name = @tagName(be),
472 | .type = T,
473 | .alignment = @alignOf(T),
474 | };
475 | }
476 |
477 | // If our union only has zero-sized types, we need to add some
478 | // non-zero sized padding entry. This avoids a Zig 0.13 compiler
479 | // crash when trying to create a zero-sized union using @unionInit
480 | // from a switch of a comptime-generated enum. I wasn't able to
481 | // minimize this. In future Zig versions we can remove this and if
482 | // our examples can build with Dynamic then we're good.
483 | var count: usize = bes.len;
484 | if (largest == 0) {
485 | fields[count] = .{
486 | .name = "_zig_bug_padding",
487 | .type = u8,
488 | .alignment = @alignOf(u8),
489 | };
490 | count += 1;
491 | }
492 |
493 | return @Type(.{
494 | .@"union" = .{
495 | .layout = .auto,
496 | .tag_type = if (tagged) EnumSubset(
497 | AllBackend,
498 | bes,
499 | ) else null,
500 | .fields = fields[0..count],
501 | .decls = &.{},
502 | },
503 | });
504 | }
505 |
506 | /// Create a new error set from a list of error sets within
507 | /// the given backends at the given field name. For example,
508 | /// to merge all xev.Async.WaitErrors:
509 | ///
510 | /// ErrorSet(bes, &.{"Async", "WaitError"});
511 | ///
512 | fn ErrorSet(
513 | comptime bes: []const AllBackend,
514 | comptime field: []const []const u8,
515 | ) type {
516 | var Set: type = error{};
517 | for (bes) |be| {
518 | var NextSet: type = be.Api();
519 | for (field) |f| NextSet = @field(NextSet, f);
520 | Set = Set || NextSet;
521 | }
522 |
523 | return Set;
524 | }
525 |
--------------------------------------------------------------------------------
/src/heap.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 |
4 | /// An intrusive heap implementation backed by a pairing heap[1] implementation.
5 | ///
6 | /// Why? Intrusive data structures require the element type to hold the metadata
7 | /// required for the structure, rather than an additional container structure.
8 | /// There are numerous pros/cons that are documented well by Boost[2]. For Zig,
9 | /// I think the primary benefits are making data structures allocation free
10 | /// (rather, shifting allocation up to the consumer which can choose how they
11 | /// want the memory to be available). There are various costs to this such as
12 | /// the costs of pointer chasing, larger memory overhead, requiring the element
13 | /// type to be aware of its container, etc. But for certain use cases an intrusive
14 | /// data structure can yield much better performance.
15 | ///
16 | /// Usage notes:
17 | /// - The element T is expected to have a field "heap" of type InstrusiveHeapField.
18 | /// See the tests for a full example of how to set this.
19 | /// - You can easily make this a min or max heap by inverting the result of
20 | /// "less" below.
21 | ///
22 | /// [1]: https://en.wikipedia.org/wiki/Pairing_heap
23 | /// [2]: https://www.boost.org/doc/libs/1_64_0/doc/html/intrusive/intrusive_vs_nontrusive.html
24 | pub fn Intrusive(
25 | comptime T: type,
26 | comptime Context: type,
27 | comptime less: *const fn (ctx: Context, a: *T, b: *T) bool,
28 | ) type {
29 | return struct {
30 | const Self = @This();
31 |
32 | root: ?*T = null,
33 | context: Context,
34 |
35 | /// Insert a new element v into the heap. An element v can only
36 | /// be a member of a single heap at any given time. When compiled
37 | /// with runtime-safety, assertions will help verify this property.
38 | pub fn insert(self: *Self, v: *T) void {
39 | self.root = if (self.root) |root| self.meld(v, root) else v;
40 | }
41 |
42 | /// Look at the next minimum value but do not remove it.
43 | pub fn peek(self: *Self) ?*T {
44 | return self.root;
45 | }
46 |
47 | /// Delete the minimum value from the heap and return it.
48 | pub fn deleteMin(self: *Self) ?*T {
49 | const root = self.root orelse return null;
50 | self.root = if (root.heap.child) |child|
51 | self.combine_siblings(child)
52 | else
53 | null;
54 |
55 | // Clear pointers with runtime safety so we can verify on
56 | // insert that values aren't incorrectly being set multiple times.
57 | root.heap = .{};
58 |
59 | return root;
60 | }
61 |
62 | /// Remove the value v from the heap.
63 | pub fn remove(self: *Self, v: *T) void {
64 | // If v doesn't have a previous value, this must be the root
65 | // element. If it is NOT the root element, v can't be in this
66 | // heap and we trigger an assertion failure.
67 | const prev = v.heap.prev orelse {
68 | assert(self.root.? == v);
69 | _ = self.deleteMin();
70 | return;
71 | };
72 |
73 | // Detach "v" from the tree and clean up any links so it
74 | // is as if this node never nexisted. The previous value
75 | // must point to the proper next value and the pointers
76 | // must all be cleaned up.
77 | if (v.heap.next) |next| next.heap.prev = prev;
78 | if (prev.heap.child == v)
79 | prev.heap.child = v.heap.next
80 | else
81 | prev.heap.next = v.heap.next;
82 | v.heap.prev = null;
83 | v.heap.next = null;
84 |
85 | // If we have children, then we need to merge them back in.
86 | const child = v.heap.child orelse return;
87 | v.heap.child = null;
88 | const x = self.combine_siblings(child);
89 | self.root = self.meld(x, self.root.?);
90 | }
91 |
92 | /// Meld (union) two heaps together. This isn't a generalized
93 | /// union. It assumes that a.heap.next is null so this is only
94 | /// meant in specific scenarios in the pairing heap where meld
95 | /// is expected.
96 | ///
97 | /// For example, when melding a new value "v" with an existing
98 | /// root "root", "v" must always be the first param.
99 | fn meld(self: *Self, a: *T, b: *T) *T {
100 | assert(a.heap.next == null);
101 |
102 | if (less(self.context, a, b)) {
103 | // B points back to A
104 | b.heap.prev = a;
105 |
106 | // If B has siblings, then A inherits B's siblings
107 | // and B's immediate sibling must point back to A to
108 | // maintain the doubly linked list.
109 | if (b.heap.next) |b_next| {
110 | a.heap.next = b_next;
111 | b_next.heap.prev = a;
112 | b.heap.next = null;
113 | }
114 |
115 | // If A has a child, then B becomes the leftmost sibling
116 | // of that child.
117 | if (a.heap.child) |a_child| {
118 | b.heap.next = a_child;
119 | a_child.heap.prev = b;
120 | }
121 |
122 | // B becomes the leftmost child of A
123 | a.heap.child = b;
124 |
125 | return a;
126 | }
127 |
128 | // Replace A with B in the tree. Any of B's children
129 | // become siblings of A. A becomes the leftmost child of B.
130 | // A points back to B
131 | b.heap.prev = a.heap.prev;
132 | a.heap.prev = b;
133 | if (b.heap.child) |b_child| {
134 | a.heap.next = b_child;
135 | b_child.heap.prev = a;
136 | }
137 | b.heap.child = a;
138 | return b;
139 | }
140 |
141 | /// Combine the siblings of the leftmost value "left" into a single
142 | /// new rooted with the minimum value.
143 | fn combine_siblings(self: *Self, left: *T) *T {
144 | left.heap.prev = null;
145 |
146 | // Merge pairs right
147 | var root: *T = root: {
148 | var a: *T = left;
149 | while (true) {
150 | var b = a.heap.next orelse break :root a;
151 | a.heap.next = null;
152 | b = self.meld(a, b);
153 | a = b.heap.next orelse break :root b;
154 | }
155 | };
156 |
157 | // Merge pairs left
158 | while (true) {
159 | var b = root.heap.prev orelse return root;
160 | b.heap.next = null;
161 | root = self.meld(b, root);
162 | }
163 | }
164 | };
165 | }
166 |
167 | /// The state that is required for IntrusiveHeap element types. This
168 | /// should be set as the "heap" field in the type T.
169 | pub fn IntrusiveField(comptime T: type) type {
170 | return struct {
171 | child: ?*T = null,
172 | prev: ?*T = null,
173 | next: ?*T = null,
174 | };
175 | }
176 |
177 | test "heap" {
178 | const Elem = struct {
179 | const Self = @This();
180 | value: usize = 0,
181 | heap: IntrusiveField(Self) = .{},
182 | };
183 |
184 | const Heap = Intrusive(Elem, void, (struct {
185 | fn less(ctx: void, a: *Elem, b: *Elem) bool {
186 | _ = ctx;
187 | return a.value < b.value;
188 | }
189 | }).less);
190 |
191 | var a: Elem = .{ .value = 12 };
192 | var b: Elem = .{ .value = 24 };
193 | var c: Elem = .{ .value = 7 };
194 | var d: Elem = .{ .value = 9 };
195 |
196 | var h: Heap = .{ .context = {} };
197 | h.insert(&a);
198 | h.insert(&b);
199 | h.insert(&c);
200 | h.insert(&d);
201 | h.remove(&d);
202 |
203 | const testing = std.testing;
204 | try testing.expect(h.deleteMin().?.value == 7);
205 | try testing.expect(h.deleteMin().?.value == 12);
206 | try testing.expect(h.deleteMin().?.value == 24);
207 | try testing.expect(h.deleteMin() == null);
208 | }
209 |
210 | test "heap remove root" {
211 | const Elem = struct {
212 | const Self = @This();
213 | value: usize = 0,
214 | heap: IntrusiveField(Self) = .{},
215 | };
216 |
217 | const Heap = Intrusive(Elem, void, (struct {
218 | fn less(ctx: void, a: *Elem, b: *Elem) bool {
219 | _ = ctx;
220 | return a.value < b.value;
221 | }
222 | }).less);
223 |
224 | var a: Elem = .{ .value = 12 };
225 | var b: Elem = .{ .value = 24 };
226 |
227 | var h: Heap = .{ .context = {} };
228 | h.insert(&a);
229 | h.insert(&b);
230 | h.remove(&a);
231 |
232 | const testing = std.testing;
233 | try testing.expect(h.deleteMin().?.value == 24);
234 | try testing.expect(h.deleteMin() == null);
235 | }
236 |
237 | test "heap remove with children" {
238 | const Elem = struct {
239 | const Self = @This();
240 | value: usize = 0,
241 | heap: IntrusiveField(Self) = .{},
242 | };
243 |
244 | const Heap = Intrusive(Elem, void, (struct {
245 | fn less(ctx: void, a: *Elem, b: *Elem) bool {
246 | _ = ctx;
247 | return a.value < b.value;
248 | }
249 | }).less);
250 |
251 | var a: Elem = .{ .value = 36 };
252 | var b: Elem = .{ .value = 24 };
253 | var c: Elem = .{ .value = 12 };
254 |
255 | var h: Heap = .{ .context = {} };
256 | h.insert(&a);
257 | h.insert(&b);
258 | h.insert(&c);
259 | h.remove(&b);
260 |
261 | const testing = std.testing;
262 | try testing.expect(h.deleteMin().?.value == 12);
263 | try testing.expect(h.deleteMin().?.value == 36);
264 | try testing.expect(h.deleteMin() == null);
265 | }
266 |
267 | test "heap equal values" {
268 | const testing = std.testing;
269 |
270 | const Elem = struct {
271 | const Self = @This();
272 | value: usize = 0,
273 | heap: IntrusiveField(Self) = .{},
274 | };
275 |
276 | const Heap = Intrusive(Elem, void, (struct {
277 | fn less(ctx: void, a: *Elem, b: *Elem) bool {
278 | _ = ctx;
279 | return a.value < b.value;
280 | }
281 | }).less);
282 |
283 | var a: Elem = .{ .value = 1 };
284 | var b: Elem = .{ .value = 2 };
285 | var c: Elem = .{ .value = 3 };
286 | var d: Elem = .{ .value = 4 };
287 |
288 | var h: Heap = .{ .context = {} };
289 | h.insert(&a);
290 | h.insert(&b);
291 | h.insert(&c);
292 | h.insert(&d);
293 |
294 | try testing.expect(h.deleteMin().?.value == 1);
295 | try testing.expect(h.deleteMin().?.value == 2);
296 | try testing.expect(h.deleteMin().?.value == 3);
297 | try testing.expect(h.deleteMin().?.value == 4);
298 | try testing.expect(h.deleteMin() == null);
299 | }
300 |
301 | test "heap: million values" {
302 | const testing = std.testing;
303 | const alloc = testing.allocator;
304 |
305 | const Elem = struct {
306 | const Self = @This();
307 | value: usize = 0,
308 | heap: IntrusiveField(Self) = .{},
309 | };
310 |
311 | const Heap = Intrusive(Elem, void, (struct {
312 | fn less(ctx: void, a: *Elem, b: *Elem) bool {
313 | _ = ctx;
314 | return a.value < b.value;
315 | }
316 | }).less);
317 |
318 | const NUM_TIMERS: usize = 1000 * 1000;
319 | var elems = try alloc.alloc(Elem, NUM_TIMERS);
320 | defer alloc.free(elems);
321 |
322 | var i: usize = 0;
323 | var value: usize = 0;
324 | while (i < NUM_TIMERS) : (i += 1) {
325 | if (i % 100 == 0) value += 1;
326 | elems[i] = .{ .value = value };
327 | }
328 |
329 | var h: Heap = .{ .context = {} };
330 | for (elems) |*elem| {
331 | h.insert(elem);
332 | }
333 |
334 | var count: usize = 0;
335 | var last: usize = 0;
336 | while (h.deleteMin()) |elem| {
337 | count += 1;
338 | try testing.expect(elem.value >= last);
339 | last = elem.value;
340 | }
341 | try testing.expect(h.deleteMin() == null);
342 | try testing.expect(count == NUM_TIMERS);
343 | }
344 |
345 | test "heap: dangling next pointer" {
346 | const testing = std.testing;
347 | const Elem = struct {
348 | const Self = @This();
349 | value: usize = 0,
350 | heap: IntrusiveField(Self) = .{},
351 | };
352 |
353 | const Heap = Intrusive(Elem, void, (struct {
354 | fn less(ctx: void, a: *Elem, b: *Elem) bool {
355 | _ = ctx;
356 | return a.value < b.value;
357 | }
358 | }).less);
359 |
360 | var a: Elem = .{ .value = 2 };
361 | var b: Elem = .{ .value = 4 };
362 | var c: Elem = .{ .value = 5 };
363 | var d: Elem = .{ .value = 1 };
364 | var e: Elem = .{ .value = 3 };
365 |
366 | var h: Heap = .{ .context = {} };
367 | h.insert(&a);
368 | h.insert(&b);
369 | h.insert(&c);
370 | h.insert(&d);
371 | h.insert(&e);
372 |
373 | try testing.expect(h.deleteMin().?.value == 1);
374 | try testing.expect(h.deleteMin().?.value == 2);
375 | try testing.expect(h.deleteMin().?.value == 3);
376 | try testing.expect(h.deleteMin().?.value == 4);
377 | try testing.expect(h.deleteMin().?.value == 5);
378 | try testing.expect(h.deleteMin() == null);
379 | }
380 |
--------------------------------------------------------------------------------
/src/linux/timerfd.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const linux = std.os.linux;
3 | const posix = std.posix;
4 |
5 | /// Timerfd is a wrapper around the timerfd system calls. See the
6 | /// timerfd_create man page for information on timerfd and associated
7 | /// system calls.
8 | ///
9 | /// This is a small wrapper around timerfd to make it slightly more
10 | /// pleasant to use, but may not expose all available functionality.
11 | /// For maximum control you should use the syscalls directly.
12 | pub const Timerfd = struct {
13 | /// The timerfd file descriptor for use with poll, etc.
14 | fd: i32,
15 |
16 | /// timerfd_create
17 | pub fn init(
18 | clock: linux.timerfd_clockid_t,
19 | flags: linux.TFD,
20 | ) !Timerfd {
21 | const res = linux.timerfd_create(clock, flags);
22 | return switch (posix.errno(res)) {
23 | .SUCCESS => .{ .fd = @as(i32, @intCast(res)) },
24 | else => error.UnknownError,
25 | };
26 | }
27 |
28 | pub fn deinit(self: *const Timerfd) void {
29 | posix.close(self.fd);
30 | }
31 |
32 | /// timerfd_settime
33 | pub fn set(
34 | self: *const Timerfd,
35 | flags: linux.TFD.TIMER,
36 | new_value: *const Spec,
37 | old_value: ?*Spec,
38 | ) !void {
39 | const res = linux.timerfd_settime(
40 | self.fd,
41 | flags,
42 | @as(*const linux.itimerspec, @ptrCast(new_value)),
43 | @as(?*linux.itimerspec, @ptrCast(old_value)),
44 | );
45 |
46 | return switch (posix.errno(res)) {
47 | .SUCCESS => {},
48 | else => error.UnknownError,
49 | };
50 | }
51 |
52 | /// timerfd_gettime
53 | pub fn get(self: *const Timerfd) !Spec {
54 | var out: Spec = undefined;
55 | const res = linux.timerfd_gettime(self.fd, @as(*linux.itimerspec, @ptrCast(&out)));
56 | return switch (posix.errno(res)) {
57 | .SUCCESS => out,
58 | else => error.UnknownError,
59 | };
60 | }
61 |
62 | /// itimerspec
63 | pub const Spec = extern struct {
64 | interval: TimeSpec = .{},
65 | value: TimeSpec = .{},
66 | };
67 |
68 | /// timespec
69 | pub const TimeSpec = extern struct {
70 | seconds: isize = 0,
71 | nanoseconds: isize = 0,
72 | };
73 | };
74 |
75 | test Timerfd {
76 | const testing = std.testing;
77 |
78 | var t = try Timerfd.init(.MONOTONIC, .{});
79 | defer t.deinit();
80 |
81 | // Set
82 | try t.set(.{}, &.{ .value = .{ .seconds = 60 } }, null);
83 | try testing.expect((try t.get()).value.seconds > 0);
84 |
85 | // Disarm
86 | var old: Timerfd.Spec = undefined;
87 | try t.set(.{}, &.{ .value = .{ .seconds = 0 } }, &old);
88 | try testing.expect(old.value.seconds > 0);
89 | }
90 |
--------------------------------------------------------------------------------
/src/loop.zig:
--------------------------------------------------------------------------------
1 | //! Common loop structures. The actual loop implementation is in backend-specific
2 | //! files such as linux/io_uring.zig.
3 |
4 | const std = @import("std");
5 | const assert = std.debug.assert;
6 | const xev = @import("main.zig");
7 |
8 | /// Common options across backends. Not all options apply to all backends.
9 | /// Read the doc comment for individual fields to learn what backends they
10 | /// apply to.
11 | pub const Options = struct {
12 | /// The number of queued completions that can be in flight before
13 | /// requiring interaction with the kernel.
14 | ///
15 | /// Backends: io_uring
16 | entries: u32 = 256,
17 |
18 | /// A thread pool to use for blocking operations. If the backend doesn't
19 | /// need to perform any blocking operations then no threads will ever
20 | /// be spawned. If the backend does need to perform blocking operations
21 | /// on a thread and no thread pool is provided, the operations will simply
22 | /// fail. Unless you're trying to really optimize for space, it is
23 | /// recommended you provide a thread pool.
24 | ///
25 | /// Backends: epoll, kqueue
26 | thread_pool: ?*xev.ThreadPool = null,
27 | };
28 |
29 | /// The loop run mode -- all backends are required to support this in some way.
30 | /// Backends may provide backend-specific APIs that behave slightly differently
31 | /// or in a more configurable way.
32 | pub const RunMode = enum(c_int) {
33 | /// Run the event loop once. If there are no blocking operations ready,
34 | /// return immediately.
35 | no_wait = 0,
36 |
37 | /// Run the event loop once, waiting for at least one blocking operation
38 | /// to complete.
39 | once = 1,
40 |
41 | /// Run the event loop until it is "done". "Doneness" is defined as
42 | /// there being no more completions that are active.
43 | until_done = 2,
44 | };
45 |
46 | /// The result type for callbacks. This should be used by all loop
47 | /// implementations and higher level abstractions in order to control
48 | /// what to do after the loop completes.
49 | pub const CallbackAction = enum(c_int) {
50 | /// The request is complete and is not repeated. For example, a read
51 | /// callback only fires once and is no longer watched for reads. You
52 | /// can always free memory associated with the completion prior to
53 | /// returning this.
54 | disarm = 0,
55 |
56 | /// Requeue the same operation request with the same parameters
57 | /// with the event loop. This makes it easy to repeat a read, timer,
58 | /// etc. This rearms the request EXACTLY as-is. For example, the
59 | /// low-level timer interface for io_uring uses an absolute timeout.
60 | /// If you rearm the timer, it will fire immediately because the absolute
61 | /// timeout will be in the past.
62 | ///
63 | /// The completion is reused so it is not safe to use the same completion
64 | /// for anything else.
65 | rearm = 1,
66 | };
67 |
68 | /// The state that a completion can be in.
69 | pub const CompletionState = enum(c_int) {
70 | /// The completion is not being used and is ready to be configured
71 | /// for new work.
72 | dead = 0,
73 |
74 | /// The completion is part of an event loop. This may be already waited
75 | /// on or in the process of being registered.
76 | active = 1,
77 | };
78 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const builtin = @import("builtin");
3 |
4 | /// The low-level IO interfaces using the recommended compile-time
5 | /// interface for the target system.
6 | const xev = Backend.default().Api();
7 | pub usingnamespace xev;
8 | //pub usingnamespace Epoll;
9 |
10 | /// The dynamic interface that allows for runtime selection of the
11 | /// backend to use. This is useful if you want to support multiple
12 | /// backends and have a fallback mechanism.
13 | ///
14 | /// There is a very small overhead to using this API compared to the
15 | /// static API, but it is generally negligible.
16 | ///
17 | /// The API for this isn't _exactly_ the same as the static API
18 | /// since it requires initialization of the main struct to detect
19 | /// a backend and then this needs to be passed to every high-level
20 | /// type such as Async, File, etc. so that they can use the correct
21 | /// backend that is coherent with the loop.
22 | pub const Dynamic = DynamicXev(Backend.candidates());
23 | pub const DynamicXev = @import("dynamic.zig").Xev;
24 |
25 | /// System-specific interfaces. Note that they are always pub for
26 | /// all systems but if you reference them and force them to be analyzed
27 | /// the proper system APIs must exist. Due to Zig's lazy analysis, if you
28 | /// don't use any interface it will NOT be compiled (yay!).
29 | pub const IO_Uring = Xev(.io_uring, @import("backend/io_uring.zig"));
30 | pub const Epoll = Xev(.epoll, @import("backend/epoll.zig"));
31 | pub const Kqueue = Xev(.kqueue, @import("backend/kqueue.zig"));
32 | pub const WasiPoll = Xev(.wasi_poll, @import("backend/wasi_poll.zig"));
33 | pub const IOCP = Xev(.iocp, @import("backend/iocp.zig"));
34 |
35 | /// Generic thread pool implementation.
36 | pub const ThreadPool = @import("ThreadPool.zig");
37 |
38 | /// This stream (lowercase s) can be used as a namespace to access
39 | /// Closeable, Writeable, Readable, etc. so that custom streams
40 | /// can be constructed.
41 | pub const stream = @import("watcher/stream.zig");
42 |
43 | /// The backend types.
44 | pub const Backend = enum {
45 | io_uring,
46 | epoll,
47 | kqueue,
48 | wasi_poll,
49 | iocp,
50 |
51 | /// Returns a recommend default backend from inspecting the system.
52 | pub fn default() Backend {
53 | return switch (builtin.os.tag) {
54 | .linux => .io_uring,
55 | .ios, .macos => .kqueue,
56 | .wasi => .wasi_poll,
57 | .windows => .iocp,
58 | else => {
59 | @compileLog(builtin.os);
60 | @compileError("no default backend for this target");
61 | },
62 | };
63 | }
64 |
65 | /// Candidate backends for this platform in priority order.
66 | pub fn candidates() []const Backend {
67 | return switch (builtin.os.tag) {
68 | .linux => &.{ .io_uring, .epoll },
69 | .ios, .macos => &.{.kqueue},
70 | .wasi => &.{.wasi_poll},
71 | .windows => &.{.iocp},
72 | else => {
73 | @compileLog(builtin.os);
74 | @compileError("no candidate backends for this target");
75 | },
76 | };
77 | }
78 |
79 | /// Returns the Api (return value of Xev) for the given backend type.
80 | pub fn Api(comptime self: Backend) type {
81 | return switch (self) {
82 | .io_uring => IO_Uring,
83 | .epoll => Epoll,
84 | .kqueue => Kqueue,
85 | .wasi_poll => WasiPoll,
86 | .iocp => IOCP,
87 | };
88 | }
89 | };
90 |
91 | /// Creates the Xev API based on a backend type.
92 | ///
93 | /// For the default backend type for your system (i.e. io_uring on Linux),
94 | /// this is the main API you interact with. It is `usingnamespaced` into
95 | /// the "xev" package so you'd use types such as `xev.Loop`, `xev.Completion`,
96 | /// etc.
97 | ///
98 | /// Unless you're using a custom or specific backend type, you do NOT ever
99 | /// need to call the Xev function itself.
100 | pub fn Xev(comptime be: Backend, comptime T: type) type {
101 | return struct {
102 | const Self = @This();
103 | const loop = @import("loop.zig");
104 |
105 | /// This is used to detect a static vs dynamic API at comptime.
106 | pub const dynamic = false;
107 |
108 | /// The backend that this is. This is supplied at comptime so
109 | /// it is up to the caller to say the right thing. This lets custom
110 | /// implementations also "quack" like an implementation.
111 | pub const backend = be;
112 |
113 | /// A function to test if this API is available on the
114 | /// current system.
115 | pub const available = T.available;
116 |
117 | /// The core loop APIs.
118 | pub const Loop = T.Loop;
119 | pub const Completion = T.Completion;
120 | pub const Result = T.Result;
121 | pub const ReadBuffer = T.ReadBuffer;
122 | pub const WriteBuffer = T.WriteBuffer;
123 | pub const Options = loop.Options;
124 | pub const RunMode = loop.RunMode;
125 | pub const CallbackAction = loop.CallbackAction;
126 | pub const CompletionState = loop.CompletionState;
127 |
128 | /// Error types
129 | pub const AcceptError = T.AcceptError;
130 | pub const CancelError = T.CancelError;
131 | pub const CloseError = T.CloseError;
132 | pub const ConnectError = T.ConnectError;
133 | pub const ShutdownError = T.ShutdownError;
134 | pub const WriteError = T.WriteError;
135 | pub const ReadError = T.ReadError;
136 |
137 | /// Shared stream types
138 | const SharedStream = stream.Shared(Self);
139 | pub const PollError = SharedStream.PollError;
140 | pub const PollEvent = SharedStream.PollEvent;
141 | pub const WriteQueue = SharedStream.WriteQueue;
142 | pub const WriteRequest = SharedStream.WriteRequest;
143 |
144 | /// The high-level helper interfaces that make it easier to perform
145 | /// common tasks. These may not work with all possible Loop implementations.
146 | pub const Async = @import("watcher/async.zig").Async(Self);
147 | pub const File = @import("watcher/file.zig").File(Self);
148 | pub const Process = @import("watcher/process.zig").Process(Self);
149 | pub const Stream = stream.GenericStream(Self);
150 | pub const Timer = @import("watcher/timer.zig").Timer(Self);
151 | pub const TCP = @import("watcher/tcp.zig").TCP(Self);
152 | pub const UDP = @import("watcher/udp.zig").UDP(Self);
153 |
154 | /// The callback of the main Loop operations. Higher level interfaces may
155 | /// use a different callback mechanism.
156 | pub const Callback = *const fn (
157 | userdata: ?*anyopaque,
158 | loop: *Loop,
159 | completion: *Completion,
160 | result: Result,
161 | ) CallbackAction;
162 |
163 | /// A way to access the raw type.
164 | pub const Sys = T;
165 |
166 | /// A callback that does nothing and immediately disarms. This
167 | /// implements xev.Callback and is the default value for completions.
168 | pub fn noopCallback(
169 | _: ?*anyopaque,
170 | _: *Loop,
171 | _: *Completion,
172 | _: Result,
173 | ) CallbackAction {
174 | return .disarm;
175 | }
176 |
177 | test {
178 | @import("std").testing.refAllDecls(@This());
179 | }
180 |
181 | test "completion is zero-able" {
182 | const c: Completion = .{};
183 | _ = c;
184 | }
185 | };
186 | }
187 |
188 | test {
189 | // Tested on all platforms
190 | _ = @import("heap.zig");
191 | _ = @import("queue.zig");
192 | _ = @import("queue_mpsc.zig");
193 | _ = ThreadPool;
194 | _ = Dynamic;
195 |
196 | // Test the C API
197 | if (builtin.os.tag != .wasi) _ = @import("c_api.zig");
198 |
199 | // OS-specific tests
200 | switch (builtin.os.tag) {
201 | .linux => {
202 | _ = Epoll;
203 | _ = IO_Uring;
204 | _ = @import("linux/timerfd.zig");
205 | },
206 |
207 | .wasi => {
208 | //_ = WasiPoll;
209 | _ = @import("backend/wasi_poll.zig");
210 | },
211 |
212 | .windows => {
213 | _ = @import("backend/iocp.zig");
214 | },
215 |
216 | else => {},
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/src/queue.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 |
4 | /// An intrusive queue implementation. The type T must have a field
5 | /// "next" of type `?*T`.
6 | ///
7 | /// For those unaware, an intrusive variant of a data structure is one in which
8 | /// the data type in the list has the pointer to the next element, rather
9 | /// than a higher level "node" or "container" type. The primary benefit
10 | /// of this (and the reason we implement this) is that it defers all memory
11 | /// management to the caller: the data structure implementation doesn't need
12 | /// to allocate "nodes" to contain each element. Instead, the caller provides
13 | /// the element and how its allocated is up to them.
14 | pub fn Intrusive(comptime T: type) type {
15 | return struct {
16 | const Self = @This();
17 |
18 | /// Head is the front of the queue and tail is the back of the queue.
19 | head: ?*T = null,
20 | tail: ?*T = null,
21 |
22 | /// Enqueue a new element to the back of the queue.
23 | pub fn push(self: *Self, v: *T) void {
24 | assert(v.next == null);
25 |
26 | if (self.tail) |tail| {
27 | // If we have elements in the queue, then we add a new tail.
28 | tail.next = v;
29 | self.tail = v;
30 | } else {
31 | // No elements in the queue we setup the initial state.
32 | self.head = v;
33 | self.tail = v;
34 | }
35 | }
36 |
37 | /// Dequeue the next element from the queue.
38 | pub fn pop(self: *Self) ?*T {
39 | // The next element is in "head".
40 | const next = self.head orelse return null;
41 |
42 | // If the head and tail are equal this is the last element
43 | // so we also set tail to null so we can now be empty.
44 | if (self.head == self.tail) self.tail = null;
45 |
46 | // Head is whatever is next (if we're the last element,
47 | // this will be null);
48 | self.head = next.next;
49 |
50 | // We set the "next" field to null so that this element
51 | // can be inserted again.
52 | next.next = null;
53 | return next;
54 | }
55 |
56 | /// Returns true if the queue is empty.
57 | pub fn empty(self: *const Self) bool {
58 | return self.head == null;
59 | }
60 | };
61 | }
62 |
63 | test Intrusive {
64 | const testing = std.testing;
65 |
66 | // Types
67 | const Elem = struct {
68 | const Self = @This();
69 | next: ?*Self = null,
70 | };
71 | const Queue = Intrusive(Elem);
72 | var q: Queue = .{};
73 | try testing.expect(q.empty());
74 |
75 | // Elems
76 | var elems: [10]Elem = .{Elem{}} ** 10;
77 |
78 | // One
79 | try testing.expect(q.pop() == null);
80 | q.push(&elems[0]);
81 | try testing.expect(!q.empty());
82 | try testing.expect(q.pop().? == &elems[0]);
83 | try testing.expect(q.pop() == null);
84 | try testing.expect(q.empty());
85 |
86 | // Two
87 | try testing.expect(q.pop() == null);
88 | q.push(&elems[0]);
89 | q.push(&elems[1]);
90 | try testing.expect(q.pop().? == &elems[0]);
91 | try testing.expect(q.pop().? == &elems[1]);
92 | try testing.expect(q.pop() == null);
93 |
94 | // Interleaved
95 | try testing.expect(q.pop() == null);
96 | q.push(&elems[0]);
97 | try testing.expect(q.pop().? == &elems[0]);
98 | q.push(&elems[1]);
99 | try testing.expect(q.pop().? == &elems[1]);
100 | try testing.expect(q.pop() == null);
101 | }
102 |
--------------------------------------------------------------------------------
/src/queue_mpsc.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const assert = std.debug.assert;
3 |
4 | /// An intrusive MPSC (multi-provider, single consumer) queue implementation.
5 | /// The type T must have a field "next" of type `?*T`.
6 | ///
7 | /// This is an implementatin of a Vyukov Queue[1].
8 | /// TODO(mitchellh): I haven't audited yet if I got all the atomic operations
9 | /// correct. I was short term more focused on getting something that seemed
10 | /// to work; I need to make sure it actually works.
11 | ///
12 | /// For those unaware, an intrusive variant of a data structure is one in which
13 | /// the data type in the list has the pointer to the next element, rather
14 | /// than a higher level "node" or "container" type. The primary benefit
15 | /// of this (and the reason we implement this) is that it defers all memory
16 | /// management to the caller: the data structure implementation doesn't need
17 | /// to allocate "nodes" to contain each element. Instead, the caller provides
18 | /// the element and how its allocated is up to them.
19 | ///
20 | /// [1]: https://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
21 | pub fn Intrusive(comptime T: type) type {
22 | return struct {
23 | const Self = @This();
24 |
25 | /// Head is the front of the queue and tail is the back of the queue.
26 | head: *T,
27 | tail: *T,
28 | stub: T,
29 |
30 | /// Initialize the queue. This requires a stable pointer to itself.
31 | /// This must be called before the queue is used concurrently.
32 | pub fn init(self: *Self) void {
33 | self.head = &self.stub;
34 | self.tail = &self.stub;
35 | self.stub.next = null;
36 | }
37 |
38 | /// Push an item onto the queue. This can be called by any number
39 | /// of producers.
40 | pub fn push(self: *Self, v: *T) void {
41 | @atomicStore(?*T, &v.next, null, .unordered);
42 | const prev = @atomicRmw(*T, &self.head, .Xchg, v, .acq_rel);
43 | @atomicStore(?*T, &prev.next, v, .release);
44 | }
45 |
46 | /// Pop the first in element from the queue. This must be called
47 | /// by only a single consumer at any given time.
48 | pub fn pop(self: *Self) ?*T {
49 | var tail = @atomicLoad(*T, &self.tail, .unordered);
50 | var next_ = @atomicLoad(?*T, &tail.next, .acquire);
51 | if (tail == &self.stub) {
52 | const next = next_ orelse return null;
53 | @atomicStore(*T, &self.tail, next, .unordered);
54 | tail = next;
55 | next_ = @atomicLoad(?*T, &tail.next, .acquire);
56 | }
57 |
58 | if (next_) |next| {
59 | @atomicStore(*T, &self.tail, next, .release);
60 | tail.next = null;
61 | return tail;
62 | }
63 |
64 | const head = @atomicLoad(*T, &self.head, .unordered);
65 | if (tail != head) return null;
66 | self.push(&self.stub);
67 |
68 | next_ = @atomicLoad(?*T, &tail.next, .acquire);
69 | if (next_) |next| {
70 | @atomicStore(*T, &self.tail, next, .unordered);
71 | tail.next = null;
72 | return tail;
73 | }
74 |
75 | return null;
76 | }
77 | };
78 | }
79 |
80 | test Intrusive {
81 | const testing = std.testing;
82 |
83 | // Types
84 | const Elem = struct {
85 | const Self = @This();
86 | next: ?*Self = null,
87 | };
88 | const Queue = Intrusive(Elem);
89 | var q: Queue = undefined;
90 | q.init();
91 |
92 | // Elems
93 | var elems: [10]Elem = .{Elem{}} ** 10;
94 |
95 | // One
96 | try testing.expect(q.pop() == null);
97 | q.push(&elems[0]);
98 | try testing.expect(q.pop().? == &elems[0]);
99 | try testing.expect(q.pop() == null);
100 |
101 | // Two
102 | try testing.expect(q.pop() == null);
103 | q.push(&elems[0]);
104 | q.push(&elems[1]);
105 | try testing.expect(q.pop().? == &elems[0]);
106 | try testing.expect(q.pop().? == &elems[1]);
107 | try testing.expect(q.pop() == null);
108 |
109 | // // Interleaved
110 | try testing.expect(q.pop() == null);
111 | q.push(&elems[0]);
112 | try testing.expect(q.pop().? == &elems[0]);
113 | q.push(&elems[1]);
114 | try testing.expect(q.pop().? == &elems[1]);
115 | try testing.expect(q.pop() == null);
116 | }
117 |
--------------------------------------------------------------------------------
/src/watcher/common.zig:
--------------------------------------------------------------------------------
1 | /// Convert the callback value with an opaque pointer into the userdata type
2 | /// that we can pass to our higher level callback types.
3 | pub fn userdataValue(comptime Userdata: type, v: ?*anyopaque) ?*Userdata {
4 | // Void userdata is always a null pointer.
5 | if (Userdata == void) return null;
6 | return @ptrCast(@alignCast(v));
7 | }
8 |
--------------------------------------------------------------------------------
/src/windows.zig:
--------------------------------------------------------------------------------
1 | //! Namespace containing missing utils from std
2 |
3 | const std = @import("std");
4 | const windows = std.os.windows;
5 | const posix = std.posix;
6 |
7 | pub usingnamespace std.os.windows;
8 |
9 | pub extern "kernel32" fn DeleteFileW(lpFileName: [*:0]const u16) callconv(windows.WINAPI) windows.BOOL;
10 |
11 | pub const exp = struct {
12 | pub const STATUS_PENDING = 0x00000103;
13 | pub const STILL_ACTIVE = STATUS_PENDING;
14 |
15 | pub const JOBOBJECT_ASSOCIATE_COMPLETION_PORT = extern struct {
16 | CompletionKey: windows.ULONG_PTR,
17 | CompletionPort: windows.HANDLE,
18 | };
19 |
20 | pub const JOBOBJECT_BASIC_LIMIT_INFORMATION = extern struct {
21 | PerProcessUserTimeLimit: windows.LARGE_INTEGER,
22 | PerJobUserTimeLimit: windows.LARGE_INTEGER,
23 | LimitFlags: windows.DWORD,
24 | MinimumWorkingSetSize: windows.SIZE_T,
25 | MaximumWorkingSetSize: windows.SIZE_T,
26 | ActiveProcessLimit: windows.DWORD,
27 | Affinity: windows.ULONG_PTR,
28 | PriorityClass: windows.DWORD,
29 | SchedulingClass: windows.DWORD,
30 | };
31 |
32 | pub const IO_COUNTERS = extern struct {
33 | ReadOperationCount: windows.ULONGLONG,
34 | WriteOperationCount: windows.ULONGLONG,
35 | OtherOperationCount: windows.ULONGLONG,
36 | ReadTransferCount: windows.ULONGLONG,
37 | WriteTransferCount: windows.ULONGLONG,
38 | OtherTransferCount: windows.ULONGLONG,
39 | };
40 |
41 | pub const JOBOBJECT_EXTENDED_LIMIT_INFORMATION = extern struct {
42 | BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION,
43 | IoInfo: IO_COUNTERS,
44 | ProcessMemoryLimit: windows.SIZE_T,
45 | JobMemoryLimit: windows.SIZE_T,
46 | PeakProcessMemoryUsed: windows.SIZE_T,
47 | PeakJobMemoryUsed: windows.SIZE_T,
48 | };
49 |
50 | pub const JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x00000008;
51 | pub const JOB_OBJECT_LIMIT_AFFINITY = 0x00000010;
52 | pub const JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800;
53 | pub const JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x00000400;
54 | pub const JOB_OBJECT_LIMIT_JOB_MEMORY = 0x00000200;
55 | pub const JOB_OBJECT_LIMIT_JOB_TIME = 0x00000004;
56 | pub const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
57 | pub const JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x00000004;
58 | pub const JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x00000020;
59 | pub const JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x00000100;
60 | pub const JOB_OBJECT_LIMIT_PROCESS_TIME = 0x00000002;
61 | pub const JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x00000080;
62 | pub const JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000;
63 | pub const JOB_OBJECT_LIMIT_SUBSET_AFFINITY = 0x00004000;
64 | pub const JOB_OBJECT_LIMIT_WORKINGSET = 0x00000001;
65 |
66 | pub const JOBOBJECT_INFORMATION_CLASS = enum(c_int) {
67 | JobObjectAssociateCompletionPortInformation = 7,
68 | JobObjectBasicLimitInformation = 2,
69 | JobObjectBasicUIRestrictions = 4,
70 | JobObjectCpuRateControlInformation = 15,
71 | JobObjectEndOfJobTimeInformation = 6,
72 | JobObjectExtendedLimitInformation = 9,
73 | JobObjectGroupInformation = 11,
74 | JobObjectGroupInformationEx = 14,
75 | JobObjectLimitViolationInformation2 = 34,
76 | JobObjectNetRateControlInformation = 32,
77 | JobObjectNotificationLimitInformation = 12,
78 | JobObjectNotificationLimitInformation2 = 33,
79 | JobObjectSecurityLimitInformation = 5,
80 | };
81 |
82 | pub const JOB_OBJECT_MSG_TYPE = enum(windows.DWORD) {
83 | JOB_OBJECT_MSG_END_OF_JOB_TIME = 1,
84 | JOB_OBJECT_MSG_END_OF_PROCESS_TIME = 2,
85 | JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT = 3,
86 | JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO = 4,
87 | JOB_OBJECT_MSG_NEW_PROCESS = 6,
88 | JOB_OBJECT_MSG_EXIT_PROCESS = 7,
89 | JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS = 8,
90 | JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT = 9,
91 | JOB_OBJECT_MSG_JOB_MEMORY_LIMIT = 10,
92 | JOB_OBJECT_MSG_NOTIFICATION_LIMIT = 11,
93 | JOB_OBJECT_MSG_JOB_CYCLE_TIME_LIMIT = 12,
94 | JOB_OBJECT_MSG_SILO_TERMINATED = 13,
95 | _,
96 | };
97 |
98 | pub const kernel32 = struct {
99 | pub extern "kernel32" fn GetProcessId(Process: windows.HANDLE) callconv(windows.WINAPI) windows.DWORD;
100 | pub extern "kernel32" fn CreateJobObjectA(lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES, lpName: ?windows.LPCSTR) callconv(windows.WINAPI) windows.HANDLE;
101 | pub extern "kernel32" fn AssignProcessToJobObject(hJob: windows.HANDLE, hProcess: windows.HANDLE) callconv(windows.WINAPI) windows.BOOL;
102 | pub extern "kernel32" fn SetInformationJobObject(
103 | hJob: windows.HANDLE,
104 | JobObjectInformationClass: JOBOBJECT_INFORMATION_CLASS,
105 | lpJobObjectInformation: windows.LPVOID,
106 | cbJobObjectInformationLength: windows.DWORD,
107 | ) callconv(windows.WINAPI) windows.BOOL;
108 | };
109 |
110 | pub const CreateFileError = error{} || posix.UnexpectedError;
111 |
112 | pub fn CreateFile(
113 | lpFileName: [*:0]const u16,
114 | dwDesiredAccess: windows.DWORD,
115 | dwShareMode: windows.DWORD,
116 | lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES,
117 | dwCreationDisposition: windows.DWORD,
118 | dwFlagsAndAttributes: windows.DWORD,
119 | hTemplateFile: ?windows.HANDLE,
120 | ) CreateFileError!windows.HANDLE {
121 | const handle = windows.kernel32.CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
122 | if (handle == windows.INVALID_HANDLE_VALUE) {
123 | const err = windows.kernel32.GetLastError();
124 | return switch (err) {
125 | else => windows.unexpectedError(err),
126 | };
127 | }
128 |
129 | return handle;
130 | }
131 |
132 | pub fn ReadFile(
133 | handle: windows.HANDLE,
134 | buffer: []u8,
135 | overlapped: ?*windows.OVERLAPPED,
136 | ) windows.ReadFileError!?usize {
137 | var read: windows.DWORD = 0;
138 | const result: windows.BOOL = windows.kernel32.ReadFile(handle, buffer.ptr, @as(windows.DWORD, @intCast(buffer.len)), &read, overlapped);
139 | if (result == windows.FALSE) {
140 | const err = windows.kernel32.GetLastError();
141 | return switch (err) {
142 | windows.Win32Error.IO_PENDING => null,
143 | else => windows.unexpectedError(err),
144 | };
145 | }
146 |
147 | return @as(usize, @intCast(read));
148 | }
149 |
150 | pub fn WriteFile(
151 | handle: windows.HANDLE,
152 | buffer: []const u8,
153 | overlapped: ?*windows.OVERLAPPED,
154 | ) windows.WriteFileError!?usize {
155 | var written: windows.DWORD = 0;
156 | const result: windows.BOOL = windows.kernel32.WriteFile(handle, buffer.ptr, @as(windows.DWORD, @intCast(buffer.len)), &written, overlapped);
157 | if (result == windows.FALSE) {
158 | const err = windows.kernel32.GetLastError();
159 | return switch (err) {
160 | windows.Win32Error.IO_PENDING => null,
161 | else => windows.unexpectedError(err),
162 | };
163 | }
164 |
165 | return @as(usize, @intCast(written));
166 | }
167 |
168 | pub const DeleteFileError = error{} || posix.UnexpectedError;
169 |
170 | pub fn DeleteFile(name: [*:0]const u16) DeleteFileError!void {
171 | const result: windows.BOOL = DeleteFileW(name);
172 | if (result == windows.FALSE) {
173 | const err = windows.kernel32.GetLastError();
174 | return switch (err) {
175 | else => windows.unexpectedError(err),
176 | };
177 | }
178 | }
179 |
180 | pub const CreateJobObjectError = error{AlreadyExists} || posix.UnexpectedError;
181 | pub fn CreateJobObject(
182 | lpSecurityAttributes: ?*windows.SECURITY_ATTRIBUTES,
183 | lpName: ?windows.LPCSTR,
184 | ) !windows.HANDLE {
185 | const handle = kernel32.CreateJobObjectA(lpSecurityAttributes, lpName);
186 | return switch (windows.kernel32.GetLastError()) {
187 | .SUCCESS => handle,
188 | .ALREADY_EXISTS => CreateJobObjectError.AlreadyExists,
189 | else => |err| windows.unexpectedError(err),
190 | };
191 | }
192 |
193 | pub fn AssignProcessToJobObject(hJob: windows.HANDLE, hProcess: windows.HANDLE) posix.UnexpectedError!void {
194 | const result: windows.BOOL = kernel32.AssignProcessToJobObject(hJob, hProcess);
195 | if (result == windows.FALSE) {
196 | const err = windows.kernel32.GetLastError();
197 | return switch (err) {
198 | else => windows.unexpectedError(err),
199 | };
200 | }
201 | }
202 |
203 | pub fn SetInformationJobObject(
204 | hJob: windows.HANDLE,
205 | JobObjectInformationClass: JOBOBJECT_INFORMATION_CLASS,
206 | lpJobObjectInformation: windows.LPVOID,
207 | cbJobObjectInformationLength: windows.DWORD,
208 | ) posix.UnexpectedError!void {
209 | const result: windows.BOOL = kernel32.SetInformationJobObject(
210 | hJob,
211 | JobObjectInformationClass,
212 | lpJobObjectInformation,
213 | cbJobObjectInformationLength,
214 | );
215 |
216 | if (result == windows.FALSE) {
217 | const err = windows.kernel32.GetLastError();
218 | return switch (err) {
219 | else => windows.unexpectedError(err),
220 | };
221 | }
222 | }
223 | };
224 |
--------------------------------------------------------------------------------
/website/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/website/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | const withNextra = require('nextra')({
7 | theme: 'nextra-theme-docs',
8 | themeConfig: './theme.config.jsx',
9 | })
10 |
11 | module.exports = withNextra(nextConfig)
12 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next/font": "13.1.2",
13 | "@types/node": "18.11.18",
14 | "@types/react": "18.0.26",
15 | "@types/react-dom": "18.0.10",
16 | "eslint": "8.32.0",
17 | "eslint-config-next": "13.1.2",
18 | "next": "13.1.2",
19 | "nextra": "^2.2.5",
20 | "nextra-theme-docs": "^2.2.5",
21 | "react": "18.2.0",
22 | "react-dom": "18.2.0",
23 | "typescript": "4.9.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/website/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css'
2 | import type { AppProps } from 'next/app'
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/website/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/website/pages/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": "Home",
3 | "getting-started": "Getting Started",
4 | "api-docs": "API"
5 | }
6 |
--------------------------------------------------------------------------------
/website/pages/api-docs/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "concepts": "Concepts"
3 | }
4 |
--------------------------------------------------------------------------------
/website/pages/api-docs/concepts.mdx:
--------------------------------------------------------------------------------
1 | # General API Concepts
2 |
3 |
--------------------------------------------------------------------------------
/website/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/website/pages/getting-started.mdx:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | TODO
4 |
--------------------------------------------------------------------------------
/website/pages/getting-started/_meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "concepts": "Concepts"
3 | }
4 |
--------------------------------------------------------------------------------
/website/pages/getting-started/concepts.mdx:
--------------------------------------------------------------------------------
1 | # General Concepts
2 |
3 | TODO
4 |
--------------------------------------------------------------------------------
/website/pages/index.mdx:
--------------------------------------------------------------------------------
1 | # libxev
2 |
3 | import { Callout } from 'nextra-theme-docs'
4 |
5 | TODO! See the GitHub repo for now.
6 |
7 |
8 | **Coming soon.** I've just got some scaffolding down.
9 |
10 |
--------------------------------------------------------------------------------
/website/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitchellh/libxev/58507577fc87b89471809a1a23415bee1d81814d/website/public/favicon.ico
--------------------------------------------------------------------------------
/website/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | width: var(--max-width);
46 | max-width: 100%;
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 4rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: '';
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo,
108 | .thirteen {
109 | position: relative;
110 | }
111 |
112 | .thirteen {
113 | display: flex;
114 | justify-content: center;
115 | align-items: center;
116 | width: 75px;
117 | height: 75px;
118 | padding: 25px 10px;
119 | margin-left: 16px;
120 | transform: translateZ(0);
121 | border-radius: var(--border-radius);
122 | overflow: hidden;
123 | box-shadow: 0px 2px 8px -1px #0000001a;
124 | }
125 |
126 | .thirteen::before,
127 | .thirteen::after {
128 | content: '';
129 | position: absolute;
130 | z-index: -1;
131 | }
132 |
133 | /* Conic Gradient Animation */
134 | .thirteen::before {
135 | animation: 6s rotate linear infinite;
136 | width: 200%;
137 | height: 200%;
138 | background: var(--tile-border);
139 | }
140 |
141 | /* Inner Square */
142 | .thirteen::after {
143 | inset: 0;
144 | padding: 1px;
145 | border-radius: var(--border-radius);
146 | background: linear-gradient(
147 | to bottom right,
148 | rgba(var(--tile-start-rgb), 1),
149 | rgba(var(--tile-end-rgb), 1)
150 | );
151 | background-clip: content-box;
152 | }
153 |
154 | /* Enable hover only on non-touch devices */
155 | @media (hover: hover) and (pointer: fine) {
156 | .card:hover {
157 | background: rgba(var(--card-rgb), 0.1);
158 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
159 | }
160 |
161 | .card:hover span {
162 | transform: translateX(4px);
163 | }
164 | }
165 |
166 | @media (prefers-reduced-motion) {
167 | .thirteen::before {
168 | animation: none;
169 | }
170 |
171 | .card:hover span {
172 | transform: none;
173 | }
174 | }
175 |
176 | /* Mobile */
177 | @media (max-width: 700px) {
178 | .content {
179 | padding: 4rem;
180 | }
181 |
182 | .grid {
183 | grid-template-columns: 1fr;
184 | margin-bottom: 120px;
185 | max-width: 320px;
186 | text-align: center;
187 | }
188 |
189 | .card {
190 | padding: 1rem 2.5rem;
191 | }
192 |
193 | .card h2 {
194 | margin-bottom: 0.5rem;
195 | }
196 |
197 | .center {
198 | padding: 8rem 0 6rem;
199 | }
200 |
201 | .center::before {
202 | transform: none;
203 | height: 300px;
204 | }
205 |
206 | .description {
207 | font-size: 0.8rem;
208 | }
209 |
210 | .description a {
211 | padding: 1rem;
212 | }
213 |
214 | .description p,
215 | .description div {
216 | display: flex;
217 | justify-content: center;
218 | position: fixed;
219 | width: 100%;
220 | }
221 |
222 | .description p {
223 | align-items: center;
224 | inset: 0 0 auto;
225 | padding: 2rem 1rem 1.4rem;
226 | border-radius: 0;
227 | border: none;
228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
229 | background: linear-gradient(
230 | to bottom,
231 | rgba(var(--background-start-rgb), 1),
232 | rgba(var(--callout-rgb), 0.5)
233 | );
234 | background-clip: padding-box;
235 | backdrop-filter: blur(24px);
236 | }
237 |
238 | .description div {
239 | align-items: flex-end;
240 | pointer-events: none;
241 | inset: auto 0 0;
242 | padding: 2rem;
243 | height: 200px;
244 | background: linear-gradient(
245 | to bottom,
246 | transparent 0%,
247 | rgb(var(--background-end-rgb)) 40%
248 | );
249 | z-index: 1;
250 | }
251 | }
252 |
253 | /* Tablet and Smaller Desktop */
254 | @media (min-width: 701px) and (max-width: 1120px) {
255 | .grid {
256 | grid-template-columns: repeat(2, 50%);
257 | }
258 | }
259 |
260 | @media (prefers-color-scheme: dark) {
261 | .vercelLogo {
262 | filter: invert(1);
263 | }
264 |
265 | .logo,
266 | .thirteen img {
267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
268 | }
269 | }
270 |
271 | @keyframes rotate {
272 | from {
273 | transform: rotate(360deg);
274 | }
275 | to {
276 | transform: rotate(0deg);
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/website/styles/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --max-width: 1100px;
3 | --border-radius: 12px;
4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
7 |
8 | --foreground-rgb: 0, 0, 0;
9 | --background-start-rgb: 214, 219, 220;
10 | --background-end-rgb: 255, 255, 255;
11 |
12 | --primary-glow: conic-gradient(
13 | from 180deg at 50% 50%,
14 | #16abff33 0deg,
15 | #0885ff33 55deg,
16 | #54d6ff33 120deg,
17 | #0071ff33 160deg,
18 | transparent 360deg
19 | );
20 | --secondary-glow: radial-gradient(
21 | rgba(255, 255, 255, 1),
22 | rgba(255, 255, 255, 0)
23 | );
24 |
25 | --tile-start-rgb: 239, 245, 249;
26 | --tile-end-rgb: 228, 232, 233;
27 | --tile-border: conic-gradient(
28 | #00000080,
29 | #00000040,
30 | #00000030,
31 | #00000020,
32 | #00000010,
33 | #00000010,
34 | #00000080
35 | );
36 |
37 | --callout-rgb: 238, 240, 241;
38 | --callout-border-rgb: 172, 175, 176;
39 | --card-rgb: 180, 185, 188;
40 | --card-border-rgb: 131, 134, 135;
41 | }
42 |
43 | @media (prefers-color-scheme: dark) {
44 | :root {
45 | --foreground-rgb: 255, 255, 255;
46 | --background-start-rgb: 0, 0, 0;
47 | --background-end-rgb: 0, 0, 0;
48 |
49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50 | --secondary-glow: linear-gradient(
51 | to bottom right,
52 | rgba(1, 65, 255, 0),
53 | rgba(1, 65, 255, 0),
54 | rgba(1, 65, 255, 0.3)
55 | );
56 |
57 | --tile-start-rgb: 2, 13, 46;
58 | --tile-end-rgb: 2, 5, 19;
59 | --tile-border: conic-gradient(
60 | #ffffff80,
61 | #ffffff40,
62 | #ffffff30,
63 | #ffffff20,
64 | #ffffff10,
65 | #ffffff10,
66 | #ffffff80
67 | );
68 |
69 | --callout-rgb: 20, 20, 20;
70 | --callout-border-rgb: 108, 108, 108;
71 | --card-rgb: 100, 100, 100;
72 | --card-border-rgb: 200, 200, 200;
73 | }
74 | }
75 |
76 | * {
77 | box-sizing: border-box;
78 | padding: 0;
79 | margin: 0;
80 | }
81 |
82 | html,
83 | body {
84 | max-width: 100vw;
85 | overflow-x: hidden;
86 | }
87 |
88 | body {
89 | color: rgb(var(--foreground-rgb));
90 | background: linear-gradient(
91 | to bottom,
92 | transparent,
93 | rgb(var(--background-end-rgb))
94 | )
95 | rgb(var(--background-start-rgb));
96 | }
97 |
98 | a {
99 | color: inherit;
100 | text-decoration: none;
101 | }
102 |
103 | @media (prefers-color-scheme: dark) {
104 | html {
105 | color-scheme: dark;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/website/theme.config.jsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router'
2 |
3 | export default {
4 | logo: libxev ,
5 | docsRepositoryBase: 'https://github.com/mitchellh/libxev/blob/website/pages',
6 |
7 | project: {
8 | link: 'https://github.com/mitchellh/libxev',
9 | },
10 |
11 | feedback: {
12 | content: null,
13 | },
14 |
15 | footer: {
16 | text:
17 | © {new Date().getFullYear()}
18 | libxev
19 | ,
20 | },
21 |
22 | useNextSeoProps() {
23 | const { route } = useRouter()
24 | if (route !== '/') {
25 | return {
26 | titleTemplate: '%s – libxev'
27 | }
28 | }
29 | },
30 |
31 | // Hide the last updated date.
32 | gitTimestamp: ,
33 | }
34 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "@/*": ["./*"]
20 | }
21 | },
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
23 | "exclude": ["node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------