This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

zio / src / poller.zig
6.6 kB 185 lines
1//! platform readiness poller: epoll (linux) / kqueue (darwin, bsd). 2//! level-triggered on both platforms — handlers drain until EAGAIN. 3//! written against zig 0.16.0; syscalls via std.c (libc required). 4const std = @import("std"); 5const builtin = @import("builtin"); 6const c = std.c; 7 8pub const Interest = struct { 9 read: bool = false, 10 write: bool = false, 11}; 12 13pub const Event = struct { 14 token: u64, 15 readable: bool, 16 writable: bool, 17 hup: bool, 18}; 19 20pub const Poller = switch (builtin.os.tag) { 21 .linux => Epoll, 22 .macos, .ios, .tvos, .watchos, .visionos, .freebsd, .netbsd, .openbsd, .dragonfly => Kqueue, 23 else => @compileError("zio poller: unsupported os"), 24}; 25 26pub const Epoll = struct { 27 epfd: c.fd_t, 28 29 const EPOLL = std.os.linux.EPOLL; 30 31 pub fn init() !Epoll { 32 const epfd = c.epoll_create1(EPOLL.CLOEXEC); 33 if (epfd < 0) return error.PollerInitFailed; 34 return .{ .epfd = epfd }; 35 } 36 37 pub fn deinit(p: *Epoll) void { 38 _ = c.close(p.epfd); 39 } 40 41 pub fn add(p: *Epoll, fd: c.fd_t, interest: Interest, token: u64) !void { 42 var ev: c.epoll_event = .{ 43 .events = eventsFrom(interest), 44 .data = .{ .u64 = token }, 45 }; 46 if (c.epoll_ctl(p.epfd, EPOLL.CTL_ADD, fd, &ev) != 0) return error.PollerAddFailed; 47 } 48 49 pub fn del(p: *Epoll, fd: c.fd_t) void { 50 _ = c.epoll_ctl(p.epfd, EPOLL.CTL_DEL, fd, null); 51 } 52 53 pub fn mod(p: *Epoll, fd: c.fd_t, interest: Interest, token: u64) !void { 54 var ev: c.epoll_event = .{ 55 .events = eventsFrom(interest), 56 .data = .{ .u64 = token }, 57 }; 58 if (c.epoll_ctl(p.epfd, EPOLL.CTL_MOD, fd, &ev) != 0) return error.PollerModFailed; 59 } 60 61 /// arm a single edge: fires once, then disarms until re-armed. 62 pub fn armOneshot(p: *Epoll, fd: c.fd_t, interest: Interest, token: u64) !void { 63 var ev: c.epoll_event = .{ 64 .events = eventsFrom(interest) | EPOLL.ONESHOT, 65 .data = .{ .u64 = token }, 66 }; 67 // fd may or may not be registered; try MOD first (common: re-arm), fall back to ADD. 68 if (c.epoll_ctl(p.epfd, EPOLL.CTL_MOD, fd, &ev) == 0) return; 69 if (c.epoll_ctl(p.epfd, EPOLL.CTL_ADD, fd, &ev) == 0) return; 70 return error.PollerArmFailed; 71 } 72 73 /// fills `out` with ready events; blocks up to timeout_ms. returns slice of out. 74 pub fn wait(p: *Epoll, out: []Event, timeout_ms: i32) []Event { 75 var raw: [max_batch]c.epoll_event = undefined; 76 const cap: c_uint = @intCast(@min(out.len, raw.len)); 77 const n = c.epoll_wait(p.epfd, &raw, cap, timeout_ms); 78 if (n <= 0) return out[0..0]; 79 const count: usize = @intCast(n); 80 for (raw[0..count], out[0..count]) |r, *e| { 81 e.* = .{ 82 .token = r.data.u64, 83 .readable = r.events & EPOLL.IN != 0, 84 .writable = r.events & EPOLL.OUT != 0, 85 .hup = r.events & (EPOLL.HUP | EPOLL.ERR | EPOLL.RDHUP) != 0, 86 }; 87 } 88 return out[0..count]; 89 } 90 91 fn eventsFrom(interest: Interest) u32 { 92 var events: u32 = EPOLL.RDHUP; 93 if (interest.read) events |= EPOLL.IN; 94 if (interest.write) events |= EPOLL.OUT; 95 return events; 96 } 97}; 98 99pub const Kqueue = struct { 100 kq: c.fd_t, 101 102 pub fn init() !Kqueue { 103 const kq = c.kqueue(); 104 if (kq < 0) return error.PollerInitFailed; 105 return .{ .kq = kq }; 106 } 107 108 pub fn deinit(p: *Kqueue) void { 109 _ = c.close(p.kq); 110 } 111 112 pub fn add(p: *Kqueue, fd: c.fd_t, interest: Interest, token: u64) !void { 113 // both filters are always registered; interest toggles ENABLE/DISABLE. 114 // kqueue EV_ADD on an existing (ident,filter) updates it, so mod == add. 115 const changes = [2]c.Kevent{ 116 kev(fd, c.EVFILT.READ, interest.read, token), 117 kev(fd, c.EVFILT.WRITE, interest.write, token), 118 }; 119 var none: [0]c.Kevent = .{}; 120 const rc = c.kevent(p.kq, &changes, changes.len, &none, 0, null); 121 if (rc < 0) return error.PollerAddFailed; 122 } 123 124 pub fn mod(p: *Kqueue, fd: c.fd_t, interest: Interest, token: u64) !void { 125 return p.add(fd, interest, token) catch error.PollerModFailed; 126 } 127 128 /// arm a single edge: fires once, then disarms until re-armed. 129 pub fn armOneshot(p: *Kqueue, fd: c.fd_t, interest: Interest, token: u64) !void { 130 var changes: [1]c.Kevent = undefined; 131 const filter: @FieldType(c.Kevent, "filter") = if (interest.read) c.EVFILT.READ else c.EVFILT.WRITE; 132 changes[0] = .{ 133 .ident = @intCast(fd), 134 .filter = filter, 135 .flags = c.EV.ADD | c.EV.ENABLE | c.EV.ONESHOT, 136 .fflags = 0, 137 .data = 0, 138 .udata = token, 139 }; 140 var none: [0]c.Kevent = .{}; 141 if (c.kevent(p.kq, &changes, changes.len, &none, 0, null) < 0) return error.PollerArmFailed; 142 } 143 144 pub fn del(p: *Kqueue, fd: c.fd_t) void { 145 // closing the fd removes its kevents automatically; explicit EV_DELETE 146 // would race with close-initiated removal, so rely on close semantics. 147 _ = p; 148 _ = fd; 149 } 150 151 pub fn wait(p: *Kqueue, out: []Event, timeout_ms: i32) []Event { 152 var raw: [max_batch]c.Kevent = undefined; 153 const cap: c_int = @intCast(@min(out.len, raw.len)); 154 const ts: c.timespec = .{ 155 .sec = @divTrunc(timeout_ms, 1000), 156 .nsec = @as(@FieldType(c.timespec, "nsec"), @intCast(@mod(timeout_ms, 1000))) * 1_000_000, 157 }; 158 const no_changes: [0]c.Kevent = .{}; 159 const n = c.kevent(p.kq, &no_changes, 0, &raw, cap, &ts); 160 if (n <= 0) return out[0..0]; 161 const count: usize = @intCast(n); 162 for (raw[0..count], out[0..count]) |r, *e| { 163 e.* = .{ 164 .token = r.udata, 165 .readable = r.filter == c.EVFILT.READ, 166 .writable = r.filter == c.EVFILT.WRITE, 167 .hup = r.flags & c.EV.EOF != 0, 168 }; 169 } 170 return out[0..count]; 171 } 172 173 fn kev(fd: c.fd_t, filter: @FieldType(c.Kevent, "filter"), enabled: bool, token: u64) c.Kevent { 174 return .{ 175 .ident = @intCast(fd), 176 .filter = filter, 177 .flags = c.EV.ADD | @as(@FieldType(c.Kevent, "flags"), if (enabled) c.EV.ENABLE else c.EV.DISABLE), 178 .fflags = 0, 179 .data = 0, 180 .udata = token, 181 }; 182 } 183}; 184 185pub const max_batch = 256;