Files
ghostty/src/libuv/Embed.zig
Mitchell Hashimoto cca32c4d1c embedded libuv loop. still some issues:
1. 100% CPU if no handles/requests
2. slow to exit cause it waits for the next tick
2022-04-22 10:01:52 -07:00

110 lines
3.2 KiB
Zig

//! 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 Sem = @import("Sem.zig");
const Thread = @import("Thread.zig");
const TerminateAtomic = std.atomic.Atomic(bool);
loop: Loop,
sem: Sem,
terminate: TerminateAtomic,
callback: 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: fn () void) !Embed {
return Embed{
.loop = loop,
.sem = try Sem.init(alloc, 0),
.terminate = TerminateAtomic.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 {
std.debug.assert(self.thread == null);
self.sem.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 {
var thread = self.thread orelse return;
// Mark that we want to terminate
self.terminate.store(true, .SeqCst);
// Post to the semaphore to ensure that any waits are processed.
self.sem.post();
// Wait
try thread.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 {
_ = try self.loop.run(.nowait);
self.sem.post();
}
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 => {
std.log.info("FOO {} {}", .{ fd, timeout });
var ev: [1]std.os.linux.epoll_event = undefined;
while (std.os.epoll_wait(fd, &ev, timeout) == -1) {}
},
else => @compileError("unsupported libuv Embed platform"),
}
// Call our trigger
self.callback();
// Wait for libuv to run a tick
self.sem.wait();
}
}
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();
try embed.stop();
}