diff --git a/src/libuv/Async.zig b/src/libuv/Async.zig new file mode 100644 index 000000000..b9e1b0da7 --- /dev/null +++ b/src/libuv/Async.zig @@ -0,0 +1,54 @@ +//! Async handles allow the user to “wakeup” the event loop and get a callback +//! called from another thread. +const Async = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const testing = std.testing; +const c = @import("c.zig"); +const errors = @import("error.zig"); +const Loop = @import("Loop.zig"); +const Handle = @import("handle.zig").Handle; + +handle: *c.uv_async_t, + +pub usingnamespace Handle(Async); + +pub fn init(alloc: Allocator, loop: Loop, comptime cb: fn (Async) void) !Async { + var handle = try alloc.create(c.uv_async_t); + errdefer alloc.destroy(handle); + + const Wrapper = struct { + pub fn callback(arg: [*c]c.uv_async_t) callconv(.C) void { + const newSelf: Async = .{ .handle = arg }; + @call(.{ .modifier = .always_inline }, cb, .{newSelf}); + } + }; + + try errors.convertError(c.uv_async_init(loop.loop, handle, Wrapper.callback)); + return Async{ .handle = handle }; +} + +pub fn deinit(self: *Async, alloc: Allocator) void { + alloc.destroy(self.handle); + self.* = undefined; +} + +/// Wake up the event loop and call the async handle’s callback. +pub fn send(self: Async) !void { + try errors.convertError(c.uv_async_send(self.handle)); +} + +test "Async" { + var loop = try Loop.init(testing.allocator); + defer loop.deinit(testing.allocator); + var h = try init(testing.allocator, loop, (struct { + fn callback(v: Async) void { + v.close(null); + } + }).callback); + defer h.deinit(testing.allocator); + + try h.send(); + _ = try loop.run(.default); +} diff --git a/src/libuv/Loop.zig b/src/libuv/Loop.zig index 30f22bd77..564993895 100644 --- a/src/libuv/Loop.zig +++ b/src/libuv/Loop.zig @@ -46,6 +46,13 @@ pub fn run(self: Loop, mode: RunMode) !u32 { return @intCast(u32, res); } +/// Stop the event loop, causing uv_run() to end as soon as possible. This +/// will happen not sooner than the next loop iteration. If this function was +/// called before blocking for i/o, the loop won’t block for i/o on this iteration. +pub fn stop(self: Loop) void { + c.uv_stop(self.loop); +} + /// Get backend file descriptor. Only kqueue, epoll and event ports are supported. /// /// This can be used in conjunction with uv_run(loop, UV_RUN_NOWAIT) to poll diff --git a/src/libuv/handle.zig b/src/libuv/handle.zig index adb088300..cc67bdebb 100644 --- a/src/libuv/handle.zig +++ b/src/libuv/handle.zig @@ -1,5 +1,7 @@ const c = @import("c.zig"); +const Loop = @import("Loop.zig"); + /// Returns a struct that has all the shared handle functions for the /// given handle type T. The type T must have a field named "handle". /// This is expected to be used with usingnamespace to add the shared @@ -40,6 +42,12 @@ pub fn Handle(comptime T: type) type { c.uv_close(@ptrCast(*c.uv_handle_t, self.handle), cbParam); } + /// Loop returns the loop that this handle is a part of. + pub fn loop(self: T) Loop { + const handle = @ptrCast(*c.uv_handle_t, self.handle); + return .{ .loop = c.uv_handle_get_loop(handle) }; + } + /// Sets handle->data to data. pub fn setData(self: T, pointer: ?*anyopaque) void { c.uv_handle_set_data( diff --git a/src/libuv/main.zig b/src/libuv/main.zig index f64d19c46..c6c65f00b 100644 --- a/src/libuv/main.zig +++ b/src/libuv/main.zig @@ -1,4 +1,5 @@ pub const Loop = @import("Loop.zig"); +pub const Async = @import("Async.zig"); pub const Timer = @import("Timer.zig"); pub const Sem = @import("Sem.zig"); pub const Thread = @import("Thread.zig"); @@ -7,7 +8,10 @@ pub const Error = @import("error.zig").Error; pub const Embed = @import("Embed.zig"); test { + _ = @import("tests.zig"); + _ = Loop; + _ = Async; _ = Timer; _ = Sem; _ = Thread; diff --git a/src/libuv/tests.zig b/src/libuv/tests.zig new file mode 100644 index 000000000..d407dc182 --- /dev/null +++ b/src/libuv/tests.zig @@ -0,0 +1,37 @@ +//! This file contains other behavior tests for the libuv integration. +//! We trust that libuv works, but still test some behaviors to ensure +//! that our wrappers around libuv are working as expected. + +const std = @import("std"); +const testing = std.testing; +const libuv = @import("main.zig"); + +test "Async: cancel timer" { + const alloc = testing.allocator; + var loop = try libuv.Loop.init(alloc); + defer loop.deinit(alloc); + + var timer = try libuv.Timer.init(alloc, loop); + defer timer.deinit(alloc); + + // Start a timer with a long timeout. This will block our loop. + try timer.start((struct { + fn callback(_: libuv.Timer) void {} + }).callback, 5000, 5000); + + var async_handle = try libuv.Async.init(testing.allocator, loop, (struct { + fn callback(v: libuv.Async) void { + v.loop().stop(); + v.close(null); + } + }).callback); + defer async_handle.deinit(testing.allocator); + try async_handle.send(); + + // This run through the loop should exit because we called loop stop. + _ = try loop.run(.default); + + // We need to run the loop one more time to handle all our close callbacks. + timer.close(null); + _ = try loop.run(.default); +}