pkg/libuv: delete

This commit is contained in:
Mitchell Hashimoto
2023-02-06 13:07:47 -08:00
parent a5d03d1318
commit 18f20add34
21 changed files with 0 additions and 22388 deletions

View File

@ -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 handles 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);
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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 wont 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 loops 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.
/// Dont 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 loops 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 wont 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));
}

View File

@ -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);
}

View File

@ -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);
}
};

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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.*;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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));
}

View File

@ -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, .{&param});
}
}).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 its inactive.
/// Rule of thumb: if a handle of type uv_foo_t has a uv_foo_start()
/// function, then its 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;
}
};
}

View File

@ -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;
}

View File

@ -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 wont queue a write request if it cant
/// 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, .{
&param,
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, .{
&param,
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;
}

View File

@ -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);
}