mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-09 12:51:45 -04:00
serve: Improve cdp/bidi server
Significant rework of the CDP/BiDi server. There are two main changes: 1 - poll replaced with EPoll/Kqueue (1) 2 - make http serving a first class citizen The change from poll -> epoll/kqueue isn't performance driven, it's just about tighter code. Both epoll and kqueue let you associate arbitrary data with a socket, so we don't need to keep arrays in sync in order to associate a socket with a CDP by index. They both provide some event/notification mechanism, which is cleaner than the pipe required by poll. The poll -> epoll/kqueue change could almost have been mechanical. Making HTTP a first class citizen is the more significant of the two changes In `main`, a new connection always spawns a thread and, until does its own little read loop until the connection is upgraded. This is not efficient, it uses up a connection slot, and it's inconsistent with the final WebSocket connection which _is_ polled off the main loop. Using up a slot means that keepalive isn't possible, else HTTP connections would quickly use up all available slots/threads. This commit parses and serves HTTP requests on the main thread (safe because none of the processing is blocking). The approach is better streamlined for HTTP requests which never upgrade (/metrics, WebDriver) without causing any performance overhead for those that do. It simplifies some things (e.g. an "http" socket or a "websocket" socket is monitored and read in a similar manner (on the main loop)). It makes other things more complicated; the flow is no longer accept -> spawn -> upgrade -> websocket loop. It's loop -> accept -> loop -> process -> (http | ws). This is built ontop of the BiDi branch because (a) WebDriver is what needs better HTTP support and (b) some of the more mechanical changes already exist in that branch (e.g. src/cdp/, src/server.zig -> src/server/*) (1) kqueue landing in 2 commits from now on this branch.
This commit is contained in:
11 files changed
+2155
-1686
No files matched your search
+65
-23
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
// Pierres Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
@@ -180,28 +180,6 @@ pub fn getsockname(sock: socket_t, addr: *posix.sockaddr, len: *posix.socklen_t)
|
||||
}
|
||||
}
|
||||
|
||||
/// pipe2 semantics; flags applied via fcntl since macOS has no pipe2.
|
||||
pub fn pipe2(flags: struct { NONBLOCK: bool = false, CLOEXEC: bool = false }) ![2]posix.fd_t {
|
||||
var fds: [2]posix.fd_t = undefined;
|
||||
const rc = c.pipe(&fds);
|
||||
if (rc != 0) {
|
||||
return errnoError(c.errno(rc));
|
||||
}
|
||||
errdefer for (fds) |fd| {
|
||||
_ = c.close(fd);
|
||||
};
|
||||
for (fds) |fd| {
|
||||
if (flags.NONBLOCK) {
|
||||
const fl = try fcntl(fd, posix.F.GETFL, 0);
|
||||
_ = try fcntl(fd, posix.F.SETFL, fl | @as(u32, @bitCast(posix.O{ .NONBLOCK = true })));
|
||||
}
|
||||
if (flags.CLOEXEC) {
|
||||
_ = try fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC);
|
||||
}
|
||||
}
|
||||
return fds;
|
||||
}
|
||||
|
||||
pub fn connect(addr: *const IpAddress) !socket_t {
|
||||
const sock = try socket(family(addr), posix.SOCK.STREAM, posix.IPPROTO.TCP);
|
||||
errdefer _ = c.close(sock);
|
||||
@@ -242,6 +220,70 @@ pub fn fcntl(fd: posix.fd_t, cmd: i32, arg: usize) !usize {
|
||||
return @intCast(rc);
|
||||
}
|
||||
|
||||
pub fn close(fd: posix.fd_t) void {
|
||||
switch (c.errno(c.close(fd))) {
|
||||
.BADF => unreachable, // Always a race condition.
|
||||
.INTR => {}, // This is still a success. See https://github.com/ziglang/zig/issues/2425
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoll_create1(flags: u32) !i32 {
|
||||
const rc = c.epoll_create1(flags);
|
||||
return switch (c.errno(rc)) {
|
||||
.SUCCESS => return @intCast(rc),
|
||||
.INVAL => unreachable,
|
||||
.MFILE => error.ProcessFdQuotaExceeded,
|
||||
.NFILE => error.SystemFdQuotaExceeded,
|
||||
.NOMEM => error.SystemResources,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn eventfd(initval: u32, flags: u32) !i32 {
|
||||
const rc = c.eventfd(initval, flags);
|
||||
return switch (c.errno(rc)) {
|
||||
.SUCCESS => @intCast(rc),
|
||||
.INVAL => unreachable, // invalid parameters
|
||||
.MFILE => error.ProcessFdQuotaExceeded,
|
||||
.NFILE => error.SystemFdQuotaExceeded,
|
||||
.NODEV => error.SystemResources,
|
||||
.NOMEM => error.SystemResources,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: ?*c.epoll_event) !void {
|
||||
const rc = c.epoll_ctl(epfd, op, fd, event);
|
||||
return switch (c.errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.BADF => unreachable, // always a race condition if this happens
|
||||
.EXIST => error.FileDescriptorAlreadyPresentInSet,
|
||||
.INVAL => unreachable,
|
||||
.LOOP => error.OperationCausesCircularLoop,
|
||||
.NOENT => error.FileDescriptorNotRegistered,
|
||||
.NOMEM => error.SystemResources,
|
||||
.NOSPC => error.UserResourceLimitReached,
|
||||
.PERM => error.FileDescriptorIncompatibleWithEpoll,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn epoll_wait(epfd: i32, events: []c.epoll_event, timeout: i32) usize {
|
||||
while (true) {
|
||||
// TODO get rid of the @intCast
|
||||
const rc = c.epoll_wait(epfd, events.ptr, @intCast(events.len), timeout);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => return @intCast(rc),
|
||||
.INTR => continue,
|
||||
.BADF => unreachable,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn errnoError(e: posix.E) anyerror {
|
||||
return switch (e) {
|
||||
.AGAIN => error.WouldBlock,
|
||||
|
||||
Reference in new issue
Block a user