An HTTP/1.1 server for zig
0

Configure Feed

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

Fix lost websocket read by releasing processing before re-arm

With EPOLLONESHOT, re-arming the fd while still holding processing loses a read
that arrives in the window between the re-arm and the release: the event loop
sees the connection as busy and the one-shot arming is consumed, so the read is
dropped and the connection hangs / leaks (CLOSE_WAIT). Release processing before
re-arming instead; no extra state needed.

+11 -2
+11 -2
src/worker.zig
··· 851 851 } 852 852 853 853 pub fn processWebsocketData(self: *Self, conn: *Conn(WSH), thread_buf: []u8, hc: *ws.HandlerConn(WSH)) void { 854 - defer conn.releaseProcessing(); 855 - 856 854 var ws_conn = &hc.conn; 857 855 const success = self.websocket.worker.dataAvailable(hc, thread_buf); 858 856 if (success == false) { 859 857 ws_conn.close(.{ .code = 4997, .reason = "wsz" }) catch {}; 860 858 self.websocket.cleanupConn(hc); 859 + conn.releaseProcessing(); 861 860 } else if (ws_conn.isClosed()) { 862 861 self.websocket.cleanupConn(hc); 862 + conn.releaseProcessing(); 863 863 } else { 864 + // Release `processing` before re-arming. With EPOLLONESHOT, re-arming 865 + // while still holding `processing` loses a read that arrives in the 866 + // window between the re-arm and the release: the event loop sees the 867 + // connection as busy (acquireProcessing fails) and the one-shot arming 868 + // is consumed, so the read is dropped and the connection hangs / leaks 869 + // (CLOSE_WAIT). Releasing first means a read arriving after this 870 + // dispatches a fresh worker, and a read already buffered is re-reported 871 + // when rearmRead arms the readable fd. 872 + conn.releaseProcessing(); 864 873 self.loop.rearmRead(conn) catch |err| { 865 874 log.debug("({f}) failed to add read event monitor: {}", .{ ws_conn.address, err }); 866 875 ws_conn.close(.{ .code = 4998, .reason = "wsz" }) catch {};