╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │364 return self.cs_cipher.readPacket(self.allocator, self.reader); │ │365 } │ │366 }; │ │367 + │ │368 +// how long a peer probe waits for a packet that may already be on its way │ │369 +const probe_timeout: std.Io.Timeout = .{ .duration = .{ .raw = .fromMilliseconds(1), .clock = .awake } };│ │370 │ │371 /// session bridge handed to the consumer's handleSession callback. exposes a │ │372 /// pumped event API plus a byte-write API; the byte stream goes out as │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │402 │ │403 pub fn deinit(self: *SessionCtx) void { │ │404 self.incoming_buffer.deinit(self.conn.allocator); │ │405 + } │ │406 + │ │407 + /// whether the client is gone. a push spends its transaction writing │ │408 + /// rather than reading, so a peer that hung up has to be pumped for. one │ │409 + /// pending packet is processed, which is where a close is seen. │ │410 + pub fn peerGone(self: *SessionCtx) bool { │ │411 + if (self.closed or self.remote_closed) return true; │ │412 + self.conn.read_timeout = probe_timeout.toDeadline(self.conn.io); │ │413 + defer self.conn.read_timeout = .none; │ │414 + self.processOneBackgroundPacket() catch |err| return err != error.Timeout;│ │415 + return self.remote_closed; │ │416 } │ │417 │ │418 /// the std.Io adapters can only report ReadFailed or WriteFailed. recover │ ╰───────────────────────────────────────────────────────────────────────────────────────╯