mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
pkg/libuv: delete
This commit is contained in:
@ -1,54 +0,0 @@
|
||||
//! 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 {
|
||||
var newSelf: Async = .{ .handle = arg };
|
||||
@call(.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);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
//! Condition variables implemented via libuv.
|
||||
const Cond = @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 Mutex = @import("Mutex.zig");
|
||||
|
||||
cond: *c.uv_cond_t,
|
||||
|
||||
pub fn init(alloc: Allocator) !Cond {
|
||||
const cond = try alloc.create(c.uv_cond_t);
|
||||
try errors.convertError(c.uv_cond_init(cond));
|
||||
return Cond{ .cond = cond };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Cond, alloc: Allocator) void {
|
||||
c.uv_cond_destroy(self.cond);
|
||||
alloc.destroy(self.cond);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn signal(self: Cond) void {
|
||||
c.uv_cond_signal(self.cond);
|
||||
}
|
||||
|
||||
pub fn broadcast(self: Cond) void {
|
||||
c.uv_cond_broadcast(self.cond);
|
||||
}
|
||||
|
||||
pub fn wait(self: Cond, mutex: Mutex) void {
|
||||
c.uv_cond_wait(self.cond, mutex.mutex);
|
||||
}
|
||||
|
||||
pub fn timedwait(self: Cond, mutex: Mutex, timeout: u64) c_int {
|
||||
return c.uv_cond_timedwait(self.cond, mutex.mutex, timeout);
|
||||
}
|
||||
|
||||
test {
|
||||
var cond = try init(testing.allocator);
|
||||
defer cond.deinit(testing.allocator);
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
//! This has a helper for embedding libuv in another event loop.
|
||||
//! This is an extension of libuv and not a helper built-in to libuv
|
||||
//! itself, although it uses official APIs of libuv to enable the
|
||||
//! functionality.
|
||||
|
||||
const Embed = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Loop = @import("Loop.zig");
|
||||
const Thread = @import("Thread.zig");
|
||||
const Mutex = @import("Mutex.zig");
|
||||
const Cond = @import("Cond.zig");
|
||||
|
||||
const log = std.log.scoped(.libuv_embed);
|
||||
|
||||
const BoolAtomic = std.atomic.Atomic(bool);
|
||||
|
||||
loop: Loop,
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
ready: bool = false,
|
||||
terminate: BoolAtomic,
|
||||
callback: *const fn () void,
|
||||
thread: ?Thread,
|
||||
|
||||
/// Initialize a new embedder. The callback is called when libuv should
|
||||
/// tick. The callback should be as fast as possible.
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
loop: Loop,
|
||||
callback: *const fn () void,
|
||||
) !Embed {
|
||||
return Embed{
|
||||
.loop = loop,
|
||||
.mutex = try Mutex.init(alloc),
|
||||
.cond = try Cond.init(alloc),
|
||||
.terminate = BoolAtomic.init(false),
|
||||
.callback = callback,
|
||||
.thread = null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Deinit the embed struct. This will not automatically terminate
|
||||
/// the embed thread. You must call stop manually.
|
||||
pub fn deinit(self: *Embed, alloc: Allocator) void {
|
||||
self.mutex.deinit(alloc);
|
||||
self.cond.deinit(alloc);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Start the thread that runs the embed logic and calls callback
|
||||
/// when the libuv loop should tick. This must only be called once.
|
||||
pub fn start(self: *Embed) !void {
|
||||
self.thread = try Thread.initData(self, Embed.threadMain);
|
||||
}
|
||||
|
||||
/// Stop stops the embed thread and blocks until the thread joins.
|
||||
pub fn stop(self: *Embed) void {
|
||||
if (self.thread == null) return;
|
||||
|
||||
// Mark that we want to terminate
|
||||
self.terminate.store(true, .SeqCst);
|
||||
|
||||
// Post to the semaphore to ensure that any waits are processed.
|
||||
self.cond.broadcast();
|
||||
}
|
||||
|
||||
/// Wait for the thread backing the embedding to end.
|
||||
pub fn join(self: *Embed) !void {
|
||||
if (self.thread) |*thr| {
|
||||
try thr.join();
|
||||
self.thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// loopRun runs the next tick of the libuv event loop. This should be
|
||||
/// called by the main loop thread as a result of callback making some
|
||||
/// signal. This should NOT be called from callback.
|
||||
pub fn loopRun(self: *Embed) !void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
|
||||
if (self.ready) {
|
||||
self.ready = false;
|
||||
_ = try self.loop.run(.nowait);
|
||||
self.cond.broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
fn threadMain(self: *Embed) void {
|
||||
while (self.terminate.load(.SeqCst) == false) {
|
||||
const fd = self.loop.backendFd() catch unreachable;
|
||||
const timeout = self.loop.backendTimeout();
|
||||
|
||||
switch (builtin.os.tag) {
|
||||
// epoll
|
||||
.linux => {
|
||||
var ev: [1]std.os.linux.epoll_event = undefined;
|
||||
_ = std.os.epoll_wait(fd, &ev, timeout);
|
||||
},
|
||||
|
||||
// kqueue
|
||||
.macos, .dragonfly, .freebsd, .openbsd, .netbsd => {
|
||||
var ts: std.os.timespec = .{
|
||||
.tv_sec = @divTrunc(timeout, 1000),
|
||||
.tv_nsec = @mod(timeout, 1000) * 1000000,
|
||||
};
|
||||
|
||||
// Important: for kevent to block properly, it needs an
|
||||
// EMPTY changeset and a NON-EMPTY event set.
|
||||
var changes: [0]std.os.Kevent = undefined;
|
||||
var events: [1]std.os.Kevent = undefined;
|
||||
_ = std.os.kevent(
|
||||
fd,
|
||||
&changes,
|
||||
&events,
|
||||
if (timeout < 0) null else &ts,
|
||||
) catch |err| blk: {
|
||||
log.err("kevent error: {}", .{err});
|
||||
break :blk 0;
|
||||
};
|
||||
},
|
||||
|
||||
else => @compileError("unsupported libuv Embed platform"),
|
||||
}
|
||||
|
||||
// Call our trigger
|
||||
self.callback();
|
||||
|
||||
// Wait for libuv to run a tick.
|
||||
//
|
||||
// NOTE: we use timedwait because I /believe/ there is a race here
|
||||
// with gflw post event that sometimes causes it not to receive it.
|
||||
// Therefore, if too much time passes, we just go back and loop
|
||||
// through the poller.
|
||||
//
|
||||
// TODO: this is suboptimal for performance. There as to be a better
|
||||
// way to do this.
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
self.ready = true;
|
||||
_ = self.cond.timedwait(self.mutex, 10 * 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
test "Embed" {
|
||||
var loop = try Loop.init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
|
||||
var embed = try init(testing.allocator, loop, (struct {
|
||||
fn callback() void {}
|
||||
}).callback);
|
||||
defer embed.deinit(testing.allocator);
|
||||
|
||||
// This just tests that the thread can start and then stop.
|
||||
// It doesn't do much else at the moment
|
||||
try embed.start();
|
||||
embed.stop();
|
||||
try embed.join();
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
//! Idle handles will run the given callback once per loop iteration, right
|
||||
//! before the uv_prepare_t handles.
|
||||
const Idle = @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_idle_t,
|
||||
|
||||
pub usingnamespace Handle(Idle);
|
||||
|
||||
pub fn init(alloc: Allocator, loop: Loop) !Idle {
|
||||
var handle = try alloc.create(c.uv_idle_t);
|
||||
errdefer alloc.destroy(handle);
|
||||
|
||||
try errors.convertError(c.uv_idle_init(loop.loop, handle));
|
||||
return Idle{ .handle = handle };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Idle, alloc: Allocator) void {
|
||||
alloc.destroy(self.handle);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Start the handle with the given callback. This function always succeeds,
|
||||
/// except when cb is NULL.
|
||||
pub fn start(self: Idle, comptime cb: fn (*Idle) void) !void {
|
||||
const Wrapper = struct {
|
||||
pub fn callback(arg: [*c]c.uv_idle_t) callconv(.C) void {
|
||||
var newSelf: Idle = .{ .handle = arg };
|
||||
@call(.always_inline, cb, .{&newSelf});
|
||||
}
|
||||
};
|
||||
|
||||
try errors.convertError(c.uv_idle_start(self.handle, Wrapper.callback));
|
||||
}
|
||||
|
||||
/// Stop the handle, the callback will no longer be called.
|
||||
pub fn stop(self: Idle) !void {
|
||||
try errors.convertError(c.uv_idle_stop(self.handle));
|
||||
}
|
||||
|
||||
test "Idle" {
|
||||
var loop = try Loop.init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
var h = try init(testing.allocator, loop);
|
||||
defer h.deinit(testing.allocator);
|
||||
|
||||
var called: bool = false;
|
||||
h.setData(&called);
|
||||
try h.start((struct {
|
||||
fn callback(t: *Idle) void {
|
||||
t.getData(bool).?.* = true;
|
||||
t.close(null);
|
||||
}
|
||||
}).callback);
|
||||
|
||||
_ = try loop.run(.default);
|
||||
|
||||
try testing.expect(called);
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
const Loop = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const c = @import("c.zig");
|
||||
const errors = @import("error.zig");
|
||||
|
||||
loop: *c.uv_loop_t,
|
||||
|
||||
/// Initialize a new uv_loop.
|
||||
pub fn init(alloc: Allocator) !Loop {
|
||||
// The uv_loop_t type MUST be heap allocated and must not be copied.
|
||||
// I can't find a definitive source on this, but the test suite starts
|
||||
// hanging in weird places and doing bad things when it is copied.
|
||||
const loop = try alloc.create(c.uv_loop_t);
|
||||
try errors.convertError(c.uv_loop_init(loop));
|
||||
return Loop{ .loop = loop };
|
||||
}
|
||||
|
||||
/// Releases all internal loop resources. Call this function only when the
|
||||
/// loop has finished executing and all open handles and requests have been
|
||||
/// closed, or this will silently fail (in debug mode it will panic).
|
||||
pub fn deinit(self: *Loop, alloc: Allocator) void {
|
||||
// deinit functions idiomatically cannot fail in Zig, so we do the
|
||||
// next best thing here and assert so that in debug mode you'll get
|
||||
// a crash.
|
||||
std.debug.assert(c.uv_loop_close(self.loop) >= 0);
|
||||
alloc.destroy(self.loop);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Returns true if the loop is still alive.
|
||||
pub fn alive(self: Loop) !bool {
|
||||
const res = c.uv_loop_alive(self.loop);
|
||||
try errors.convertError(res);
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
/// This function runs the event loop. See RunMode for mode documentation.
|
||||
///
|
||||
/// This is not reentrant. It must not be called from a callback.
|
||||
pub fn run(self: Loop, mode: RunMode) !u32 {
|
||||
const res = c.uv_run(self.loop, @enumToInt(mode));
|
||||
try errors.convertError(res);
|
||||
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
|
||||
/// in one thread and run the event loop’s callbacks in another see
|
||||
/// test/test-embed.c for an example.
|
||||
pub fn backendFd(self: Loop) !c_int {
|
||||
const res = c.uv_backend_fd(self.loop);
|
||||
try errors.convertError(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Get the poll timeout. The return value is in milliseconds, or -1 for no
|
||||
/// timeout.
|
||||
pub fn backendTimeout(self: Loop) c_int {
|
||||
return c.uv_backend_timeout(self.loop);
|
||||
}
|
||||
|
||||
/// Return the current timestamp in milliseconds. The timestamp is cached at
|
||||
/// the start of the event loop tick, see uv_update_time() for details and rationale.
|
||||
///
|
||||
/// The timestamp increases monotonically from some arbitrary point in time.
|
||||
/// Don’t make assumptions about the starting point, you will only get disappointed.
|
||||
pub fn now(self: Loop) u64 {
|
||||
return c.uv_now(self.loop);
|
||||
}
|
||||
|
||||
/// Update the event loop’s concept of “now”. Libuv caches the current time at
|
||||
/// the start of the event loop tick in order to reduce the number of time-related
|
||||
/// system calls.
|
||||
///
|
||||
/// You won’t normally need to call this function unless you have callbacks
|
||||
/// that block the event loop for longer periods of time, where “longer” is
|
||||
/// somewhat subjective but probably on the order of a millisecond or more.
|
||||
pub fn updateTime(self: Loop) void {
|
||||
return c.uv_update_time(self.loop);
|
||||
}
|
||||
|
||||
/// Sets loop->data to data.
|
||||
pub fn setData(self: Loop, pointer: ?*anyopaque) void {
|
||||
c.uv_loop_set_data(self.loop, pointer);
|
||||
}
|
||||
|
||||
/// Returns loop->data.
|
||||
pub fn getData(self: Loop, comptime DT: type) ?*DT {
|
||||
return if (c.uv_loop_get_data(self.loop)) |ptr|
|
||||
@ptrCast(?*DT, @alignCast(@alignOf(DT), ptr))
|
||||
else
|
||||
null;
|
||||
}
|
||||
|
||||
/// Mode used to run the loop with uv_run().
|
||||
pub const RunMode = enum(c.uv_run_mode) {
|
||||
default = c.UV_RUN_DEFAULT,
|
||||
once = c.UV_RUN_ONCE,
|
||||
nowait = c.UV_RUN_NOWAIT,
|
||||
};
|
||||
|
||||
test {
|
||||
var loop = try init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
|
||||
var data: u8 = 42;
|
||||
loop.setData(&data);
|
||||
try testing.expect(loop.getData(u8).?.* == 42);
|
||||
|
||||
try testing.expect((try loop.backendFd()) > 0);
|
||||
try testing.expectEqual(@as(u32, 0), try loop.run(.nowait));
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
//! Mutexes implemented via libuv.
|
||||
const Mutex = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const c = @import("c.zig");
|
||||
const errors = @import("error.zig");
|
||||
|
||||
mutex: *c.uv_mutex_t,
|
||||
|
||||
pub fn init(alloc: Allocator) !Mutex {
|
||||
const mutex = try alloc.create(c.uv_mutex_t);
|
||||
try errors.convertError(c.uv_mutex_init(mutex));
|
||||
return Mutex{ .mutex = mutex };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Mutex, alloc: Allocator) void {
|
||||
c.uv_mutex_destroy(self.mutex);
|
||||
alloc.destroy(self.mutex);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn lock(self: Mutex) void {
|
||||
c.uv_mutex_lock(self.mutex);
|
||||
}
|
||||
|
||||
pub fn unlock(self: Mutex) void {
|
||||
c.uv_mutex_unlock(self.mutex);
|
||||
}
|
||||
|
||||
test {
|
||||
var mutex = try init(testing.allocator);
|
||||
defer mutex.deinit(testing.allocator);
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
//! Pipe handles provide an abstraction over streaming files on Unix
|
||||
//! (including local domain sockets, pipes, and FIFOs) and named pipes on
|
||||
//! Windows.
|
||||
const Pipe = @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;
|
||||
const stream = @import("stream.zig");
|
||||
const Stream = stream.Stream;
|
||||
const WriteReq = stream.WriteReq;
|
||||
|
||||
handle: *c.uv_pipe_t,
|
||||
|
||||
pub usingnamespace Handle(Pipe);
|
||||
pub usingnamespace Stream(Pipe);
|
||||
|
||||
/// Valid flags for pipe.
|
||||
pub const Flags = packed struct {
|
||||
_ignore: u6 = 0,
|
||||
nonblock: bool = false, // UV_NONBLOCK_PIPE = 0x40
|
||||
_ignore_high: u1 = 0,
|
||||
|
||||
pub inline fn toInt(self: Flags, comptime IntType: type) IntType {
|
||||
return @intCast(IntType, @bitCast(u8, self));
|
||||
}
|
||||
|
||||
test "Flags: expected value" {
|
||||
const f: Flags = .{ .nonblock = true };
|
||||
try testing.expectEqual(c.UV_NONBLOCK_PIPE, f.toInt(c_int));
|
||||
}
|
||||
};
|
||||
|
||||
/// Pair is a pair of ends to a single pipe.
|
||||
pub const Pair = struct {
|
||||
read: c.uv_file,
|
||||
write: c.uv_file,
|
||||
};
|
||||
|
||||
/// Create a pair of connected pipe handles. Data may be written to fds[1] and
|
||||
/// read from fds[0]. The resulting handles can be passed to uv_pipe_open,
|
||||
/// used with uv_spawn, or for any other purpose.
|
||||
pub fn pipe(read_flags: Flags, write_flags: Flags) !Pair {
|
||||
var res: [2]c.uv_file = undefined;
|
||||
try errors.convertError(c.uv_pipe(
|
||||
&res,
|
||||
read_flags.toInt(c_int),
|
||||
write_flags.toInt(c_int),
|
||||
));
|
||||
return Pair{ .read = res[0], .write = res[1] };
|
||||
}
|
||||
|
||||
pub fn init(alloc: Allocator, loop: Loop, ipc: bool) !Pipe {
|
||||
var handle = try alloc.create(c.uv_pipe_t);
|
||||
errdefer alloc.destroy(handle);
|
||||
try errors.convertError(c.uv_pipe_init(loop.loop, handle, @boolToInt(ipc)));
|
||||
return Pipe{ .handle = handle };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Pipe, alloc: Allocator) void {
|
||||
alloc.destroy(self.handle);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Open an existing file descriptor or HANDLE as a pipe.
|
||||
pub fn open(self: Pipe, file: c.uv_file) !void {
|
||||
try errors.convertError(c.uv_pipe_open(self.handle, file));
|
||||
}
|
||||
|
||||
test {
|
||||
_ = Flags;
|
||||
}
|
||||
|
||||
test "Pipe" {
|
||||
const pair = try pipe(.{ .nonblock = true }, .{ .nonblock = true });
|
||||
|
||||
var loop = try Loop.init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
|
||||
// Read side
|
||||
var reader = try init(testing.allocator, loop, false);
|
||||
defer reader.deinit(testing.allocator);
|
||||
|
||||
try reader.open(pair.read);
|
||||
try testing.expect(try reader.isReadable());
|
||||
try testing.expect(!try reader.isWritable());
|
||||
|
||||
// Write side
|
||||
var writer = try init(testing.allocator, loop, false);
|
||||
defer writer.deinit(testing.allocator);
|
||||
|
||||
try writer.open(pair.write);
|
||||
try testing.expect(!try writer.isReadable());
|
||||
try testing.expect(try writer.isWritable());
|
||||
|
||||
// Set our data that we'll use to assert
|
||||
var data: TestData = .{};
|
||||
defer data.deinit();
|
||||
writer.setData(&data);
|
||||
|
||||
// Write
|
||||
var writeReq = try WriteReq.init(testing.allocator);
|
||||
defer writeReq.deinit(testing.allocator);
|
||||
|
||||
try writer.write(
|
||||
writeReq,
|
||||
&[_][]const u8{
|
||||
"hello",
|
||||
},
|
||||
TestData.write,
|
||||
);
|
||||
|
||||
// Run write and verify success
|
||||
_ = try loop.run(.once);
|
||||
try testing.expectEqual(@as(u8, 1), data.count);
|
||||
try testing.expectEqual(@as(i32, 0), data.status);
|
||||
|
||||
// Read
|
||||
try reader.readStart(TestData.alloc, TestData.read);
|
||||
reader.setData(&data);
|
||||
_ = try loop.run(.once);
|
||||
|
||||
// Check our data
|
||||
try testing.expectEqual(@as(usize, 5), data.data.items.len);
|
||||
try testing.expectEqualStrings("hello", data.data.items);
|
||||
data.data.clearRetainingCapacity();
|
||||
|
||||
// Try writing directly
|
||||
_ = try writer.tryWrite(&[_][]const u8{"world"});
|
||||
_ = try loop.run(.once);
|
||||
try testing.expectEqual(@as(usize, 5), data.data.items.len);
|
||||
try testing.expectEqualStrings("world", data.data.items);
|
||||
|
||||
// End
|
||||
reader.readStop();
|
||||
reader.close(null);
|
||||
writer.close(null);
|
||||
_ = try loop.run(.default);
|
||||
}
|
||||
|
||||
/// Logic for testing read/write of pipes.
|
||||
const TestData = struct {
|
||||
count: u8 = 0,
|
||||
status: i32 = 0,
|
||||
data: std.ArrayListUnmanaged(u8) = .{},
|
||||
|
||||
fn deinit(self: *TestData) void {
|
||||
self.data.deinit(testing.allocator);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn write(req: *WriteReq, status: i32) void {
|
||||
var data = req.handle(Pipe).?.getData(TestData).?;
|
||||
data.count += 1;
|
||||
data.status = status;
|
||||
}
|
||||
|
||||
fn alloc(_: *Pipe, size: usize) ?[]u8 {
|
||||
return testing.allocator.alloc(u8, size) catch null;
|
||||
}
|
||||
|
||||
fn read(h: *Pipe, n: isize, buf: []const u8) void {
|
||||
var data = h.getData(TestData).?;
|
||||
data.data.appendSlice(
|
||||
testing.allocator,
|
||||
buf[0..@intCast(usize, n)],
|
||||
) catch unreachable;
|
||||
testing.allocator.free(buf);
|
||||
}
|
||||
};
|
@ -1,66 +0,0 @@
|
||||
//! Prepare handles will run the given callback once per loop iteration, right
|
||||
//! before polling for i/o.
|
||||
const Prepare = @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_prepare_t,
|
||||
|
||||
pub usingnamespace Handle(Prepare);
|
||||
|
||||
pub fn init(alloc: Allocator, loop: Loop) !Prepare {
|
||||
var handle = try alloc.create(c.uv_prepare_t);
|
||||
errdefer alloc.destroy(handle);
|
||||
|
||||
try errors.convertError(c.uv_prepare_init(loop.loop, handle));
|
||||
return Prepare{ .handle = handle };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Prepare, alloc: Allocator) void {
|
||||
alloc.destroy(self.handle);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Start the handle with the given callback. This function always succeeds,
|
||||
/// except when cb is NULL.
|
||||
pub fn start(self: Prepare, comptime cb: fn (*Prepare) void) !void {
|
||||
const Wrapper = struct {
|
||||
pub fn callback(arg: [*c]c.uv_prepare_t) callconv(.C) void {
|
||||
var newSelf: Prepare = .{ .handle = arg };
|
||||
@call(.always_inline, cb, .{&newSelf});
|
||||
}
|
||||
};
|
||||
|
||||
try errors.convertError(c.uv_prepare_start(self.handle, Wrapper.callback));
|
||||
}
|
||||
|
||||
/// Stop the handle, the callback will no longer be called.
|
||||
pub fn stop(self: Prepare) !void {
|
||||
try errors.convertError(c.uv_prepare_stop(self.handle));
|
||||
}
|
||||
|
||||
test "Prepare" {
|
||||
var loop = try Loop.init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
var h = try init(testing.allocator, loop);
|
||||
defer h.deinit(testing.allocator);
|
||||
|
||||
var called: bool = false;
|
||||
h.setData(&called);
|
||||
try h.start((struct {
|
||||
fn callback(t: *Prepare) void {
|
||||
t.getData(bool).?.* = true;
|
||||
t.close(null);
|
||||
}
|
||||
}).callback);
|
||||
|
||||
_ = try loop.run(.default);
|
||||
|
||||
try testing.expect(called);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
//! Semaphores implemented via libuv.
|
||||
const Sem = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const c = @import("c.zig");
|
||||
const errors = @import("error.zig");
|
||||
|
||||
sem: *c.uv_sem_t,
|
||||
|
||||
pub fn init(alloc: Allocator, value: u32) !Sem {
|
||||
const sem = try alloc.create(c.uv_sem_t);
|
||||
try errors.convertError(c.uv_sem_init(sem, value));
|
||||
return Sem{ .sem = sem };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Sem, alloc: Allocator) void {
|
||||
c.uv_sem_destroy(self.sem);
|
||||
alloc.destroy(self.sem);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn post(self: Sem) void {
|
||||
c.uv_sem_post(self.sem);
|
||||
}
|
||||
|
||||
pub fn wait(self: Sem) void {
|
||||
c.uv_sem_wait(self.sem);
|
||||
}
|
||||
|
||||
test {
|
||||
var sem = try init(testing.allocator, 0);
|
||||
defer sem.deinit(testing.allocator);
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
//! Threading implemented by libuv.
|
||||
const Thread = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const c = @import("c.zig");
|
||||
const errors = @import("error.zig");
|
||||
|
||||
thread: c.uv_thread_t,
|
||||
|
||||
/// Get the current thread
|
||||
pub fn self() Thread {
|
||||
return .{ .thread = c.uv_thread_self() };
|
||||
}
|
||||
|
||||
/// Initialize a new thread.
|
||||
pub fn init(
|
||||
comptime callback: fn () void,
|
||||
) !Thread {
|
||||
const CWrapper = struct {
|
||||
pub fn wrapper(_: ?*const anyopaque) callconv(.C) void {
|
||||
@call(.always_inline, callback, .{});
|
||||
}
|
||||
};
|
||||
|
||||
var res = Thread{ .thread = undefined };
|
||||
try errors.convertError(c.uv_thread_create(&res.thread, CWrapper.wrapper, null));
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Initialize a new thread with user data attached.
|
||||
pub fn initData(
|
||||
data: anytype,
|
||||
comptime callback: fn (arg: @TypeOf(data)) void,
|
||||
) !Thread {
|
||||
// Comptime stuff to learn more about our data parameter. This is used
|
||||
// to do the proper casts for the callback.
|
||||
const Data = @TypeOf(data);
|
||||
const dataInfo = @typeInfo(Data);
|
||||
if (dataInfo != .Pointer) @compileError("data must be a pointer type");
|
||||
|
||||
const CWrapper = struct {
|
||||
pub fn wrapper(arg: ?*anyopaque) callconv(.C) void {
|
||||
@call(.always_inline, callback, .{
|
||||
@ptrCast(Data, @alignCast(@alignOf(dataInfo.Pointer.child), arg)),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var res: Thread = .{ .thread = undefined };
|
||||
try errors.convertError(c.uv_thread_create(
|
||||
&res.thread,
|
||||
CWrapper.wrapper,
|
||||
data,
|
||||
));
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn join(t: *Thread) !void {
|
||||
try errors.convertError(c.uv_thread_join(&t.thread));
|
||||
}
|
||||
|
||||
test "Thread: no data argument" {
|
||||
count = 0;
|
||||
var thread = try init(incr);
|
||||
try thread.join();
|
||||
try testing.expectEqual(@as(u8, 1), count);
|
||||
}
|
||||
|
||||
test "Thread: with data argument" {
|
||||
count = 0;
|
||||
var data: u8 = 2;
|
||||
var thread = try initData(&data, incrBy);
|
||||
try thread.join();
|
||||
try testing.expectEqual(@as(u8, 2), count);
|
||||
}
|
||||
|
||||
var count: u8 = 0;
|
||||
|
||||
fn incr() void {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
fn incrBy(v: *u8) void {
|
||||
count += v.*;
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
//! Timer handles are used to schedule callbacks to be called in the future.
|
||||
const Timer = @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_timer_t,
|
||||
|
||||
pub usingnamespace Handle(Timer);
|
||||
|
||||
pub fn init(alloc: Allocator, loop: Loop) !Timer {
|
||||
var timer = try alloc.create(c.uv_timer_t);
|
||||
errdefer alloc.destroy(timer);
|
||||
try errors.convertError(c.uv_timer_init(loop.loop, timer));
|
||||
return Timer{ .handle = timer };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Timer, alloc: Allocator) void {
|
||||
alloc.destroy(self.handle);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Start the timer. timeout and repeat are in milliseconds.
|
||||
///
|
||||
/// If timeout is zero, the callback fires on the next event loop iteration.
|
||||
/// If repeat is non-zero, the callback fires first after timeout milliseconds
|
||||
/// and then repeatedly after repeat milliseconds.
|
||||
pub fn start(
|
||||
self: Timer,
|
||||
comptime cb: fn (*Timer) void,
|
||||
timeout: u64,
|
||||
repeat: u64,
|
||||
) !void {
|
||||
const Wrapper = struct {
|
||||
pub fn callback(handle: [*c]c.uv_timer_t) callconv(.C) void {
|
||||
var newSelf: Timer = .{ .handle = handle };
|
||||
@call(.always_inline, cb, .{&newSelf});
|
||||
}
|
||||
};
|
||||
|
||||
try errors.convertError(c.uv_timer_start(
|
||||
self.handle,
|
||||
Wrapper.callback,
|
||||
timeout,
|
||||
repeat,
|
||||
));
|
||||
}
|
||||
|
||||
/// Stop the timer, the callback will not be called anymore.
|
||||
pub fn stop(self: Timer) !void {
|
||||
try errors.convertError(c.uv_timer_stop(self.handle));
|
||||
}
|
||||
|
||||
/// Stop the timer, and if it is repeating restart it using the repeat value
|
||||
/// as the timeout. If the timer has never been started before it returns UV_EINVAL.
|
||||
pub fn again(self: Timer) !void {
|
||||
try errors.convertError(c.uv_timer_again(self.handle));
|
||||
}
|
||||
|
||||
/// Get the timer repeat value.
|
||||
pub fn getRepeat(self: Timer) u64 {
|
||||
return c.uv_timer_get_repeat(self.handle);
|
||||
}
|
||||
|
||||
/// Set the repeat interval value in milliseconds. The timer will be scheduled
|
||||
/// to run on the given interval, regardless of the callback execution duration,
|
||||
/// and will follow normal timer semantics in the case of a time-slice overrun.
|
||||
pub fn setRepeat(self: Timer, repeat: u64) void {
|
||||
c.uv_timer_set_repeat(self.handle, repeat);
|
||||
}
|
||||
|
||||
test "Timer" {
|
||||
var loop = try Loop.init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
var timer = try init(testing.allocator, loop);
|
||||
defer timer.deinit(testing.allocator);
|
||||
|
||||
var called: bool = false;
|
||||
timer.setData(&called);
|
||||
try timer.start((struct {
|
||||
fn callback(t: *Timer) void {
|
||||
t.getData(bool).?.* = true;
|
||||
t.close(null);
|
||||
}
|
||||
}).callback, 10, 1000);
|
||||
|
||||
_ = try loop.run(.default);
|
||||
|
||||
try testing.expect(called);
|
||||
}
|
||||
|
||||
test "Timer: close callback" {
|
||||
var loop = try Loop.init(testing.allocator);
|
||||
defer loop.deinit(testing.allocator);
|
||||
var timer = try init(testing.allocator, loop);
|
||||
defer timer.deinit(testing.allocator);
|
||||
|
||||
var data: u8 = 42;
|
||||
timer.setData(&data);
|
||||
timer.close((struct {
|
||||
fn callback(v: *Timer) void {
|
||||
var dataPtr = v.getData(u8).?;
|
||||
dataPtr.* = 24;
|
||||
}
|
||||
}).callback);
|
||||
_ = try loop.run(.default);
|
||||
|
||||
try testing.expectEqual(@as(u8, 24), data);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
//! Tty handles represent a stream for the console.
|
||||
const Tty = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const fd_t = std.os.fd_t;
|
||||
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;
|
||||
const Stream = @import("stream.zig").Stream;
|
||||
|
||||
handle: *c.uv_tty_t,
|
||||
|
||||
pub usingnamespace Handle(Tty);
|
||||
pub usingnamespace Stream(Tty);
|
||||
|
||||
pub fn init(alloc: Allocator, loop: Loop, fd: fd_t) !Tty {
|
||||
var tty = try alloc.create(c.uv_tty_t);
|
||||
errdefer alloc.destroy(tty);
|
||||
try errors.convertError(c.uv_tty_init(loop.loop, tty, fd, 0));
|
||||
return Tty{ .handle = tty };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Tty, alloc: Allocator) void {
|
||||
alloc.destroy(self.handle);
|
||||
self.* = undefined;
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Directories with our includes.
|
||||
const root = thisDir() ++ "../../../vendor/libuv/";
|
||||
const include_path = root ++ "include";
|
||||
|
||||
pub const pkg = std.build.Pkg{
|
||||
.name = "libuv",
|
||||
.source = .{ .path = thisDir() ++ "/main.zig" },
|
||||
};
|
||||
|
||||
fn thisDir() []const u8 {
|
||||
return std.fs.path.dirname(@src().file) orelse ".";
|
||||
}
|
||||
|
||||
pub fn link(b: *std.build.Builder, step: *std.build.LibExeObjStep) !*std.build.LibExeObjStep {
|
||||
const libuv = try buildLibuv(b, step);
|
||||
step.linkLibrary(libuv);
|
||||
step.addIncludePath(include_path);
|
||||
return libuv;
|
||||
}
|
||||
|
||||
pub fn buildLibuv(
|
||||
b: *std.build.Builder,
|
||||
step: *std.build.LibExeObjStep,
|
||||
) !*std.build.LibExeObjStep {
|
||||
const lib = b.addStaticLibrary("uv", null);
|
||||
lib.setTarget(step.target);
|
||||
lib.setBuildMode(step.build_mode);
|
||||
|
||||
const target = step.target;
|
||||
|
||||
// Include dirs
|
||||
lib.addIncludePath(include_path);
|
||||
lib.addIncludePath(root ++ "src");
|
||||
|
||||
// Links
|
||||
if (target.isWindows()) {
|
||||
lib.linkSystemLibrary("psapi");
|
||||
lib.linkSystemLibrary("user32");
|
||||
lib.linkSystemLibrary("advapi32");
|
||||
lib.linkSystemLibrary("iphlpapi");
|
||||
lib.linkSystemLibrary("userenv");
|
||||
lib.linkSystemLibrary("ws2_32");
|
||||
}
|
||||
if (target.isLinux()) {
|
||||
lib.linkSystemLibrary("pthread");
|
||||
}
|
||||
lib.linkLibC();
|
||||
|
||||
// Compilation
|
||||
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||
defer flags.deinit();
|
||||
// try flags.appendSlice(&.{});
|
||||
|
||||
if (!target.isWindows()) {
|
||||
try flags.appendSlice(&.{
|
||||
"-D_FILE_OFFSET_BITS=64",
|
||||
"-D_LARGEFILE_SOURCE",
|
||||
});
|
||||
}
|
||||
|
||||
if (target.isLinux()) {
|
||||
try flags.appendSlice(&.{
|
||||
"-D_GNU_SOURCE",
|
||||
"-D_POSIX_C_SOURCE=200112",
|
||||
});
|
||||
}
|
||||
|
||||
if (target.isDarwin()) {
|
||||
try flags.appendSlice(&.{
|
||||
"-D_DARWIN_UNLIMITED_SELECT=1",
|
||||
"-D_DARWIN_USE_64_BIT_INODE=1",
|
||||
});
|
||||
}
|
||||
|
||||
// C files common to all platforms
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/fs-poll.c",
|
||||
root ++ "src/idna.c",
|
||||
root ++ "src/inet.c",
|
||||
root ++ "src/random.c",
|
||||
root ++ "src/strscpy.c",
|
||||
root ++ "src/strtok.c",
|
||||
root ++ "src/threadpool.c",
|
||||
root ++ "src/timer.c",
|
||||
root ++ "src/uv-common.c",
|
||||
root ++ "src/uv-data-getter-setters.c",
|
||||
root ++ "src/version.c",
|
||||
}, flags.items);
|
||||
|
||||
if (!target.isWindows()) {
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/unix/async.c",
|
||||
root ++ "src/unix/core.c",
|
||||
root ++ "src/unix/dl.c",
|
||||
root ++ "src/unix/fs.c",
|
||||
root ++ "src/unix/getaddrinfo.c",
|
||||
root ++ "src/unix/getnameinfo.c",
|
||||
root ++ "src/unix/loop-watcher.c",
|
||||
root ++ "src/unix/loop.c",
|
||||
root ++ "src/unix/pipe.c",
|
||||
root ++ "src/unix/poll.c",
|
||||
root ++ "src/unix/process.c",
|
||||
root ++ "src/unix/random-devurandom.c",
|
||||
root ++ "src/unix/signal.c",
|
||||
root ++ "src/unix/stream.c",
|
||||
root ++ "src/unix/tcp.c",
|
||||
root ++ "src/unix/thread.c",
|
||||
root ++ "src/unix/tty.c",
|
||||
root ++ "src/unix/udp.c",
|
||||
}, flags.items);
|
||||
}
|
||||
|
||||
if (target.isLinux() or target.isDarwin()) {
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/unix/proctitle.c",
|
||||
}, flags.items);
|
||||
}
|
||||
|
||||
if (target.isLinux()) {
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/unix/linux-core.c",
|
||||
root ++ "src/unix/linux-inotify.c",
|
||||
root ++ "src/unix/linux-syscalls.c",
|
||||
root ++ "src/unix/procfs-exepath.c",
|
||||
root ++ "src/unix/random-getrandom.c",
|
||||
root ++ "src/unix/random-sysctl-linux.c",
|
||||
root ++ "src/unix/epoll.c",
|
||||
}, flags.items);
|
||||
}
|
||||
|
||||
if (target.isDarwin() or
|
||||
target.isOpenBSD() or
|
||||
target.isNetBSD() or
|
||||
target.isFreeBSD() or
|
||||
target.isDragonFlyBSD())
|
||||
{
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/unix/bsd-ifaddrs.c",
|
||||
root ++ "src/unix/kqueue.c",
|
||||
}, flags.items);
|
||||
}
|
||||
|
||||
if (target.isDarwin() or target.isOpenBSD()) {
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/unix/random-getentropy.c",
|
||||
}, flags.items);
|
||||
}
|
||||
|
||||
if (target.isDarwin()) {
|
||||
lib.addCSourceFiles(&.{
|
||||
root ++ "src/unix/darwin-proctitle.c",
|
||||
root ++ "src/unix/darwin.c",
|
||||
root ++ "src/unix/fsevents.c",
|
||||
}, flags.items);
|
||||
}
|
||||
|
||||
return lib;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub usingnamespace switch (builtin.zig_backend) {
|
||||
.stage1 => @cImport({
|
||||
@cInclude("uv.h");
|
||||
}),
|
||||
|
||||
// Workaround for:
|
||||
// https://github.com/ziglang/zig/issues/12325
|
||||
//
|
||||
// Generated by:
|
||||
// zig translate-c -target aarch64-macos -lc -Ivendor/libuv/include vendor/libuv/include/uv.h
|
||||
// (and then manually modified)
|
||||
else => switch (builtin.os.tag) {
|
||||
.macos => @import("cimport_macos.zig"),
|
||||
.linux => @import("cimport_linux.zig"),
|
||||
else => @compileError("unsupported OS for now, see this line"),
|
||||
},
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,196 +0,0 @@
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const c = @import("c.zig");
|
||||
|
||||
/// Enum mapping for errors.
|
||||
pub const Errno = enum(i32) {
|
||||
E2BIG = c.UV_E2BIG,
|
||||
EACCES = c.UV_EACCES,
|
||||
EADDRINUSE = c.UV_EADDRINUSE,
|
||||
EADDRNOTAVAIL = c.UV_EADDRNOTAVAIL,
|
||||
EAFNOSUPPORT = c.UV_EAFNOSUPPORT,
|
||||
EAGAIN = c.UV_EAGAIN,
|
||||
EAI_ADDRFAMILY = c.UV_EAI_ADDRFAMILY,
|
||||
EAI_AGAIN = c.UV_EAI_AGAIN,
|
||||
EAI_BADFLAGS = c.UV_EAI_BADFLAGS,
|
||||
EAI_BADHINTS = c.UV_EAI_BADHINTS,
|
||||
EAI_CANCELED = c.UV_EAI_CANCELED,
|
||||
EAI_FAIL = c.UV_EAI_FAIL,
|
||||
EAI_FAMILY = c.UV_EAI_FAMILY,
|
||||
EAI_MEMORY = c.UV_EAI_MEMORY,
|
||||
EAI_NODATA = c.UV_EAI_NODATA,
|
||||
EAI_NONAME = c.UV_EAI_NONAME,
|
||||
EAI_OVERFLOW = c.UV_EAI_OVERFLOW,
|
||||
EAI_PROTOCOL = c.UV_EAI_PROTOCOL,
|
||||
EAI_SERVICE = c.UV_EAI_SERVICE,
|
||||
EAI_SOCKTYPE = c.UV_EAI_SOCKTYPE,
|
||||
EALREADY = c.UV_EALREADY,
|
||||
EBADF = c.UV_EBADF,
|
||||
EBUSY = c.UV_EBUSY,
|
||||
ECANCELED = c.UV_ECANCELED,
|
||||
ECHARSET = c.UV_ECHARSET,
|
||||
ECONNABORTED = c.UV_ECONNABORTED,
|
||||
ECONNREFUSED = c.UV_ECONNREFUSED,
|
||||
ECONNRESET = c.UV_ECONNRESET,
|
||||
EDESTADDRREQ = c.UV_EDESTADDRREQ,
|
||||
EEXIST = c.UV_EEXIST,
|
||||
EFAULT = c.UV_EFAULT,
|
||||
EFBIG = c.UV_EFBIG,
|
||||
EHOSTUNREACH = c.UV_EHOSTUNREACH,
|
||||
EINTR = c.UV_EINTR,
|
||||
EINVAL = c.UV_EINVAL,
|
||||
EIO = c.UV_EIO,
|
||||
EISCONN = c.UV_EISCONN,
|
||||
EISDIR = c.UV_EISDIR,
|
||||
ELOOP = c.UV_ELOOP,
|
||||
EMFILE = c.UV_EMFILE,
|
||||
EMSGSIZE = c.UV_EMSGSIZE,
|
||||
ENAMETOOLONG = c.UV_ENAMETOOLONG,
|
||||
ENETDOWN = c.UV_ENETDOWN,
|
||||
ENETUNREACH = c.UV_ENETUNREACH,
|
||||
ENFILE = c.UV_ENFILE,
|
||||
ENOBUFS = c.UV_ENOBUFS,
|
||||
ENODEV = c.UV_ENODEV,
|
||||
ENOENT = c.UV_ENOENT,
|
||||
ENOMEM = c.UV_ENOMEM,
|
||||
ENONET = c.UV_ENONET,
|
||||
ENOPROTOOPT = c.UV_ENOPROTOOPT,
|
||||
ENOSPC = c.UV_ENOSPC,
|
||||
ENOSYS = c.UV_ENOSYS,
|
||||
ENOTCONN = c.UV_ENOTCONN,
|
||||
ENOTDIR = c.UV_ENOTDIR,
|
||||
ENOTEMPTY = c.UV_ENOTEMPTY,
|
||||
ENOTSOCK = c.UV_ENOTSOCK,
|
||||
ENOTSUP = c.UV_ENOTSUP,
|
||||
EPERM = c.UV_EPERM,
|
||||
EPIPE = c.UV_EPIPE,
|
||||
EPROTO = c.UV_EPROTO,
|
||||
EPROTONOSUPPORT = c.UV_EPROTONOSUPPORT,
|
||||
EPROTOTYPE = c.UV_EPROTOTYPE,
|
||||
ERANGE = c.UV_ERANGE,
|
||||
EROFS = c.UV_EROFS,
|
||||
ESHUTDOWN = c.UV_ESHUTDOWN,
|
||||
ESPIPE = c.UV_ESPIPE,
|
||||
ESRCH = c.UV_ESRCH,
|
||||
ETIMEDOUT = c.UV_ETIMEDOUT,
|
||||
ETXTBSY = c.UV_ETXTBSY,
|
||||
EXDEV = c.UV_EXDEV,
|
||||
UNKNOWN = c.UV_UNKNOWN,
|
||||
EOF = c.UV_EOF,
|
||||
ENXIO = c.UV_ENXIO,
|
||||
EHOSTDOWN = c.UV_EHOSTDOWN,
|
||||
EREMOTEIO = c.UV_EREMOTEIO,
|
||||
ENOTTY = c.UV_ENOTTY,
|
||||
EFTYPE = c.UV_EFTYPE,
|
||||
EILSEQ = c.UV_EILSEQ,
|
||||
ESOCKTNOSUPPORT = c.UV_ESOCKTNOSUPPORT,
|
||||
};
|
||||
|
||||
/// Errors that libuv can produce.
|
||||
///
|
||||
/// http://docs.libuv.org/en/v1.x/errors.html
|
||||
pub const Error = blk: {
|
||||
// We produce these from the Errno enum so that we can easily
|
||||
// keep it synced.
|
||||
const info = @typeInfo(Errno).Enum;
|
||||
var errors: [info.fields.len]std.builtin.Type.Error = undefined;
|
||||
for (info.fields) |field, i| {
|
||||
errors[i] = .{ .name = field.name };
|
||||
}
|
||||
|
||||
break :blk @Type(.{ .ErrorSet = &errors });
|
||||
};
|
||||
|
||||
/// Convert the result of a libuv API call to an error (or no error).
|
||||
pub fn convertError(r: i32) !void {
|
||||
if (r >= 0) return;
|
||||
|
||||
return switch (@intToEnum(Errno, r)) {
|
||||
.E2BIG => Error.E2BIG,
|
||||
.EACCES => Error.EACCES,
|
||||
.EADDRINUSE => Error.EADDRINUSE,
|
||||
.EADDRNOTAVAIL => Error.EADDRNOTAVAIL,
|
||||
.EAFNOSUPPORT => Error.EAFNOSUPPORT,
|
||||
.EAGAIN => Error.EAGAIN,
|
||||
.EAI_ADDRFAMILY => Error.EAI_ADDRFAMILY,
|
||||
.EAI_AGAIN => Error.EAI_AGAIN,
|
||||
.EAI_BADFLAGS => Error.EAI_BADFLAGS,
|
||||
.EAI_BADHINTS => Error.EAI_BADHINTS,
|
||||
.EAI_CANCELED => Error.EAI_CANCELED,
|
||||
.EAI_FAIL => Error.EAI_FAIL,
|
||||
.EAI_FAMILY => Error.EAI_FAMILY,
|
||||
.EAI_MEMORY => Error.EAI_MEMORY,
|
||||
.EAI_NODATA => Error.EAI_NODATA,
|
||||
.EAI_NONAME => Error.EAI_NONAME,
|
||||
.EAI_OVERFLOW => Error.EAI_OVERFLOW,
|
||||
.EAI_PROTOCOL => Error.EAI_PROTOCOL,
|
||||
.EAI_SERVICE => Error.EAI_SERVICE,
|
||||
.EAI_SOCKTYPE => Error.EAI_SOCKTYPE,
|
||||
.EALREADY => Error.EALREADY,
|
||||
.EBADF => Error.EBADF,
|
||||
.EBUSY => Error.EBUSY,
|
||||
.ECANCELED => Error.ECANCELED,
|
||||
.ECHARSET => Error.ECHARSET,
|
||||
.ECONNABORTED => Error.ECONNABORTED,
|
||||
.ECONNREFUSED => Error.ECONNREFUSED,
|
||||
.ECONNRESET => Error.ECONNRESET,
|
||||
.EDESTADDRREQ => Error.EDESTADDRREQ,
|
||||
.EEXIST => Error.EEXIST,
|
||||
.EFAULT => Error.EFAULT,
|
||||
.EFBIG => Error.EFBIG,
|
||||
.EHOSTUNREACH => Error.EHOSTUNREACH,
|
||||
.EINTR => Error.EINTR,
|
||||
.EINVAL => Error.EINVAL,
|
||||
.EIO => Error.EIO,
|
||||
.EISCONN => Error.EISCONN,
|
||||
.EISDIR => Error.EISDIR,
|
||||
.ELOOP => Error.ELOOP,
|
||||
.EMFILE => Error.EMFILE,
|
||||
.EMSGSIZE => Error.EMSGSIZE,
|
||||
.ENAMETOOLONG => Error.ENAMETOOLONG,
|
||||
.ENETDOWN => Error.ENETDOWN,
|
||||
.ENETUNREACH => Error.ENETUNREACH,
|
||||
.ENFILE => Error.ENFILE,
|
||||
.ENOBUFS => Error.ENOBUFS,
|
||||
.ENODEV => Error.ENODEV,
|
||||
.ENOENT => Error.ENOENT,
|
||||
.ENOMEM => Error.ENOMEM,
|
||||
.ENONET => Error.ENONET,
|
||||
.ENOPROTOOPT => Error.ENOPROTOOPT,
|
||||
.ENOSPC => Error.ENOSPC,
|
||||
.ENOSYS => Error.ENOSYS,
|
||||
.ENOTCONN => Error.ENOTCONN,
|
||||
.ENOTDIR => Error.ENOTDIR,
|
||||
.ENOTEMPTY => Error.ENOTEMPTY,
|
||||
.ENOTSOCK => Error.ENOTSOCK,
|
||||
.ENOTSUP => Error.ENOTSUP,
|
||||
.EPERM => Error.EPERM,
|
||||
.EPIPE => Error.EPIPE,
|
||||
.EPROTO => Error.EPROTO,
|
||||
.EPROTONOSUPPORT => Error.EPROTONOSUPPORT,
|
||||
.EPROTOTYPE => Error.EPROTOTYPE,
|
||||
.ERANGE => Error.ERANGE,
|
||||
.EROFS => Error.EROFS,
|
||||
.ESHUTDOWN => Error.ESHUTDOWN,
|
||||
.ESPIPE => Error.ESPIPE,
|
||||
.ESRCH => Error.ESRCH,
|
||||
.ETIMEDOUT => Error.ETIMEDOUT,
|
||||
.ETXTBSY => Error.ETXTBSY,
|
||||
.EXDEV => Error.EXDEV,
|
||||
.UNKNOWN => Error.UNKNOWN,
|
||||
.EOF => Error.EOF,
|
||||
.ENXIO => Error.ENXIO,
|
||||
.EHOSTDOWN => Error.EHOSTDOWN,
|
||||
.EREMOTEIO => Error.EREMOTEIO,
|
||||
.ENOTTY => Error.ENOTTY,
|
||||
.EFTYPE => Error.EFTYPE,
|
||||
.EILSEQ => Error.EILSEQ,
|
||||
.ESOCKTNOSUPPORT => Error.ESOCKTNOSUPPORT,
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
// This is mostly just forcing our error type and function to be
|
||||
// codegenned and run once to ensure we have all the types.
|
||||
try testing.expectError(Error.EFTYPE, convertError(c.UV_EFTYPE));
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
const c = @import("c.zig");
|
||||
|
||||
const Loop = @import("Loop.zig");
|
||||
const errors = @import("error.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
|
||||
/// handler functions to other handle types.
|
||||
pub fn Handle(comptime T: type) type {
|
||||
// 1. T should be a struct
|
||||
// 2. First field should be the handle pointer
|
||||
|
||||
return struct {
|
||||
// note: this has to be here: https://github.com/ziglang/zig/issues/11367
|
||||
const tInfo = @typeInfo(T).Struct;
|
||||
const HandleType = tInfo.fields[0].type;
|
||||
|
||||
// Request handle to be closed. close_cb will be called asynchronously
|
||||
// after this call. This MUST be called on each handle before memory
|
||||
// is released. Moreover, the memory can only be released in close_cb
|
||||
// or after it has returned.
|
||||
//
|
||||
// Handles that wrap file descriptors are closed immediately but
|
||||
// close_cb will still be deferred to the next iteration of the event
|
||||
// loop. It gives you a chance to free up any resources associated with
|
||||
// the handle.
|
||||
//
|
||||
// In-progress requests, like uv_connect_t or uv_write_t, are cancelled
|
||||
// and have their callbacks called asynchronously with status=UV_ECANCELED.
|
||||
pub fn close(self: T, comptime cb: ?fn (*T) void) void {
|
||||
const cbParam = if (cb) |f|
|
||||
(struct {
|
||||
pub fn callback(handle: [*c]c.uv_handle_t) callconv(.C) void {
|
||||
// We get the raw handle, so we need to reconstruct
|
||||
// the T. This is mutable because a lot of the libuv APIs
|
||||
// are non-const but modifying it makes no sense.
|
||||
var param: T = .{ .handle = @ptrCast(HandleType, handle) };
|
||||
@call(.always_inline, f, .{¶m});
|
||||
}
|
||||
}).callback
|
||||
else
|
||||
null;
|
||||
|
||||
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) };
|
||||
}
|
||||
|
||||
/// Returns non-zero if the handle is active, zero if it’s inactive.
|
||||
/// Rule of thumb: if a handle of type uv_foo_t has a uv_foo_start()
|
||||
/// function, then it’s active from the moment that function is called.
|
||||
/// Likewise, uv_foo_stop() deactivates the handle again.
|
||||
pub fn isActive(self: T) !bool {
|
||||
const res = c.uv_is_active(@ptrCast(*c.uv_handle_t, self.handle));
|
||||
try errors.convertError(res);
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
/// Sets handle->data to data.
|
||||
pub fn setData(self: T, pointer: ?*anyopaque) void {
|
||||
c.uv_handle_set_data(
|
||||
@ptrCast(*c.uv_handle_t, self.handle),
|
||||
pointer,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns handle->data.
|
||||
pub fn getData(self: T, comptime DT: type) ?*DT {
|
||||
return if (c.uv_handle_get_data(@ptrCast(*c.uv_handle_t, self.handle))) |ptr|
|
||||
@ptrCast(?*DT, @alignCast(@alignOf(DT), ptr))
|
||||
else
|
||||
null;
|
||||
}
|
||||
};
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
const std = @import("std");
|
||||
const stream = @import("stream.zig");
|
||||
|
||||
pub const c = @import("c.zig");
|
||||
pub const Loop = @import("Loop.zig");
|
||||
pub const Async = @import("Async.zig");
|
||||
pub const Idle = @import("Idle.zig");
|
||||
pub const Pipe = @import("Pipe.zig");
|
||||
pub const Prepare = @import("Prepare.zig");
|
||||
pub const Timer = @import("Timer.zig");
|
||||
pub const Tty = @import("Tty.zig");
|
||||
pub const Cond = @import("Cond.zig");
|
||||
pub const Mutex = @import("Mutex.zig");
|
||||
pub const Sem = @import("Sem.zig");
|
||||
pub const Thread = @import("Thread.zig");
|
||||
pub const WriteReq = stream.WriteReq;
|
||||
|
||||
pub const Embed = @import("Embed.zig");
|
||||
|
||||
pub usingnamespace @import("error.zig");
|
||||
|
||||
test {
|
||||
// Leak a loop... I don't know why but this fixes CI failures. Probably
|
||||
// a miscompilation or something. TODO: double check this once self-hosted
|
||||
// lands to see if we need this.
|
||||
_ = try Loop.init(std.heap.page_allocator);
|
||||
|
||||
_ = @import("tests.zig");
|
||||
_ = stream;
|
||||
|
||||
_ = Loop;
|
||||
_ = Async;
|
||||
_ = Idle;
|
||||
_ = Prepare;
|
||||
_ = Pipe;
|
||||
_ = Timer;
|
||||
_ = Tty;
|
||||
_ = Cond;
|
||||
_ = Mutex;
|
||||
_ = Sem;
|
||||
_ = Thread;
|
||||
|
||||
_ = Embed;
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
const c = @import("c.zig");
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const Loop = @import("Loop.zig");
|
||||
const errors = @import("error.zig");
|
||||
const Error = errors.Error;
|
||||
|
||||
/// Returns a struct that has all the shared stream functions for the
|
||||
/// given stream type T. The type T must have a field named "handle".
|
||||
/// This is expected to be used with usingnamespace to add the shared
|
||||
/// stream functions to other handle types.
|
||||
pub fn Stream(comptime T: type) type {
|
||||
// 1. T should be a struct
|
||||
// 2. First field should be the handle pointer
|
||||
|
||||
return struct {
|
||||
// note: this has to be here: https://github.com/ziglang/zig/issues/11367
|
||||
const tInfo = @typeInfo(T).Struct;
|
||||
const HandleType = tInfo.fields[0].type;
|
||||
|
||||
/// Returns 1 if the stream is readable, 0 otherwise.
|
||||
pub fn isReadable(self: T) !bool {
|
||||
const res = c.uv_is_readable(@ptrCast(*c.uv_stream_t, self.handle));
|
||||
try errors.convertError(res);
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
/// Returns 1 if the stream is writable, 0 otherwise.
|
||||
pub fn isWritable(self: T) !bool {
|
||||
const res = c.uv_is_writable(@ptrCast(*c.uv_stream_t, self.handle));
|
||||
try errors.convertError(res);
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
/// Write data to stream. Buffers are written in order.
|
||||
pub fn write(
|
||||
self: T,
|
||||
req: WriteReq,
|
||||
bufs: []const []const u8,
|
||||
comptime cb: fn (req: *WriteReq, status: i32) void,
|
||||
) !void {
|
||||
const Wrapper = struct {
|
||||
fn callback(cbreq: [*c]c.uv_write_t, status: c_int) callconv(.C) void {
|
||||
var newreq: WriteReq = .{ .req = cbreq };
|
||||
@call(.always_inline, cb, .{
|
||||
&newreq,
|
||||
@intCast(i32, status),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// We can directly ptrCast bufs.ptr to a C pointer of uv_buf_t
|
||||
// because they have the exact same layout in memory. We have a
|
||||
// unit test below that keeps this true.
|
||||
try errors.convertError(c.uv_write(
|
||||
req.req,
|
||||
@ptrCast(*c.uv_stream_t, self.handle),
|
||||
@ptrCast([*c]const c.uv_buf_t, bufs.ptr),
|
||||
@intCast(c_uint, bufs.len),
|
||||
Wrapper.callback,
|
||||
));
|
||||
}
|
||||
|
||||
/// Same as uv_write(), but won’t queue a write request if it can’t
|
||||
/// be completed immediately.
|
||||
pub fn tryWrite(self: T, bufs: []const []const u8) !usize {
|
||||
const res = c.uv_try_write(
|
||||
@ptrCast(*c.uv_stream_t, self.handle),
|
||||
@ptrCast([*c]const c.uv_buf_t, bufs.ptr),
|
||||
@intCast(c_uint, bufs.len),
|
||||
);
|
||||
try errors.convertError(res);
|
||||
return @intCast(usize, res);
|
||||
}
|
||||
|
||||
/// Read data from an incoming stream. The uv_read_cb callback will
|
||||
/// be made several times until there is no more data to read or
|
||||
/// uv_read_stop() is called.
|
||||
pub fn readStart(
|
||||
self: T,
|
||||
comptime alloc_cb: fn (self: *T, size: usize) ?[]u8,
|
||||
comptime read_cb: fn (self: *T, nread: isize, buf: []const u8) void,
|
||||
) !void {
|
||||
const Wrapper = struct {
|
||||
fn alloc(
|
||||
cbhandle: [*c]c.uv_handle_t,
|
||||
cbsize: usize,
|
||||
buf: [*c]c.uv_buf_t,
|
||||
) callconv(.C) void {
|
||||
var param: T = .{ .handle = @ptrCast(HandleType, cbhandle) };
|
||||
const result = @call(.always_inline, alloc_cb, .{
|
||||
¶m,
|
||||
cbsize,
|
||||
});
|
||||
|
||||
if (result) |slice| {
|
||||
buf.* = .{
|
||||
.base = slice.ptr,
|
||||
.len = slice.len,
|
||||
};
|
||||
} else {
|
||||
buf.* = .{ .base = null, .len = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
fn read(
|
||||
cbhandle: [*c]c.uv_stream_t,
|
||||
cbnread: isize,
|
||||
cbbuf: [*c]const c.uv_buf_t,
|
||||
) callconv(.C) void {
|
||||
var param: T = .{ .handle = @ptrCast(HandleType, cbhandle) };
|
||||
@call(.always_inline, read_cb, .{
|
||||
¶m,
|
||||
cbnread,
|
||||
cbbuf.*.base[0..cbbuf.*.len],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
try errors.convertError(c.uv_read_start(
|
||||
@ptrCast(*c.uv_stream_t, self.handle),
|
||||
Wrapper.alloc,
|
||||
Wrapper.read,
|
||||
));
|
||||
}
|
||||
|
||||
/// Stop reading data from the stream. The uv_read_cb callback will
|
||||
/// no longer be called.
|
||||
///
|
||||
/// This function is idempotent and may be safely called on a stopped
|
||||
/// stream.
|
||||
pub fn readStop(self: T) void {
|
||||
// Docs say we can ignore this result.
|
||||
_ = c.uv_read_stop(@ptrCast(*c.uv_stream_t, self.handle));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Write request type. Careful attention must be paid when reusing objects
|
||||
/// of this type. When a stream is in non-blocking mode, write requests sent
|
||||
/// with uv_write will be queued. Reusing objects at this point is undefined
|
||||
/// behaviour. It is safe to reuse the uv_write_t object only after the
|
||||
/// callback passed to uv_write is fired.
|
||||
pub const WriteReq = struct {
|
||||
/// This is the underlying type that WriteReq wraps. This is exposed
|
||||
/// so that you can pre-allocate the type and wrap it in a WrapReq.
|
||||
pub const T = c.uv_write_t;
|
||||
|
||||
req: *T,
|
||||
|
||||
pub fn init(alloc: Allocator) !WriteReq {
|
||||
var req = try alloc.create(c.uv_write_t);
|
||||
errdefer alloc.destroy(req);
|
||||
return WriteReq{ .req = req };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *WriteReq, alloc: Allocator) void {
|
||||
alloc.destroy(self.req);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Pointer to the stream where this write request is running.
|
||||
/// T should be a high-level handle type such as "Pipe".
|
||||
pub fn handle(self: WriteReq, comptime HT: type) ?HT {
|
||||
const tInfo = @typeInfo(HT).Struct;
|
||||
const HandleType = tInfo.fields[0].type;
|
||||
|
||||
return if (self.req.handle) |ptr|
|
||||
return HT{ .handle = @ptrCast(HandleType, ptr) }
|
||||
else
|
||||
null;
|
||||
}
|
||||
|
||||
test "Write: create and destroy" {
|
||||
var h = try init(testing.allocator);
|
||||
defer h.deinit(testing.allocator);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
_ = WriteReq;
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
//! 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);
|
||||
}
|
Reference in New Issue
Block a user