├── .envrc ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.zig ├── flake.lock ├── flake.nix ├── shell.nix └── src ├── Async.zig ├── Cond.zig ├── Idle.zig ├── Loop.zig ├── Mutex.zig ├── Pipe.zig ├── Prepare.zig ├── Sem.zig ├── Thread.zig ├── Timer.zig ├── Tty.zig ├── c.zig ├── cimport_linux.zig ├── cimport_macos.zig ├── error.zig ├── handle.zig ├── main.zig ├── stream.zig └── tests.zig /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/libuv"] 2 | path = vendor/libuv 3 | url = https://github.com/libuv/libuv.git 4 | -------------------------------------------------------------------------------- /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 |
2 | 3 | **⚠️ Project Archived ⚠️.** This project is archived. I used this for a few 4 | real projects since 2021 and it was stable, but I've since moved onto using 5 | [libxev](https://github.com/mitchellh/libxev) so I'm no longer maintaining 6 | the libuv bindings. 7 | 8 |
9 | 10 | # zig-libuv 11 | 12 | zig-libuv contains a `build.zig` that can build libuv using Zig and also 13 | contains a package with Zig bindings. Both can be used together or separately. 14 | Building libuv with Zig enables easy cross-compilation. The bindings allow 15 | you to consume libuv easily whether it is built with Zig or not. 16 | 17 | ## Example 18 | 19 | There are lots of examples in the tests for each individual handle type. 20 | Below is an example of using a timer, copied exactly from the tests: 21 | 22 | ```zig 23 | var loop = try Loop.init(testing.allocator); 24 | defer loop.deinit(testing.allocator); 25 | 26 | var timer = try init(testing.allocator, loop); 27 | defer timer.deinit(testing.allocator); 28 | 29 | var called: bool = false; 30 | timer.setData(&called); 31 | try timer.start((struct { 32 | fn callback(t: *Timer) void { 33 | t.getData(bool).?.* = true; 34 | t.close(null); 35 | } 36 | }).callback, 10, 1000); 37 | 38 | _ = try loop.run(.default); 39 | 40 | try testing.expect(called); 41 | ``` 42 | 43 | ## Usage 44 | 45 | To **build libuv:** 46 | 47 | ```zig 48 | const libuv = @import("path/to/zig-libuv/build.zig"); 49 | 50 | pub fn build(b: *std.build.Builder) !void { 51 | // ... 52 | 53 | const exe = b.addExecutable("my-program", "src/main.zig"); 54 | _ = libuv.link(b, exe); 55 | } 56 | ``` 57 | 58 | To **use the Zig bindings**, add the package: 59 | 60 | ```zig 61 | exe.addPackage(libuv.pkg); 62 | ``` 63 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | /// Directories with our includes. 4 | const root = thisDir() ++ "/vendor/libuv/"; 5 | const include_path = root ++ "include"; 6 | 7 | pub const pkg = std.build.Pkg{ 8 | .name = "libuv", 9 | .source = .{ .path = thisDir() ++ "/src/main.zig" }, 10 | }; 11 | 12 | fn thisDir() []const u8 { 13 | return std.fs.path.dirname(@src().file) orelse "."; 14 | } 15 | 16 | pub fn build(b: *std.build.Builder) !void { 17 | const target = b.standardTargetOptions(.{}); 18 | const optimize = b.standardOptimizeOption(.{}); 19 | 20 | const tests = b.addTest(.{ 21 | .name = "pixman-test", 22 | .kind = .test_exe, 23 | .root_source_file = .{ .path = "src/main.zig" }, 24 | .target = target, 25 | .optimize = optimize, 26 | }); 27 | _ = try link(b, tests); 28 | tests.install(); 29 | 30 | const test_step = b.step("test", "Run tests"); 31 | const tests_run = tests.run(); 32 | test_step.dependOn(&tests_run.step); 33 | } 34 | 35 | pub fn link(b: *std.build.Builder, step: *std.build.LibExeObjStep) !*std.build.LibExeObjStep { 36 | const libuv = try buildLibuv(b, step); 37 | step.linkLibrary(libuv); 38 | step.addIncludePath(include_path); 39 | return libuv; 40 | } 41 | 42 | pub fn buildLibuv( 43 | b: *std.build.Builder, 44 | step: *std.build.LibExeObjStep, 45 | ) !*std.build.LibExeObjStep { 46 | const target = step.target; 47 | const lib = b.addStaticLibrary(.{ 48 | .name = "uv", 49 | .target = target, 50 | .optimize = step.optimize, 51 | }); 52 | 53 | // Include dirs 54 | lib.addIncludePath(include_path); 55 | lib.addIncludePath(root ++ "src"); 56 | 57 | // Links 58 | if (target.isWindows()) { 59 | lib.linkSystemLibrary("psapi"); 60 | lib.linkSystemLibrary("user32"); 61 | lib.linkSystemLibrary("advapi32"); 62 | lib.linkSystemLibrary("iphlpapi"); 63 | lib.linkSystemLibrary("userenv"); 64 | lib.linkSystemLibrary("ws2_32"); 65 | } 66 | if (target.isLinux()) { 67 | lib.linkSystemLibrary("pthread"); 68 | } 69 | lib.linkLibC(); 70 | 71 | // Compilation 72 | var flags = std.ArrayList([]const u8).init(b.allocator); 73 | defer flags.deinit(); 74 | // try flags.appendSlice(&.{}); 75 | 76 | if (!target.isWindows()) { 77 | try flags.appendSlice(&.{ 78 | "-D_FILE_OFFSET_BITS=64", 79 | "-D_LARGEFILE_SOURCE", 80 | }); 81 | } 82 | 83 | if (target.isLinux()) { 84 | try flags.appendSlice(&.{ 85 | "-D_GNU_SOURCE", 86 | "-D_POSIX_C_SOURCE=200112", 87 | }); 88 | } 89 | 90 | if (target.isDarwin()) { 91 | try flags.appendSlice(&.{ 92 | "-D_DARWIN_UNLIMITED_SELECT=1", 93 | "-D_DARWIN_USE_64_BIT_INODE=1", 94 | }); 95 | } 96 | 97 | // C files common to all platforms 98 | lib.addCSourceFiles(&.{ 99 | root ++ "src/fs-poll.c", 100 | root ++ "src/idna.c", 101 | root ++ "src/inet.c", 102 | root ++ "src/random.c", 103 | root ++ "src/strscpy.c", 104 | root ++ "src/strtok.c", 105 | root ++ "src/threadpool.c", 106 | root ++ "src/timer.c", 107 | root ++ "src/uv-common.c", 108 | root ++ "src/uv-data-getter-setters.c", 109 | root ++ "src/version.c", 110 | }, flags.items); 111 | 112 | if (!target.isWindows()) { 113 | lib.addCSourceFiles(&.{ 114 | root ++ "src/unix/async.c", 115 | root ++ "src/unix/core.c", 116 | root ++ "src/unix/dl.c", 117 | root ++ "src/unix/fs.c", 118 | root ++ "src/unix/getaddrinfo.c", 119 | root ++ "src/unix/getnameinfo.c", 120 | root ++ "src/unix/loop-watcher.c", 121 | root ++ "src/unix/loop.c", 122 | root ++ "src/unix/pipe.c", 123 | root ++ "src/unix/poll.c", 124 | root ++ "src/unix/process.c", 125 | root ++ "src/unix/random-devurandom.c", 126 | root ++ "src/unix/signal.c", 127 | root ++ "src/unix/stream.c", 128 | root ++ "src/unix/tcp.c", 129 | root ++ "src/unix/thread.c", 130 | root ++ "src/unix/tty.c", 131 | root ++ "src/unix/udp.c", 132 | }, flags.items); 133 | } 134 | 135 | if (target.isLinux() or target.isDarwin()) { 136 | lib.addCSourceFiles(&.{ 137 | root ++ "src/unix/proctitle.c", 138 | }, flags.items); 139 | } 140 | 141 | if (target.isLinux()) { 142 | lib.addCSourceFiles(&.{ 143 | root ++ "src/unix/linux.c", 144 | root ++ "src/unix/procfs-exepath.c", 145 | root ++ "src/unix/random-getrandom.c", 146 | root ++ "src/unix/random-sysctl-linux.c", 147 | }, flags.items); 148 | } 149 | 150 | if (target.isDarwin() or 151 | target.isOpenBSD() or 152 | target.isNetBSD() or 153 | target.isFreeBSD() or 154 | target.isDragonFlyBSD()) 155 | { 156 | lib.addCSourceFiles(&.{ 157 | root ++ "src/unix/bsd-ifaddrs.c", 158 | root ++ "src/unix/kqueue.c", 159 | }, flags.items); 160 | } 161 | 162 | if (target.isDarwin() or target.isOpenBSD()) { 163 | lib.addCSourceFiles(&.{ 164 | root ++ "src/unix/random-getentropy.c", 165 | }, flags.items); 166 | } 167 | 168 | if (target.isDarwin()) { 169 | lib.addCSourceFiles(&.{ 170 | root ++ "src/unix/darwin-proctitle.c", 171 | root ++ "src/unix/darwin.c", 172 | root ++ "src/unix/fsevents.c", 173 | }, flags.items); 174 | } 175 | 176 | return lib; 177 | } 178 | -------------------------------------------------------------------------------- /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": 1673956053, 23 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 24 | "owner": "edolstra", 25 | "repo": "flake-compat", 26 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 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 | "locked": { 52 | "lastModified": 1659877975, 53 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 54 | "owner": "numtide", 55 | "repo": "flake-utils", 56 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 57 | "type": "github" 58 | }, 59 | "original": { 60 | "owner": "numtide", 61 | "repo": "flake-utils", 62 | "type": "github" 63 | } 64 | }, 65 | "nixpkgs": { 66 | "locked": { 67 | "lastModified": 1672580127, 68 | "narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=", 69 | "owner": "nixos", 70 | "repo": "nixpkgs", 71 | "rev": "0874168639713f547c05947c76124f78441ea46c", 72 | "type": "github" 73 | }, 74 | "original": { 75 | "owner": "nixos", 76 | "ref": "release-22.05", 77 | "repo": "nixpkgs", 78 | "type": "github" 79 | } 80 | }, 81 | "nixpkgs_2": { 82 | "locked": { 83 | "lastModified": 1661151577, 84 | "narHash": "sha256-++S0TuJtuz9IpqP8rKktWyHZKpgdyrzDFUXVY07MTRI=", 85 | "owner": "NixOS", 86 | "repo": "nixpkgs", 87 | "rev": "54060e816971276da05970a983487a25810c38a7", 88 | "type": "github" 89 | }, 90 | "original": { 91 | "owner": "NixOS", 92 | "ref": "nixpkgs-unstable", 93 | "repo": "nixpkgs", 94 | "type": "github" 95 | } 96 | }, 97 | "root": { 98 | "inputs": { 99 | "flake-compat": "flake-compat", 100 | "flake-utils": "flake-utils", 101 | "nixpkgs": "nixpkgs", 102 | "zig": "zig" 103 | } 104 | }, 105 | "zig": { 106 | "inputs": { 107 | "flake-compat": "flake-compat_2", 108 | "flake-utils": "flake-utils_2", 109 | "nixpkgs": "nixpkgs_2" 110 | }, 111 | "locked": { 112 | "lastModified": 1675685322, 113 | "narHash": "sha256-iQLyJcfDd5+mRF8b9sT4R/tLhwkTYOxKoaqgoAovlKo=", 114 | "owner": "mitchellh", 115 | "repo": "zig-overlay", 116 | "rev": "2c7f714a6c7fdc85289d3dc201510ea7396656b5", 117 | "type": "github" 118 | }, 119 | "original": { 120 | "owner": "mitchellh", 121 | "repo": "zig-overlay", 122 | "type": "github" 123 | } 124 | } 125 | }, 126 | "root": "root", 127 | "version": 7 128 | } 129 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "An empty project that uses Zig."; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | zig.url = "github:mitchellh/zig-overlay"; 8 | 9 | # Used for shell.nix 10 | flake-compat = { 11 | url = github:edolstra/flake-compat; 12 | flake = false; 13 | }; 14 | }; 15 | 16 | outputs = { 17 | self, 18 | nixpkgs, 19 | flake-utils, 20 | ... 21 | } @ inputs: let 22 | overlays = [ 23 | # Other overlays 24 | (final: prev: { 25 | zigpkgs = inputs.zig.packages.${prev.system}; 26 | }) 27 | ]; 28 | 29 | # Our supported systems are the same supported systems as the Zig binaries 30 | systems = builtins.attrNames inputs.zig.packages; 31 | in 32 | flake-utils.lib.eachSystem systems ( 33 | system: let 34 | pkgs = import nixpkgs {inherit overlays system;}; 35 | in rec { 36 | devShells.default = pkgs.mkShell { 37 | nativeBuildInputs = with pkgs; [ 38 | zigpkgs.master 39 | ]; 40 | }; 41 | 42 | # For compatibility with older versions of the `nix` binary 43 | devShell = self.devShells.${system}.default; 44 | } 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /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/Async.zig: -------------------------------------------------------------------------------- 1 | //! Async handles allow the user to “wakeup” the event loop and get a callback 2 | //! called from another thread. 3 | const Async = @This(); 4 | 5 | const std = @import("std"); 6 | const Allocator = std.mem.Allocator; 7 | const testing = std.testing; 8 | const c = @import("c.zig"); 9 | const errors = @import("error.zig"); 10 | const Loop = @import("Loop.zig"); 11 | const Handle = @import("handle.zig").Handle; 12 | 13 | handle: *c.uv_async_t, 14 | 15 | pub usingnamespace Handle(Async); 16 | 17 | pub fn init(alloc: Allocator, loop: Loop, comptime cb: fn (*Async) void) !Async { 18 | var handle = try alloc.create(c.uv_async_t); 19 | errdefer alloc.destroy(handle); 20 | 21 | const Wrapper = struct { 22 | pub fn callback(arg: [*c]c.uv_async_t) callconv(.C) void { 23 | var newSelf: Async = .{ .handle = arg }; 24 | @call(.always_inline, cb, .{&newSelf}); 25 | } 26 | }; 27 | 28 | try errors.convertError(c.uv_async_init(loop.loop, handle, Wrapper.callback)); 29 | return Async{ .handle = handle }; 30 | } 31 | 32 | pub fn deinit(self: *Async, alloc: Allocator) void { 33 | alloc.destroy(self.handle); 34 | self.* = undefined; 35 | } 36 | 37 | /// Wake up the event loop and call the async handle’s callback. 38 | pub fn send(self: Async) !void { 39 | try errors.convertError(c.uv_async_send(self.handle)); 40 | } 41 | 42 | test "Async" { 43 | var loop = try Loop.init(testing.allocator); 44 | defer loop.deinit(testing.allocator); 45 | var h = try init(testing.allocator, loop, (struct { 46 | fn callback(v: *Async) void { 47 | v.close(null); 48 | } 49 | }).callback); 50 | defer h.deinit(testing.allocator); 51 | 52 | try h.send(); 53 | _ = try loop.run(.default); 54 | } 55 | -------------------------------------------------------------------------------- /src/Cond.zig: -------------------------------------------------------------------------------- 1 | //! Condition variables implemented via libuv. 2 | const Cond = @This(); 3 | 4 | const std = @import("std"); 5 | const Allocator = std.mem.Allocator; 6 | const testing = std.testing; 7 | const c = @import("c.zig"); 8 | const errors = @import("error.zig"); 9 | const Mutex = @import("Mutex.zig"); 10 | 11 | cond: *c.uv_cond_t, 12 | 13 | pub fn init(alloc: Allocator) !Cond { 14 | const cond = try alloc.create(c.uv_cond_t); 15 | try errors.convertError(c.uv_cond_init(cond)); 16 | return Cond{ .cond = cond }; 17 | } 18 | 19 | pub fn deinit(self: *Cond, alloc: Allocator) void { 20 | c.uv_cond_destroy(self.cond); 21 | alloc.destroy(self.cond); 22 | self.* = undefined; 23 | } 24 | 25 | pub fn signal(self: Cond) void { 26 | c.uv_cond_signal(self.cond); 27 | } 28 | 29 | pub fn broadcast(self: Cond) void { 30 | c.uv_cond_broadcast(self.cond); 31 | } 32 | 33 | pub fn wait(self: Cond, mutex: Mutex) void { 34 | c.uv_cond_wait(self.cond, mutex.mutex); 35 | } 36 | 37 | pub fn timedwait(self: Cond, mutex: Mutex, timeout: u64) c_int { 38 | return c.uv_cond_timedwait(self.cond, mutex.mutex, timeout); 39 | } 40 | 41 | test { 42 | var cond = try init(testing.allocator); 43 | defer cond.deinit(testing.allocator); 44 | } 45 | -------------------------------------------------------------------------------- /src/Idle.zig: -------------------------------------------------------------------------------- 1 | //! Idle handles will run the given callback once per loop iteration, right 2 | //! before the uv_prepare_t handles. 3 | const Idle = @This(); 4 | 5 | const std = @import("std"); 6 | const Allocator = std.mem.Allocator; 7 | const testing = std.testing; 8 | const c = @import("c.zig"); 9 | const errors = @import("error.zig"); 10 | const Loop = @import("Loop.zig"); 11 | const Handle = @import("handle.zig").Handle; 12 | 13 | handle: *c.uv_idle_t, 14 | 15 | pub usingnamespace Handle(Idle); 16 | 17 | pub fn init(alloc: Allocator, loop: Loop) !Idle { 18 | var handle = try alloc.create(c.uv_idle_t); 19 | errdefer alloc.destroy(handle); 20 | 21 | try errors.convertError(c.uv_idle_init(loop.loop, handle)); 22 | return Idle{ .handle = handle }; 23 | } 24 | 25 | pub fn deinit(self: *Idle, alloc: Allocator) void { 26 | alloc.destroy(self.handle); 27 | self.* = undefined; 28 | } 29 | 30 | /// Start the handle with the given callback. This function always succeeds, 31 | /// except when cb is NULL. 32 | pub fn start(self: Idle, comptime cb: fn (*Idle) void) !void { 33 | const Wrapper = struct { 34 | pub fn callback(arg: [*c]c.uv_idle_t) callconv(.C) void { 35 | var newSelf: Idle = .{ .handle = arg }; 36 | @call(.always_inline, cb, .{&newSelf}); 37 | } 38 | }; 39 | 40 | try errors.convertError(c.uv_idle_start(self.handle, Wrapper.callback)); 41 | } 42 | 43 | /// Stop the handle, the callback will no longer be called. 44 | pub fn stop(self: Idle) !void { 45 | try errors.convertError(c.uv_idle_stop(self.handle)); 46 | } 47 | 48 | test "Idle" { 49 | var loop = try Loop.init(testing.allocator); 50 | defer loop.deinit(testing.allocator); 51 | var h = try init(testing.allocator, loop); 52 | defer h.deinit(testing.allocator); 53 | 54 | var called: bool = false; 55 | h.setData(&called); 56 | try h.start((struct { 57 | fn callback(t: *Idle) void { 58 | t.getData(bool).?.* = true; 59 | t.close(null); 60 | } 61 | }).callback); 62 | 63 | _ = try loop.run(.default); 64 | 65 | try testing.expect(called); 66 | } 67 | -------------------------------------------------------------------------------- /src/Loop.zig: -------------------------------------------------------------------------------- 1 | const Loop = @This(); 2 | 3 | const std = @import("std"); 4 | const Allocator = std.mem.Allocator; 5 | const testing = std.testing; 6 | const c = @import("c.zig"); 7 | const errors = @import("error.zig"); 8 | 9 | loop: *c.uv_loop_t, 10 | 11 | /// Initialize a new uv_loop. 12 | pub fn init(alloc: Allocator) !Loop { 13 | // The uv_loop_t type MUST be heap allocated and must not be copied. 14 | // I can't find a definitive source on this, but the test suite starts 15 | // hanging in weird places and doing bad things when it is copied. 16 | const loop = try alloc.create(c.uv_loop_t); 17 | try errors.convertError(c.uv_loop_init(loop)); 18 | return Loop{ .loop = loop }; 19 | } 20 | 21 | /// Releases all internal loop resources. Call this function only when the 22 | /// loop has finished executing and all open handles and requests have been 23 | /// closed, or this will silently fail (in debug mode it will panic). 24 | pub fn deinit(self: *Loop, alloc: Allocator) void { 25 | // deinit functions idiomatically cannot fail in Zig, so we do the 26 | // next best thing here and assert so that in debug mode you'll get 27 | // a crash. 28 | std.debug.assert(c.uv_loop_close(self.loop) >= 0); 29 | alloc.destroy(self.loop); 30 | self.* = undefined; 31 | } 32 | 33 | /// Returns true if the loop is still alive. 34 | pub fn alive(self: Loop) !bool { 35 | const res = c.uv_loop_alive(self.loop); 36 | try errors.convertError(res); 37 | return res > 0; 38 | } 39 | 40 | /// This function runs the event loop. See RunMode for mode documentation. 41 | /// 42 | /// This is not reentrant. It must not be called from a callback. 43 | pub fn run(self: Loop, mode: RunMode) !u32 { 44 | const res = c.uv_run(self.loop, @enumToInt(mode)); 45 | try errors.convertError(res); 46 | return @intCast(u32, res); 47 | } 48 | 49 | /// Stop the event loop, causing uv_run() to end as soon as possible. This 50 | /// will happen not sooner than the next loop iteration. If this function was 51 | /// called before blocking for i/o, the loop won’t block for i/o on this iteration. 52 | pub fn stop(self: Loop) void { 53 | c.uv_stop(self.loop); 54 | } 55 | 56 | /// Get backend file descriptor. Only kqueue, epoll and event ports are supported. 57 | /// 58 | /// This can be used in conjunction with uv_run(loop, UV_RUN_NOWAIT) to poll 59 | /// in one thread and run the event loop’s callbacks in another see 60 | /// test/test-embed.c for an example. 61 | pub fn backendFd(self: Loop) !c_int { 62 | const res = c.uv_backend_fd(self.loop); 63 | try errors.convertError(res); 64 | return res; 65 | } 66 | 67 | /// Get the poll timeout. The return value is in milliseconds, or -1 for no 68 | /// timeout. 69 | pub fn backendTimeout(self: Loop) c_int { 70 | return c.uv_backend_timeout(self.loop); 71 | } 72 | 73 | /// Return the current timestamp in milliseconds. The timestamp is cached at 74 | /// the start of the event loop tick, see uv_update_time() for details and rationale. 75 | /// 76 | /// The timestamp increases monotonically from some arbitrary point in time. 77 | /// Don’t make assumptions about the starting point, you will only get disappointed. 78 | pub fn now(self: Loop) u64 { 79 | return c.uv_now(self.loop); 80 | } 81 | 82 | /// Update the event loop’s concept of “now”. Libuv caches the current time at 83 | /// the start of the event loop tick in order to reduce the number of time-related 84 | /// system calls. 85 | /// 86 | /// You won’t normally need to call this function unless you have callbacks 87 | /// that block the event loop for longer periods of time, where “longer” is 88 | /// somewhat subjective but probably on the order of a millisecond or more. 89 | pub fn updateTime(self: Loop) void { 90 | return c.uv_update_time(self.loop); 91 | } 92 | 93 | /// Sets loop->data to data. 94 | pub fn setData(self: Loop, pointer: ?*anyopaque) void { 95 | c.uv_loop_set_data(self.loop, pointer); 96 | } 97 | 98 | /// Returns loop->data. 99 | pub fn getData(self: Loop, comptime DT: type) ?*DT { 100 | return if (c.uv_loop_get_data(self.loop)) |ptr| 101 | @ptrCast(?*DT, @alignCast(@alignOf(DT), ptr)) 102 | else 103 | null; 104 | } 105 | 106 | /// Mode used to run the loop with uv_run(). 107 | pub const RunMode = enum(c.uv_run_mode) { 108 | default = c.UV_RUN_DEFAULT, 109 | once = c.UV_RUN_ONCE, 110 | nowait = c.UV_RUN_NOWAIT, 111 | }; 112 | 113 | test { 114 | var loop = try init(testing.allocator); 115 | defer loop.deinit(testing.allocator); 116 | 117 | var data: u8 = 42; 118 | loop.setData(&data); 119 | try testing.expect(loop.getData(u8).?.* == 42); 120 | 121 | try testing.expect((try loop.backendFd()) > 0); 122 | try testing.expectEqual(@as(u32, 0), try loop.run(.nowait)); 123 | } 124 | -------------------------------------------------------------------------------- /src/Mutex.zig: -------------------------------------------------------------------------------- 1 | //! Mutexes implemented via libuv. 2 | const Mutex = @This(); 3 | 4 | const std = @import("std"); 5 | const Allocator = std.mem.Allocator; 6 | const testing = std.testing; 7 | const c = @import("c.zig"); 8 | const errors = @import("error.zig"); 9 | 10 | mutex: *c.uv_mutex_t, 11 | 12 | pub fn init(alloc: Allocator) !Mutex { 13 | const mutex = try alloc.create(c.uv_mutex_t); 14 | try errors.convertError(c.uv_mutex_init(mutex)); 15 | return Mutex{ .mutex = mutex }; 16 | } 17 | 18 | pub fn deinit(self: *Mutex, alloc: Allocator) void { 19 | c.uv_mutex_destroy(self.mutex); 20 | alloc.destroy(self.mutex); 21 | self.* = undefined; 22 | } 23 | 24 | pub fn lock(self: Mutex) void { 25 | c.uv_mutex_lock(self.mutex); 26 | } 27 | 28 | pub fn unlock(self: Mutex) void { 29 | c.uv_mutex_unlock(self.mutex); 30 | } 31 | 32 | test { 33 | var mutex = try init(testing.allocator); 34 | defer mutex.deinit(testing.allocator); 35 | } 36 | -------------------------------------------------------------------------------- /src/Pipe.zig: -------------------------------------------------------------------------------- 1 | //! Pipe handles provide an abstraction over streaming files on Unix 2 | //! (including local domain sockets, pipes, and FIFOs) and named pipes on 3 | //! Windows. 4 | const Pipe = @This(); 5 | 6 | const std = @import("std"); 7 | const Allocator = std.mem.Allocator; 8 | const testing = std.testing; 9 | const c = @import("c.zig"); 10 | const errors = @import("error.zig"); 11 | const Loop = @import("Loop.zig"); 12 | const Handle = @import("handle.zig").Handle; 13 | const stream = @import("stream.zig"); 14 | const Stream = stream.Stream; 15 | const WriteReq = stream.WriteReq; 16 | 17 | handle: *c.uv_pipe_t, 18 | 19 | pub usingnamespace Handle(Pipe); 20 | pub usingnamespace Stream(Pipe); 21 | 22 | /// Valid flags for pipe. 23 | pub const Flags = packed struct { 24 | _ignore: u6 = 0, 25 | nonblock: bool = false, // UV_NONBLOCK_PIPE = 0x40 26 | _ignore_high: u1 = 0, 27 | 28 | pub inline fn toInt(self: Flags, comptime IntType: type) IntType { 29 | return @intCast(IntType, @bitCast(u8, self)); 30 | } 31 | 32 | test "Flags: expected value" { 33 | const f: Flags = .{ .nonblock = true }; 34 | try testing.expectEqual(c.UV_NONBLOCK_PIPE, f.toInt(c_int)); 35 | } 36 | }; 37 | 38 | /// Pair is a pair of ends to a single pipe. 39 | pub const Pair = struct { 40 | read: c.uv_file, 41 | write: c.uv_file, 42 | }; 43 | 44 | /// Create a pair of connected pipe handles. Data may be written to fds[1] and 45 | /// read from fds[0]. The resulting handles can be passed to uv_pipe_open, 46 | /// used with uv_spawn, or for any other purpose. 47 | pub fn pipe(read_flags: Flags, write_flags: Flags) !Pair { 48 | var res: [2]c.uv_file = undefined; 49 | try errors.convertError(c.uv_pipe( 50 | &res, 51 | read_flags.toInt(c_int), 52 | write_flags.toInt(c_int), 53 | )); 54 | return Pair{ .read = res[0], .write = res[1] }; 55 | } 56 | 57 | pub fn init(alloc: Allocator, loop: Loop, ipc: bool) !Pipe { 58 | var handle = try alloc.create(c.uv_pipe_t); 59 | errdefer alloc.destroy(handle); 60 | try errors.convertError(c.uv_pipe_init(loop.loop, handle, @boolToInt(ipc))); 61 | return Pipe{ .handle = handle }; 62 | } 63 | 64 | pub fn deinit(self: *Pipe, alloc: Allocator) void { 65 | alloc.destroy(self.handle); 66 | self.* = undefined; 67 | } 68 | 69 | /// Open an existing file descriptor or HANDLE as a pipe. 70 | pub fn open(self: Pipe, file: c.uv_file) !void { 71 | try errors.convertError(c.uv_pipe_open(self.handle, file)); 72 | } 73 | 74 | test { 75 | _ = Flags; 76 | } 77 | 78 | test "Pipe" { 79 | const pair = try pipe(.{ .nonblock = true }, .{ .nonblock = true }); 80 | 81 | var loop = try Loop.init(testing.allocator); 82 | defer loop.deinit(testing.allocator); 83 | 84 | // Read side 85 | var reader = try init(testing.allocator, loop, false); 86 | defer reader.deinit(testing.allocator); 87 | 88 | try reader.open(pair.read); 89 | try testing.expect(try reader.isReadable()); 90 | try testing.expect(!try reader.isWritable()); 91 | 92 | // Write side 93 | var writer = try init(testing.allocator, loop, false); 94 | defer writer.deinit(testing.allocator); 95 | 96 | try writer.open(pair.write); 97 | try testing.expect(!try writer.isReadable()); 98 | try testing.expect(try writer.isWritable()); 99 | 100 | // Set our data that we'll use to assert 101 | var data: TestData = .{}; 102 | defer data.deinit(); 103 | writer.setData(&data); 104 | 105 | // Write 106 | var writeReq = try WriteReq.init(testing.allocator); 107 | defer writeReq.deinit(testing.allocator); 108 | 109 | try writer.write( 110 | writeReq, 111 | &[_][]const u8{ 112 | "hello", 113 | }, 114 | TestData.write, 115 | ); 116 | 117 | // Run write and verify success 118 | _ = try loop.run(.once); 119 | try testing.expectEqual(@as(u8, 1), data.count); 120 | try testing.expectEqual(@as(i32, 0), data.status); 121 | 122 | // Read 123 | try reader.readStart(TestData.alloc, TestData.read); 124 | reader.setData(&data); 125 | _ = try loop.run(.once); 126 | 127 | // Check our data 128 | try testing.expectEqual(@as(usize, 5), data.data.items.len); 129 | try testing.expectEqualStrings("hello", data.data.items); 130 | data.data.clearRetainingCapacity(); 131 | 132 | // Try writing directly 133 | _ = try writer.tryWrite(&[_][]const u8{"world"}); 134 | _ = try loop.run(.once); 135 | try testing.expectEqual(@as(usize, 5), data.data.items.len); 136 | try testing.expectEqualStrings("world", data.data.items); 137 | 138 | // End 139 | reader.readStop(); 140 | reader.close(null); 141 | writer.close(null); 142 | _ = try loop.run(.default); 143 | } 144 | 145 | /// Logic for testing read/write of pipes. 146 | const TestData = struct { 147 | count: u8 = 0, 148 | status: i32 = 0, 149 | data: std.ArrayListUnmanaged(u8) = .{}, 150 | 151 | fn deinit(self: *TestData) void { 152 | self.data.deinit(testing.allocator); 153 | self.* = undefined; 154 | } 155 | 156 | fn write(req: *WriteReq, status: i32) void { 157 | var data = req.handle(Pipe).?.getData(TestData).?; 158 | data.count += 1; 159 | data.status = status; 160 | } 161 | 162 | fn alloc(_: *Pipe, size: usize) ?[]u8 { 163 | return testing.allocator.alloc(u8, size) catch null; 164 | } 165 | 166 | fn read(h: *Pipe, n: isize, buf: []const u8) void { 167 | var data = h.getData(TestData).?; 168 | data.data.appendSlice( 169 | testing.allocator, 170 | buf[0..@intCast(usize, n)], 171 | ) catch unreachable; 172 | testing.allocator.free(buf); 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /src/Prepare.zig: -------------------------------------------------------------------------------- 1 | //! Prepare handles will run the given callback once per loop iteration, right 2 | //! before polling for i/o. 3 | const Prepare = @This(); 4 | 5 | const std = @import("std"); 6 | const Allocator = std.mem.Allocator; 7 | const testing = std.testing; 8 | const c = @import("c.zig"); 9 | const errors = @import("error.zig"); 10 | const Loop = @import("Loop.zig"); 11 | const Handle = @import("handle.zig").Handle; 12 | 13 | handle: *c.uv_prepare_t, 14 | 15 | pub usingnamespace Handle(Prepare); 16 | 17 | pub fn init(alloc: Allocator, loop: Loop) !Prepare { 18 | var handle = try alloc.create(c.uv_prepare_t); 19 | errdefer alloc.destroy(handle); 20 | 21 | try errors.convertError(c.uv_prepare_init(loop.loop, handle)); 22 | return Prepare{ .handle = handle }; 23 | } 24 | 25 | pub fn deinit(self: *Prepare, alloc: Allocator) void { 26 | alloc.destroy(self.handle); 27 | self.* = undefined; 28 | } 29 | 30 | /// Start the handle with the given callback. This function always succeeds, 31 | /// except when cb is NULL. 32 | pub fn start(self: Prepare, comptime cb: fn (*Prepare) void) !void { 33 | const Wrapper = struct { 34 | pub fn callback(arg: [*c]c.uv_prepare_t) callconv(.C) void { 35 | var newSelf: Prepare = .{ .handle = arg }; 36 | @call(.always_inline, cb, .{&newSelf}); 37 | } 38 | }; 39 | 40 | try errors.convertError(c.uv_prepare_start(self.handle, Wrapper.callback)); 41 | } 42 | 43 | /// Stop the handle, the callback will no longer be called. 44 | pub fn stop(self: Prepare) !void { 45 | try errors.convertError(c.uv_prepare_stop(self.handle)); 46 | } 47 | 48 | test "Prepare" { 49 | var loop = try Loop.init(testing.allocator); 50 | defer loop.deinit(testing.allocator); 51 | var h = try init(testing.allocator, loop); 52 | defer h.deinit(testing.allocator); 53 | 54 | var called: bool = false; 55 | h.setData(&called); 56 | try h.start((struct { 57 | fn callback(t: *Prepare) void { 58 | t.getData(bool).?.* = true; 59 | t.close(null); 60 | } 61 | }).callback); 62 | 63 | _ = try loop.run(.default); 64 | 65 | try testing.expect(called); 66 | } 67 | -------------------------------------------------------------------------------- /src/Sem.zig: -------------------------------------------------------------------------------- 1 | //! Semaphores implemented via libuv. 2 | const Sem = @This(); 3 | 4 | const std = @import("std"); 5 | const Allocator = std.mem.Allocator; 6 | const testing = std.testing; 7 | const c = @import("c.zig"); 8 | const errors = @import("error.zig"); 9 | 10 | sem: *c.uv_sem_t, 11 | 12 | pub fn init(alloc: Allocator, value: u32) !Sem { 13 | const sem = try alloc.create(c.uv_sem_t); 14 | try errors.convertError(c.uv_sem_init(sem, value)); 15 | return Sem{ .sem = sem }; 16 | } 17 | 18 | pub fn deinit(self: *Sem, alloc: Allocator) void { 19 | c.uv_sem_destroy(self.sem); 20 | alloc.destroy(self.sem); 21 | self.* = undefined; 22 | } 23 | 24 | pub fn post(self: Sem) void { 25 | c.uv_sem_post(self.sem); 26 | } 27 | 28 | pub fn wait(self: Sem) void { 29 | c.uv_sem_wait(self.sem); 30 | } 31 | 32 | test { 33 | var sem = try init(testing.allocator, 0); 34 | defer sem.deinit(testing.allocator); 35 | } 36 | -------------------------------------------------------------------------------- /src/Thread.zig: -------------------------------------------------------------------------------- 1 | //! Threading implemented by libuv. 2 | const Thread = @This(); 3 | 4 | const std = @import("std"); 5 | const Allocator = std.mem.Allocator; 6 | const testing = std.testing; 7 | const c = @import("c.zig"); 8 | const errors = @import("error.zig"); 9 | 10 | thread: c.uv_thread_t, 11 | 12 | /// Get the current thread 13 | pub fn self() Thread { 14 | return .{ .thread = c.uv_thread_self() }; 15 | } 16 | 17 | /// Initialize a new thread. 18 | pub fn init( 19 | comptime callback: fn () void, 20 | ) !Thread { 21 | const CWrapper = struct { 22 | pub fn wrapper(_: ?*const anyopaque) callconv(.C) void { 23 | @call(.always_inline, callback, .{}); 24 | } 25 | }; 26 | 27 | var res = Thread{ .thread = undefined }; 28 | try errors.convertError(c.uv_thread_create(&res.thread, CWrapper.wrapper, null)); 29 | return res; 30 | } 31 | 32 | /// Initialize a new thread with user data attached. 33 | pub fn initData( 34 | data: anytype, 35 | comptime callback: fn (arg: @TypeOf(data)) void, 36 | ) !Thread { 37 | // Comptime stuff to learn more about our data parameter. This is used 38 | // to do the proper casts for the callback. 39 | const Data = @TypeOf(data); 40 | const dataInfo = @typeInfo(Data); 41 | if (dataInfo != .Pointer) @compileError("data must be a pointer type"); 42 | 43 | const CWrapper = struct { 44 | pub fn wrapper(arg: ?*anyopaque) callconv(.C) void { 45 | @call(.always_inline, callback, .{ 46 | @ptrCast(Data, @alignCast(@alignOf(dataInfo.Pointer.child), arg)), 47 | }); 48 | } 49 | }; 50 | 51 | var res: Thread = .{ .thread = undefined }; 52 | try errors.convertError(c.uv_thread_create( 53 | &res.thread, 54 | CWrapper.wrapper, 55 | data, 56 | )); 57 | return res; 58 | } 59 | 60 | pub fn join(t: *Thread) !void { 61 | try errors.convertError(c.uv_thread_join(&t.thread)); 62 | } 63 | 64 | test "Thread: no data argument" { 65 | count = 0; 66 | var thread = try init(incr); 67 | try thread.join(); 68 | try testing.expectEqual(@as(u8, 1), count); 69 | } 70 | 71 | test "Thread: with data argument" { 72 | count = 0; 73 | var data: u8 = 2; 74 | var thread = try initData(&data, incrBy); 75 | try thread.join(); 76 | try testing.expectEqual(@as(u8, 2), count); 77 | } 78 | 79 | var count: u8 = 0; 80 | 81 | fn incr() void { 82 | count += 1; 83 | } 84 | 85 | fn incrBy(v: *u8) void { 86 | count += v.*; 87 | } 88 | -------------------------------------------------------------------------------- /src/Timer.zig: -------------------------------------------------------------------------------- 1 | //! Timer handles are used to schedule callbacks to be called in the future. 2 | const Timer = @This(); 3 | 4 | const std = @import("std"); 5 | const Allocator = std.mem.Allocator; 6 | const testing = std.testing; 7 | const c = @import("c.zig"); 8 | const errors = @import("error.zig"); 9 | const Loop = @import("Loop.zig"); 10 | const Handle = @import("handle.zig").Handle; 11 | 12 | handle: *c.uv_timer_t, 13 | 14 | pub usingnamespace Handle(Timer); 15 | 16 | pub fn init(alloc: Allocator, loop: Loop) !Timer { 17 | var timer = try alloc.create(c.uv_timer_t); 18 | errdefer alloc.destroy(timer); 19 | try errors.convertError(c.uv_timer_init(loop.loop, timer)); 20 | return Timer{ .handle = timer }; 21 | } 22 | 23 | pub fn deinit(self: *Timer, alloc: Allocator) void { 24 | alloc.destroy(self.handle); 25 | self.* = undefined; 26 | } 27 | 28 | /// Start the timer. timeout and repeat are in milliseconds. 29 | /// 30 | /// If timeout is zero, the callback fires on the next event loop iteration. 31 | /// If repeat is non-zero, the callback fires first after timeout milliseconds 32 | /// and then repeatedly after repeat milliseconds. 33 | pub fn start( 34 | self: Timer, 35 | comptime cb: fn (*Timer) void, 36 | timeout: u64, 37 | repeat: u64, 38 | ) !void { 39 | const Wrapper = struct { 40 | pub fn callback(handle: [*c]c.uv_timer_t) callconv(.C) void { 41 | var newSelf: Timer = .{ .handle = handle }; 42 | @call(.always_inline, cb, .{&newSelf}); 43 | } 44 | }; 45 | 46 | try errors.convertError(c.uv_timer_start( 47 | self.handle, 48 | Wrapper.callback, 49 | timeout, 50 | repeat, 51 | )); 52 | } 53 | 54 | /// Stop the timer, the callback will not be called anymore. 55 | pub fn stop(self: Timer) !void { 56 | try errors.convertError(c.uv_timer_stop(self.handle)); 57 | } 58 | 59 | /// Stop the timer, and if it is repeating restart it using the repeat value 60 | /// as the timeout. If the timer has never been started before it returns UV_EINVAL. 61 | pub fn again(self: Timer) !void { 62 | try errors.convertError(c.uv_timer_again(self.handle)); 63 | } 64 | 65 | /// Get the timer repeat value. 66 | pub fn getRepeat(self: Timer) u64 { 67 | return c.uv_timer_get_repeat(self.handle); 68 | } 69 | 70 | /// Set the repeat interval value in milliseconds. The timer will be scheduled 71 | /// to run on the given interval, regardless of the callback execution duration, 72 | /// and will follow normal timer semantics in the case of a time-slice overrun. 73 | pub fn setRepeat(self: Timer, repeat: u64) void { 74 | c.uv_timer_set_repeat(self.handle, repeat); 75 | } 76 | 77 | test "Timer" { 78 | var loop = try Loop.init(testing.allocator); 79 | defer loop.deinit(testing.allocator); 80 | var timer = try init(testing.allocator, loop); 81 | defer timer.deinit(testing.allocator); 82 | 83 | var called: bool = false; 84 | timer.setData(&called); 85 | try timer.start((struct { 86 | fn callback(t: *Timer) void { 87 | t.getData(bool).?.* = true; 88 | t.close(null); 89 | } 90 | }).callback, 10, 1000); 91 | 92 | _ = try loop.run(.default); 93 | 94 | try testing.expect(called); 95 | } 96 | 97 | test "Timer: close callback" { 98 | var loop = try Loop.init(testing.allocator); 99 | defer loop.deinit(testing.allocator); 100 | var timer = try init(testing.allocator, loop); 101 | defer timer.deinit(testing.allocator); 102 | 103 | var data: u8 = 42; 104 | timer.setData(&data); 105 | timer.close((struct { 106 | fn callback(v: *Timer) void { 107 | var dataPtr = v.getData(u8).?; 108 | dataPtr.* = 24; 109 | } 110 | }).callback); 111 | _ = try loop.run(.default); 112 | 113 | try testing.expectEqual(@as(u8, 24), data); 114 | } 115 | -------------------------------------------------------------------------------- /src/Tty.zig: -------------------------------------------------------------------------------- 1 | //! Tty handles represent a stream for the console. 2 | const Tty = @This(); 3 | 4 | const std = @import("std"); 5 | const fd_t = std.os.fd_t; 6 | const Allocator = std.mem.Allocator; 7 | const testing = std.testing; 8 | const c = @import("c.zig"); 9 | const errors = @import("error.zig"); 10 | const Loop = @import("Loop.zig"); 11 | const Handle = @import("handle.zig").Handle; 12 | const Stream = @import("stream.zig").Stream; 13 | 14 | handle: *c.uv_tty_t, 15 | 16 | pub usingnamespace Handle(Tty); 17 | pub usingnamespace Stream(Tty); 18 | 19 | pub fn init(alloc: Allocator, loop: Loop, fd: fd_t) !Tty { 20 | var tty = try alloc.create(c.uv_tty_t); 21 | errdefer alloc.destroy(tty); 22 | try errors.convertError(c.uv_tty_init(loop.loop, tty, fd, 0)); 23 | return Tty{ .handle = tty }; 24 | } 25 | 26 | pub fn deinit(self: *Tty, alloc: Allocator) void { 27 | alloc.destroy(self.handle); 28 | self.* = undefined; 29 | } 30 | -------------------------------------------------------------------------------- /src/c.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | 3 | pub usingnamespace switch (builtin.zig_backend) { 4 | .stage1 => @cImport({ 5 | @cInclude("uv.h"); 6 | }), 7 | 8 | // Workaround for: 9 | // https://github.com/ziglang/zig/issues/12325 10 | // 11 | // Generated by: 12 | // zig translate-c -target aarch64-macos -lc -Ivendor/libuv/include vendor/libuv/include/uv.h 13 | // (and then manually modified) 14 | else => switch (builtin.os.tag) { 15 | .macos => @import("cimport_macos.zig"), 16 | .linux => @import("cimport_linux.zig"), 17 | else => @compileError("unsupported OS for now, see this line"), 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/error.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const c = @import("c.zig"); 4 | 5 | /// Enum mapping for errors. 6 | pub const Errno = enum(i32) { 7 | E2BIG = c.UV_E2BIG, 8 | EACCES = c.UV_EACCES, 9 | EADDRINUSE = c.UV_EADDRINUSE, 10 | EADDRNOTAVAIL = c.UV_EADDRNOTAVAIL, 11 | EAFNOSUPPORT = c.UV_EAFNOSUPPORT, 12 | EAGAIN = c.UV_EAGAIN, 13 | EAI_ADDRFAMILY = c.UV_EAI_ADDRFAMILY, 14 | EAI_AGAIN = c.UV_EAI_AGAIN, 15 | EAI_BADFLAGS = c.UV_EAI_BADFLAGS, 16 | EAI_BADHINTS = c.UV_EAI_BADHINTS, 17 | EAI_CANCELED = c.UV_EAI_CANCELED, 18 | EAI_FAIL = c.UV_EAI_FAIL, 19 | EAI_FAMILY = c.UV_EAI_FAMILY, 20 | EAI_MEMORY = c.UV_EAI_MEMORY, 21 | EAI_NODATA = c.UV_EAI_NODATA, 22 | EAI_NONAME = c.UV_EAI_NONAME, 23 | EAI_OVERFLOW = c.UV_EAI_OVERFLOW, 24 | EAI_PROTOCOL = c.UV_EAI_PROTOCOL, 25 | EAI_SERVICE = c.UV_EAI_SERVICE, 26 | EAI_SOCKTYPE = c.UV_EAI_SOCKTYPE, 27 | EALREADY = c.UV_EALREADY, 28 | EBADF = c.UV_EBADF, 29 | EBUSY = c.UV_EBUSY, 30 | ECANCELED = c.UV_ECANCELED, 31 | ECHARSET = c.UV_ECHARSET, 32 | ECONNABORTED = c.UV_ECONNABORTED, 33 | ECONNREFUSED = c.UV_ECONNREFUSED, 34 | ECONNRESET = c.UV_ECONNRESET, 35 | EDESTADDRREQ = c.UV_EDESTADDRREQ, 36 | EEXIST = c.UV_EEXIST, 37 | EFAULT = c.UV_EFAULT, 38 | EFBIG = c.UV_EFBIG, 39 | EHOSTUNREACH = c.UV_EHOSTUNREACH, 40 | EINTR = c.UV_EINTR, 41 | EINVAL = c.UV_EINVAL, 42 | EIO = c.UV_EIO, 43 | EISCONN = c.UV_EISCONN, 44 | EISDIR = c.UV_EISDIR, 45 | ELOOP = c.UV_ELOOP, 46 | EMFILE = c.UV_EMFILE, 47 | EMSGSIZE = c.UV_EMSGSIZE, 48 | ENAMETOOLONG = c.UV_ENAMETOOLONG, 49 | ENETDOWN = c.UV_ENETDOWN, 50 | ENETUNREACH = c.UV_ENETUNREACH, 51 | ENFILE = c.UV_ENFILE, 52 | ENOBUFS = c.UV_ENOBUFS, 53 | ENODEV = c.UV_ENODEV, 54 | ENOENT = c.UV_ENOENT, 55 | ENOMEM = c.UV_ENOMEM, 56 | ENONET = c.UV_ENONET, 57 | ENOPROTOOPT = c.UV_ENOPROTOOPT, 58 | ENOSPC = c.UV_ENOSPC, 59 | ENOSYS = c.UV_ENOSYS, 60 | ENOTCONN = c.UV_ENOTCONN, 61 | ENOTDIR = c.UV_ENOTDIR, 62 | ENOTEMPTY = c.UV_ENOTEMPTY, 63 | ENOTSOCK = c.UV_ENOTSOCK, 64 | ENOTSUP = c.UV_ENOTSUP, 65 | EPERM = c.UV_EPERM, 66 | EPIPE = c.UV_EPIPE, 67 | EPROTO = c.UV_EPROTO, 68 | EPROTONOSUPPORT = c.UV_EPROTONOSUPPORT, 69 | EPROTOTYPE = c.UV_EPROTOTYPE, 70 | ERANGE = c.UV_ERANGE, 71 | EROFS = c.UV_EROFS, 72 | ESHUTDOWN = c.UV_ESHUTDOWN, 73 | ESPIPE = c.UV_ESPIPE, 74 | ESRCH = c.UV_ESRCH, 75 | ETIMEDOUT = c.UV_ETIMEDOUT, 76 | ETXTBSY = c.UV_ETXTBSY, 77 | EXDEV = c.UV_EXDEV, 78 | UNKNOWN = c.UV_UNKNOWN, 79 | EOF = c.UV_EOF, 80 | ENXIO = c.UV_ENXIO, 81 | EHOSTDOWN = c.UV_EHOSTDOWN, 82 | EREMOTEIO = c.UV_EREMOTEIO, 83 | ENOTTY = c.UV_ENOTTY, 84 | EFTYPE = c.UV_EFTYPE, 85 | EILSEQ = c.UV_EILSEQ, 86 | ESOCKTNOSUPPORT = c.UV_ESOCKTNOSUPPORT, 87 | }; 88 | 89 | /// Errors that libuv can produce. 90 | /// 91 | /// http://docs.libuv.org/en/v1.x/errors.html 92 | pub const Error = blk: { 93 | // We produce these from the Errno enum so that we can easily 94 | // keep it synced. 95 | const info = @typeInfo(Errno).Enum; 96 | var errors: [info.fields.len]std.builtin.Type.Error = undefined; 97 | for (info.fields) |field, i| { 98 | errors[i] = .{ .name = field.name }; 99 | } 100 | 101 | break :blk @Type(.{ .ErrorSet = &errors }); 102 | }; 103 | 104 | /// Convert the result of a libuv API call to an error (or no error). 105 | pub fn convertError(r: i32) !void { 106 | if (r >= 0) return; 107 | 108 | return switch (@intToEnum(Errno, r)) { 109 | .E2BIG => Error.E2BIG, 110 | .EACCES => Error.EACCES, 111 | .EADDRINUSE => Error.EADDRINUSE, 112 | .EADDRNOTAVAIL => Error.EADDRNOTAVAIL, 113 | .EAFNOSUPPORT => Error.EAFNOSUPPORT, 114 | .EAGAIN => Error.EAGAIN, 115 | .EAI_ADDRFAMILY => Error.EAI_ADDRFAMILY, 116 | .EAI_AGAIN => Error.EAI_AGAIN, 117 | .EAI_BADFLAGS => Error.EAI_BADFLAGS, 118 | .EAI_BADHINTS => Error.EAI_BADHINTS, 119 | .EAI_CANCELED => Error.EAI_CANCELED, 120 | .EAI_FAIL => Error.EAI_FAIL, 121 | .EAI_FAMILY => Error.EAI_FAMILY, 122 | .EAI_MEMORY => Error.EAI_MEMORY, 123 | .EAI_NODATA => Error.EAI_NODATA, 124 | .EAI_NONAME => Error.EAI_NONAME, 125 | .EAI_OVERFLOW => Error.EAI_OVERFLOW, 126 | .EAI_PROTOCOL => Error.EAI_PROTOCOL, 127 | .EAI_SERVICE => Error.EAI_SERVICE, 128 | .EAI_SOCKTYPE => Error.EAI_SOCKTYPE, 129 | .EALREADY => Error.EALREADY, 130 | .EBADF => Error.EBADF, 131 | .EBUSY => Error.EBUSY, 132 | .ECANCELED => Error.ECANCELED, 133 | .ECHARSET => Error.ECHARSET, 134 | .ECONNABORTED => Error.ECONNABORTED, 135 | .ECONNREFUSED => Error.ECONNREFUSED, 136 | .ECONNRESET => Error.ECONNRESET, 137 | .EDESTADDRREQ => Error.EDESTADDRREQ, 138 | .EEXIST => Error.EEXIST, 139 | .EFAULT => Error.EFAULT, 140 | .EFBIG => Error.EFBIG, 141 | .EHOSTUNREACH => Error.EHOSTUNREACH, 142 | .EINTR => Error.EINTR, 143 | .EINVAL => Error.EINVAL, 144 | .EIO => Error.EIO, 145 | .EISCONN => Error.EISCONN, 146 | .EISDIR => Error.EISDIR, 147 | .ELOOP => Error.ELOOP, 148 | .EMFILE => Error.EMFILE, 149 | .EMSGSIZE => Error.EMSGSIZE, 150 | .ENAMETOOLONG => Error.ENAMETOOLONG, 151 | .ENETDOWN => Error.ENETDOWN, 152 | .ENETUNREACH => Error.ENETUNREACH, 153 | .ENFILE => Error.ENFILE, 154 | .ENOBUFS => Error.ENOBUFS, 155 | .ENODEV => Error.ENODEV, 156 | .ENOENT => Error.ENOENT, 157 | .ENOMEM => Error.ENOMEM, 158 | .ENONET => Error.ENONET, 159 | .ENOPROTOOPT => Error.ENOPROTOOPT, 160 | .ENOSPC => Error.ENOSPC, 161 | .ENOSYS => Error.ENOSYS, 162 | .ENOTCONN => Error.ENOTCONN, 163 | .ENOTDIR => Error.ENOTDIR, 164 | .ENOTEMPTY => Error.ENOTEMPTY, 165 | .ENOTSOCK => Error.ENOTSOCK, 166 | .ENOTSUP => Error.ENOTSUP, 167 | .EPERM => Error.EPERM, 168 | .EPIPE => Error.EPIPE, 169 | .EPROTO => Error.EPROTO, 170 | .EPROTONOSUPPORT => Error.EPROTONOSUPPORT, 171 | .EPROTOTYPE => Error.EPROTOTYPE, 172 | .ERANGE => Error.ERANGE, 173 | .EROFS => Error.EROFS, 174 | .ESHUTDOWN => Error.ESHUTDOWN, 175 | .ESPIPE => Error.ESPIPE, 176 | .ESRCH => Error.ESRCH, 177 | .ETIMEDOUT => Error.ETIMEDOUT, 178 | .ETXTBSY => Error.ETXTBSY, 179 | .EXDEV => Error.EXDEV, 180 | .UNKNOWN => Error.UNKNOWN, 181 | .EOF => Error.EOF, 182 | .ENXIO => Error.ENXIO, 183 | .EHOSTDOWN => Error.EHOSTDOWN, 184 | .EREMOTEIO => Error.EREMOTEIO, 185 | .ENOTTY => Error.ENOTTY, 186 | .EFTYPE => Error.EFTYPE, 187 | .EILSEQ => Error.EILSEQ, 188 | .ESOCKTNOSUPPORT => Error.ESOCKTNOSUPPORT, 189 | }; 190 | } 191 | 192 | test { 193 | // This is mostly just forcing our error type and function to be 194 | // codegenned and run once to ensure we have all the types. 195 | try testing.expectError(Error.EFTYPE, convertError(c.UV_EFTYPE)); 196 | } 197 | -------------------------------------------------------------------------------- /src/handle.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | 3 | const Loop = @import("Loop.zig"); 4 | const errors = @import("error.zig"); 5 | 6 | /// Returns a struct that has all the shared handle functions for the 7 | /// given handle type T. The type T must have a field named "handle". 8 | /// This is expected to be used with usingnamespace to add the shared 9 | /// handler functions to other handle types. 10 | pub fn Handle(comptime T: type) type { 11 | // 1. T should be a struct 12 | // 2. First field should be the handle pointer 13 | 14 | return struct { 15 | // note: this has to be here: https://github.com/ziglang/zig/issues/11367 16 | const tInfo = @typeInfo(T).Struct; 17 | const HandleType = tInfo.fields[0].type; 18 | 19 | // Request handle to be closed. close_cb will be called asynchronously 20 | // after this call. This MUST be called on each handle before memory 21 | // is released. Moreover, the memory can only be released in close_cb 22 | // or after it has returned. 23 | // 24 | // Handles that wrap file descriptors are closed immediately but 25 | // close_cb will still be deferred to the next iteration of the event 26 | // loop. It gives you a chance to free up any resources associated with 27 | // the handle. 28 | // 29 | // In-progress requests, like uv_connect_t or uv_write_t, are cancelled 30 | // and have their callbacks called asynchronously with status=UV_ECANCELED. 31 | pub fn close(self: T, comptime cb: ?fn (*T) void) void { 32 | const cbParam = if (cb) |f| 33 | (struct { 34 | pub fn callback(handle: [*c]c.uv_handle_t) callconv(.C) void { 35 | // We get the raw handle, so we need to reconstruct 36 | // the T. This is mutable because a lot of the libuv APIs 37 | // are non-const but modifying it makes no sense. 38 | var param: T = .{ .handle = @ptrCast(HandleType, handle) }; 39 | @call(.always_inline, f, .{¶m}); 40 | } 41 | }).callback 42 | else 43 | null; 44 | 45 | c.uv_close(@ptrCast(*c.uv_handle_t, self.handle), cbParam); 46 | } 47 | 48 | /// Loop returns the loop that this handle is a part of. 49 | pub fn loop(self: T) Loop { 50 | const handle = @ptrCast(*c.uv_handle_t, self.handle); 51 | return .{ .loop = c.uv_handle_get_loop(handle) }; 52 | } 53 | 54 | /// Returns non-zero if the handle is active, zero if it’s inactive. 55 | /// Rule of thumb: if a handle of type uv_foo_t has a uv_foo_start() 56 | /// function, then it’s active from the moment that function is called. 57 | /// Likewise, uv_foo_stop() deactivates the handle again. 58 | pub fn isActive(self: T) !bool { 59 | const res = c.uv_is_active(@ptrCast(*c.uv_handle_t, self.handle)); 60 | try errors.convertError(res); 61 | return res > 0; 62 | } 63 | 64 | /// Sets handle->data to data. 65 | pub fn setData(self: T, pointer: ?*anyopaque) void { 66 | c.uv_handle_set_data( 67 | @ptrCast(*c.uv_handle_t, self.handle), 68 | pointer, 69 | ); 70 | } 71 | 72 | /// Returns handle->data. 73 | pub fn getData(self: T, comptime DT: type) ?*DT { 74 | return if (c.uv_handle_get_data(@ptrCast(*c.uv_handle_t, self.handle))) |ptr| 75 | @ptrCast(?*DT, @alignCast(@alignOf(DT), ptr)) 76 | else 77 | null; 78 | } 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const stream = @import("stream.zig"); 3 | 4 | pub const c = @import("c.zig"); 5 | pub const Loop = @import("Loop.zig"); 6 | pub const Async = @import("Async.zig"); 7 | pub const Idle = @import("Idle.zig"); 8 | pub const Pipe = @import("Pipe.zig"); 9 | pub const Prepare = @import("Prepare.zig"); 10 | pub const Timer = @import("Timer.zig"); 11 | pub const Tty = @import("Tty.zig"); 12 | pub const Cond = @import("Cond.zig"); 13 | pub const Mutex = @import("Mutex.zig"); 14 | pub const Sem = @import("Sem.zig"); 15 | pub const Thread = @import("Thread.zig"); 16 | pub const WriteReq = stream.WriteReq; 17 | 18 | pub usingnamespace @import("error.zig"); 19 | 20 | test { 21 | _ = @import("tests.zig"); 22 | _ = stream; 23 | 24 | _ = Loop; 25 | _ = Async; 26 | _ = Idle; 27 | _ = Prepare; 28 | _ = Pipe; 29 | _ = Timer; 30 | _ = Tty; 31 | _ = Cond; 32 | _ = Mutex; 33 | _ = Sem; 34 | _ = Thread; 35 | } 36 | -------------------------------------------------------------------------------- /src/stream.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | 3 | const std = @import("std"); 4 | const Allocator = std.mem.Allocator; 5 | const testing = std.testing; 6 | const Loop = @import("Loop.zig"); 7 | const errors = @import("error.zig"); 8 | const Error = errors.Error; 9 | 10 | /// Returns a struct that has all the shared stream functions for the 11 | /// given stream type T. The type T must have a field named "handle". 12 | /// This is expected to be used with usingnamespace to add the shared 13 | /// stream functions to other handle types. 14 | pub fn Stream(comptime T: type) type { 15 | // 1. T should be a struct 16 | // 2. First field should be the handle pointer 17 | 18 | return struct { 19 | // note: this has to be here: https://github.com/ziglang/zig/issues/11367 20 | const tInfo = @typeInfo(T).Struct; 21 | const HandleType = tInfo.fields[0].type; 22 | 23 | /// Returns 1 if the stream is readable, 0 otherwise. 24 | pub fn isReadable(self: T) !bool { 25 | const res = c.uv_is_readable(@ptrCast(*c.uv_stream_t, self.handle)); 26 | try errors.convertError(res); 27 | return res > 0; 28 | } 29 | 30 | /// Returns 1 if the stream is writable, 0 otherwise. 31 | pub fn isWritable(self: T) !bool { 32 | const res = c.uv_is_writable(@ptrCast(*c.uv_stream_t, self.handle)); 33 | try errors.convertError(res); 34 | return res > 0; 35 | } 36 | 37 | /// Write data to stream. Buffers are written in order. 38 | pub fn write( 39 | self: T, 40 | req: WriteReq, 41 | bufs: []const []const u8, 42 | comptime cb: fn (req: *WriteReq, status: i32) void, 43 | ) !void { 44 | const Wrapper = struct { 45 | fn callback(cbreq: [*c]c.uv_write_t, status: c_int) callconv(.C) void { 46 | var newreq: WriteReq = .{ .req = cbreq }; 47 | @call(.always_inline, cb, .{ 48 | &newreq, 49 | @intCast(i32, status), 50 | }); 51 | } 52 | }; 53 | 54 | // We can directly ptrCast bufs.ptr to a C pointer of uv_buf_t 55 | // because they have the exact same layout in memory. We have a 56 | // unit test below that keeps this true. 57 | try errors.convertError(c.uv_write( 58 | req.req, 59 | @ptrCast(*c.uv_stream_t, self.handle), 60 | @ptrCast([*c]const c.uv_buf_t, bufs.ptr), 61 | @intCast(c_uint, bufs.len), 62 | Wrapper.callback, 63 | )); 64 | } 65 | 66 | /// Same as uv_write(), but won’t queue a write request if it can’t 67 | /// be completed immediately. 68 | pub fn tryWrite(self: T, bufs: []const []const u8) !usize { 69 | const res = c.uv_try_write( 70 | @ptrCast(*c.uv_stream_t, self.handle), 71 | @ptrCast([*c]const c.uv_buf_t, bufs.ptr), 72 | @intCast(c_uint, bufs.len), 73 | ); 74 | try errors.convertError(res); 75 | return @intCast(usize, res); 76 | } 77 | 78 | /// Read data from an incoming stream. The uv_read_cb callback will 79 | /// be made several times until there is no more data to read or 80 | /// uv_read_stop() is called. 81 | pub fn readStart( 82 | self: T, 83 | comptime alloc_cb: fn (self: *T, size: usize) ?[]u8, 84 | comptime read_cb: fn (self: *T, nread: isize, buf: []const u8) void, 85 | ) !void { 86 | const Wrapper = struct { 87 | fn alloc( 88 | cbhandle: [*c]c.uv_handle_t, 89 | cbsize: usize, 90 | buf: [*c]c.uv_buf_t, 91 | ) callconv(.C) void { 92 | var param: T = .{ .handle = @ptrCast(HandleType, cbhandle) }; 93 | const result = @call(.always_inline, alloc_cb, .{ 94 | ¶m, 95 | cbsize, 96 | }); 97 | 98 | if (result) |slice| { 99 | buf.* = .{ 100 | .base = slice.ptr, 101 | .len = slice.len, 102 | }; 103 | } else { 104 | buf.* = .{ .base = null, .len = 0 }; 105 | } 106 | } 107 | 108 | fn read( 109 | cbhandle: [*c]c.uv_stream_t, 110 | cbnread: isize, 111 | cbbuf: [*c]const c.uv_buf_t, 112 | ) callconv(.C) void { 113 | var param: T = .{ .handle = @ptrCast(HandleType, cbhandle) }; 114 | @call(.always_inline, read_cb, .{ 115 | ¶m, 116 | cbnread, 117 | cbbuf.*.base[0..cbbuf.*.len], 118 | }); 119 | } 120 | }; 121 | 122 | try errors.convertError(c.uv_read_start( 123 | @ptrCast(*c.uv_stream_t, self.handle), 124 | Wrapper.alloc, 125 | Wrapper.read, 126 | )); 127 | } 128 | 129 | /// Stop reading data from the stream. The uv_read_cb callback will 130 | /// no longer be called. 131 | /// 132 | /// This function is idempotent and may be safely called on a stopped 133 | /// stream. 134 | pub fn readStop(self: T) void { 135 | // Docs say we can ignore this result. 136 | _ = c.uv_read_stop(@ptrCast(*c.uv_stream_t, self.handle)); 137 | } 138 | }; 139 | } 140 | 141 | /// Write request type. Careful attention must be paid when reusing objects 142 | /// of this type. When a stream is in non-blocking mode, write requests sent 143 | /// with uv_write will be queued. Reusing objects at this point is undefined 144 | /// behaviour. It is safe to reuse the uv_write_t object only after the 145 | /// callback passed to uv_write is fired. 146 | pub const WriteReq = struct { 147 | /// This is the underlying type that WriteReq wraps. This is exposed 148 | /// so that you can pre-allocate the type and wrap it in a WrapReq. 149 | pub const T = c.uv_write_t; 150 | 151 | req: *T, 152 | 153 | pub fn init(alloc: Allocator) !WriteReq { 154 | var req = try alloc.create(c.uv_write_t); 155 | errdefer alloc.destroy(req); 156 | return WriteReq{ .req = req }; 157 | } 158 | 159 | pub fn deinit(self: *WriteReq, alloc: Allocator) void { 160 | alloc.destroy(self.req); 161 | self.* = undefined; 162 | } 163 | 164 | /// Pointer to the stream where this write request is running. 165 | /// T should be a high-level handle type such as "Pipe". 166 | pub fn handle(self: WriteReq, comptime HT: type) ?HT { 167 | const tInfo = @typeInfo(HT).Struct; 168 | const HandleType = tInfo.fields[0].type; 169 | 170 | return if (self.req.handle) |ptr| 171 | return HT{ .handle = @ptrCast(HandleType, ptr) } 172 | else 173 | null; 174 | } 175 | 176 | test "Write: create and destroy" { 177 | var h = try init(testing.allocator); 178 | defer h.deinit(testing.allocator); 179 | } 180 | }; 181 | 182 | test { 183 | _ = WriteReq; 184 | } 185 | -------------------------------------------------------------------------------- /src/tests.zig: -------------------------------------------------------------------------------- 1 | //! This file contains other behavior tests for the libuv integration. 2 | //! We trust that libuv works, but still test some behaviors to ensure 3 | //! that our wrappers around libuv are working as expected. 4 | 5 | const std = @import("std"); 6 | const testing = std.testing; 7 | const libuv = @import("main.zig"); 8 | 9 | test "Async: cancel timer" { 10 | const alloc = testing.allocator; 11 | var loop = try libuv.Loop.init(alloc); 12 | defer loop.deinit(alloc); 13 | 14 | var timer = try libuv.Timer.init(alloc, loop); 15 | defer timer.deinit(alloc); 16 | 17 | // Start a timer with a long timeout. This will block our loop. 18 | try timer.start((struct { 19 | fn callback(_: *libuv.Timer) void {} 20 | }).callback, 5000, 5000); 21 | 22 | var async_handle = try libuv.Async.init(testing.allocator, loop, (struct { 23 | fn callback(v: *libuv.Async) void { 24 | v.loop().stop(); 25 | v.close(null); 26 | } 27 | }).callback); 28 | defer async_handle.deinit(testing.allocator); 29 | try async_handle.send(); 30 | 31 | // This run through the loop should exit because we called loop stop. 32 | _ = try loop.run(.default); 33 | 34 | // We need to run the loop one more time to handle all our close callbacks. 35 | timer.close(null); 36 | _ = try loop.run(.default); 37 | } 38 | --------------------------------------------------------------------------------