PING a quiet IRC listener client before closing it (closes #155)
check / check (push) Canceled after 0s

serve closed a connection after five minutes with nothing read and never
sent a PING, so a quiet registered client lost its session. It now sends
PING :<server> after pingInterval with nothing read, and closes the
connection when nothing is read within pongDeadline after that. Every
line read starts both waits again. readTimeout is removed.

Unregistered connections are sent the PING too, so a PONG before
registration is now ignored instead of answered with ERR_NOTREGISTERED.

Model: opus-5-5
This commit is contained in:
clawbot
2026-10-20 16:57:36 +00:00
committed by sneak
parent 5100b9a665
commit ce24b177d6
5 changed files with 148 additions and 7 deletions
+21 -4
View File
@@ -20,7 +20,6 @@ import (
const (
maxLineLen = 512
readTimeout = 5 * time.Minute
writeTimeout = 30 * time.Second
killWriteWindow = 2 * time.Second
dnsTimeout = 3 * time.Second
@@ -214,17 +213,31 @@ func resolveHost(ctx context.Context, addr string) string {
// serve is the main loop for a single IRC client connection.
// Cancelling ctx when it returns stops the relay goroutine.
func (c *Conn) serve(ctx context.Context) {
// When nothing has been read for interval the client is sent
// a PING, and when nothing is read within deadline after that
// the connection is closed.
func (c *Conn) serve(
ctx context.Context,
interval, deadline time.Duration,
) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer c.cleanup(ctx)
ping := time.AfterFunc(interval, func() {
c.send("PING :" + c.serverSfx)
})
defer ping.Stop()
scanner := bufio.NewScanner(c.conn)
scanner.Buffer(make([]byte, maxLineLen), maxLineLen)
for {
// Every line read starts both waits again.
ping.Reset(interval)
_ = c.conn.SetReadDeadline(
time.Now().Add(readTimeout),
time.Now().Add(interval + deadline),
)
if !scanner.Scan() {
@@ -366,7 +379,7 @@ func (c *Conn) handleMessage(
msg *Message,
) {
// Before registration, only NICK, USER, PASS, PING,
// QUIT, and CAP are accepted.
// PONG, QUIT, and CAP are accepted.
if !c.registered {
c.handlePreRegistration(ctx, msg)
@@ -452,6 +465,10 @@ func (c *Conn) handlePreRegistration(
case irc.CmdPing:
c.handlePing(msg)
return
case irc.CmdPong:
// The answer to serve's PING, which unregistered
// clients are sent too.
return
case irc.CmdQuit:
c.handleQuit(msg)