Skip to content

Commit f4cdced

Browse files
server: fix read error on closed connection (Linux)
Signed-off-by: Francis Bouvier <[email protected]>
1 parent c74feb9 commit f4cdced

File tree

1 file changed

+52
-56
lines changed

1 file changed

+52
-56
lines changed

src/server.zig

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const builtin = @import("builtin");
2021

2122
const jsruntime = @import("jsruntime");
2223
const Completion = jsruntime.IO.Completion;
@@ -37,6 +38,7 @@ const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError;
3738
const TimeoutCheck = std.time.ns_per_ms * 100;
3839

3940
const log = std.log.scoped(.server);
41+
const IsLinux = builtin.target.os.tag == .linux;
4042

4143
// I/O Main
4244
// --------
@@ -55,6 +57,7 @@ pub const Ctx = struct {
5557
err: ?Error = null,
5658

5759
// I/O fields
60+
accept_completion: *Completion,
5861
conn_completion: *Completion,
5962
timeout_completion: *Completion,
6063
timeout: u64,
@@ -76,13 +79,14 @@ pub const Ctx = struct {
7679
completion: *Completion,
7780
result: AcceptError!std.posix.socket_t,
7881
) void {
79-
std.debug.assert(completion == self.conn_completion);
82+
std.debug.assert(completion == self.acceptCompletion());
8083

8184
self.conn_socket = result catch |err| {
8285
log.err("accept error: {any}", .{err});
8386
self.err = err;
8487
return;
8588
};
89+
log.info("client connected", .{});
8690

8791
// set connection timestamp and timeout
8892
self.last_active = std.time.Instant.now() catch |err| {
@@ -112,6 +116,11 @@ pub const Ctx = struct {
112116
std.debug.assert(completion == self.conn_completion);
113117

114118
const size = result catch |err| {
119+
if (err == error.FileDescriptorInvalid and self.isClosed()) {
120+
// connection has been closed, do nothing
121+
log.debug("recv on closed conn", .{});
122+
return;
123+
}
115124
log.err("read error: {any}", .{err});
116125
self.err = err;
117126
return;
@@ -188,21 +197,9 @@ pub const Ctx = struct {
188197
};
189198

190199
if (now.since(self.last_active.?) > self.timeout) {
191-
// closing
192-
log.debug("conn timeout, closing...", .{});
193-
194-
// NOTE: we should cancel the current read
195-
// but it seems that's just closing the connection is enough
196-
// (and cancel does not work on MacOS)
197-
198200
// close current connection
199-
self.loop.io.close(
200-
*Ctx,
201-
self,
202-
Ctx.closeCbk,
203-
self.timeout_completion,
204-
self.conn_socket,
205-
);
201+
log.debug("conn timeout, closing...", .{});
202+
self.close();
206203
return;
207204
}
208205

@@ -216,39 +213,6 @@ pub const Ctx = struct {
216213
);
217214
}
218215

219-
fn closeCbk(self: *Ctx, completion: *Completion, result: CloseError!void) void {
220-
_ = completion;
221-
// NOTE: completion can be either self.conn_completion or self.timeout_completion
222-
223-
_ = result catch |err| {
224-
log.err("close error: {any}", .{err});
225-
self.err = err;
226-
return;
227-
};
228-
229-
// conn is closed
230-
self.last_active = null;
231-
232-
// restart a new browser session in case of re-connect
233-
if (!self.sessionNew) {
234-
self.newSession() catch |err| {
235-
log.err("new session error: {any}", .{err});
236-
return;
237-
};
238-
}
239-
240-
log.info("accepting new conn...", .{});
241-
242-
// continue accepting incoming requests
243-
self.loop.io.accept(
244-
*Ctx,
245-
self,
246-
Ctx.acceptCbk,
247-
self.conn_completion,
248-
self.accept_socket,
249-
);
250-
}
251-
252216
// shortcuts
253217
// ---------
254218

@@ -267,6 +231,15 @@ pub const Ctx = struct {
267231
return self.browser.session.env;
268232
}
269233

234+
inline fn acceptCompletion(self: *Ctx) *Completion {
235+
// NOTE: the logical completion to use here is the accept_completion
236+
// as the pipe_connection can be used simulteanously by a recv I/O operation.
237+
// But on MacOS (kqueue) the recv I/O operation on a closed socket leads to a panic
238+
// so we use the pipe_connection to avoid this problem
239+
if (IsLinux) return self.accept_completion;
240+
return self.conn_completion;
241+
}
242+
270243
// actions
271244
// -------
272245

@@ -276,13 +249,7 @@ pub const Ctx = struct {
276249
if (std.mem.eql(u8, cmd, "close")) {
277250
// close connection
278251
log.info("close cmd, closing conn...", .{});
279-
self.loop.io.close(
280-
*Ctx,
281-
self,
282-
Ctx.closeCbk,
283-
self.conn_completion,
284-
self.conn_socket,
285-
);
252+
self.close();
286253
return error.Closed;
287254
}
288255

@@ -307,6 +274,33 @@ pub const Ctx = struct {
307274
}
308275
}
309276

277+
fn close(self: *Ctx) void {
278+
std.posix.close(self.conn_socket);
279+
280+
// conn is closed
281+
log.info("connection closed", .{});
282+
self.last_active = null;
283+
284+
// restart a new browser session in case of re-connect
285+
if (!self.sessionNew) {
286+
self.newSession() catch |err| {
287+
log.err("new session error: {any}", .{err});
288+
return;
289+
};
290+
}
291+
292+
log.info("accepting new conn...", .{});
293+
294+
// continue accepting incoming requests
295+
self.loop.io.accept(
296+
*Ctx,
297+
self,
298+
Ctx.acceptCbk,
299+
self.acceptCompletion(),
300+
self.accept_socket,
301+
);
302+
}
303+
310304
fn newSession(self: *Ctx) !void {
311305
try self.browser.newSession(self.alloc(), self.loop);
312306
try self.browser.session.initInspector(
@@ -430,6 +424,7 @@ pub fn listen(
430424
defer msg_buf.deinit(loop.alloc);
431425

432426
// create I/O completions
427+
var accept_completion: Completion = undefined;
433428
var conn_completion: Completion = undefined;
434429
var timeout_completion: Completion = undefined;
435430

@@ -443,6 +438,7 @@ pub fn listen(
443438
.msg_buf = &msg_buf,
444439
.accept_socket = server_socket,
445440
.timeout = timeout,
441+
.accept_completion = &accept_completion,
446442
.conn_completion = &conn_completion,
447443
.timeout_completion = &timeout_completion,
448444
};
@@ -454,7 +450,7 @@ pub fn listen(
454450

455451
// accepting connection asynchronously on internal server
456452
log.info("accepting new conn...", .{});
457-
loop.io.accept(*Ctx, &ctx, Ctx.acceptCbk, ctx.conn_completion, ctx.accept_socket);
453+
loop.io.accept(*Ctx, &ctx, Ctx.acceptCbk, ctx.acceptCompletion(), ctx.accept_socket);
458454

459455
// infinite loop on I/O events, either:
460456
// - cmd from incoming connection on server socket

0 commit comments

Comments
 (0)