A websocket implementation for zig
0

Configure Feed

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

client: poll for read readiness instead of SO_RCVTIMEO

The TLS read path goes through std.crypto.tls, whose reader treats a socket
EAGAIN (produced by SO_RCVTIMEO on timeout) as a programmer bug and panics in
debug builds. Poll the fd for readiness instead, so a read timeout surfaces as
error.WouldBlock without ever issuing a read that can EAGAIN.

+23 -3
+23 -3
src/client/client.zig
··· 286 286 return self.stream.writeTimeout(ms); 287 287 } 288 288 289 - pub fn readTimeout(self: *const Client, ms: u32) !void { 289 + pub fn readTimeout(self: *Client, ms: u32) !void { 290 290 return self.stream.readTimeout(ms); 291 291 } 292 292 ··· 400 400 io: Io, 401 401 stream: Io.net.Stream, 402 402 tls_client: ?*TLSClient = null, 403 + read_timeout_ms: u32 = 0, 403 404 404 405 pub fn init(io: Io, stream: Io.net.Stream, tls_client: ?*TLSClient) Stream { 405 406 return .{ ··· 441 442 } 442 443 443 444 pub fn read(self: *Stream, buf: []u8) !usize { 445 + // A read timeout is implemented by polling the socket for readiness 446 + // rather than SO_RCVTIMEO. On the TLS path the underlying read goes 447 + // through std.crypto.tls, whose reader treats a socket EAGAIN (what 448 + // SO_RCVTIMEO produces on timeout) as a programmer bug and panics in 449 + // debug builds. Polling lets a timeout surface as error.WouldBlock (which 450 + // read() turns into "no message") without ever issuing a read that could 451 + // EAGAIN. The caller drains its buffer before reaching here, so polling 452 + // only gates an actual socket read. 453 + if (self.read_timeout_ms > 0) { 454 + var pfd = [_]std.posix.pollfd{.{ 455 + .fd = self.stream.socket.handle, 456 + .events = std.posix.POLL.IN, 457 + .revents = 0, 458 + }}; 459 + const ready = std.posix.poll(&pfd, @intCast(self.read_timeout_ms)) catch 0; 460 + if (ready == 0) return error.WouldBlock; 461 + } 444 462 if (self.tls_client) |tls_client| { 445 463 var w: std.Io.Writer = .fixed(buf); 446 464 while (true) { ··· 473 491 return self.setTimeout(posix.SO.SNDTIMEO, ms); 474 492 } 475 493 476 - pub fn readTimeout(self: *const Stream, ms: u32) !void { 477 - return self.setTimeout(posix.SO.RCVTIMEO, ms); 494 + pub fn readTimeout(self: *Stream, ms: u32) !void { 495 + // Stored and applied via poll() in read(); see the note there for why this 496 + // does not use SO_RCVTIMEO. 497 + self.read_timeout_ms = ms; 478 498 } 479 499 480 500 fn setTimeout(self: *const Stream, opt_name: u32, ms: u32) !void {