Keep active IRC listener users from expiring (closes #121)
check / check (push) Waiting to run
check / check (push) Waiting to run
Every command a registered IRC listener client sends, PING and PONG included, now sets last_seen on its session and its client, so the idle cleanup no longer removes an IRC user who is still active; one who sends nothing for SESSION_IDLE_TIMEOUT is still removed. The two UPDATEs that GetSessionByToken ran for every authenticated HTTP API request move into UpdateLastSeen, which both transports now call. The IRC listener refreshes on every command, with no interval between writes: that is what every HTTP API request already costs. README.md describes last_seen to match. Model: opus-5-5 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
This commit was merged in pull request #126.
This commit is contained in:
+24
-3
@@ -204,21 +204,42 @@ func (database *Database) GetSessionByToken(
|
||||
)
|
||||
}
|
||||
|
||||
_ = database.UpdateLastSeen(ctx, sessionID, clientID)
|
||||
|
||||
return sessionID, clientID, nick, nil
|
||||
}
|
||||
|
||||
// UpdateLastSeen sets last_seen to now on a session and on
|
||||
// one of its clients, so that the idle cleanup keeps them.
|
||||
func (database *Database) UpdateLastSeen(
|
||||
ctx context.Context,
|
||||
sessionID, clientID int64,
|
||||
) error {
|
||||
now := time.Now()
|
||||
|
||||
_, _ = database.conn.ExecContext(
|
||||
_, err := database.conn.ExecContext(
|
||||
ctx,
|
||||
"UPDATE sessions SET last_seen = ? WHERE id = ?",
|
||||
now, sessionID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"update session last_seen: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
_, _ = database.conn.ExecContext(
|
||||
_, err = database.conn.ExecContext(
|
||||
ctx,
|
||||
"UPDATE clients SET last_seen = ? WHERE id = ?",
|
||||
now, clientID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"update client last_seen: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
return sessionID, clientID, nick, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSessionByNick returns session id for a given nick.
|
||||
|
||||
@@ -368,6 +368,17 @@ func (c *Conn) handleMessage(
|
||||
return
|
||||
}
|
||||
|
||||
// Every command is activity, so the idle cleanup must not
|
||||
// remove this user.
|
||||
err := c.database.UpdateLastSeen(
|
||||
ctx, c.sessionID, c.clientID,
|
||||
)
|
||||
if err != nil {
|
||||
c.log.Error(
|
||||
"failed to update last_seen", "error", err,
|
||||
)
|
||||
}
|
||||
|
||||
handler, ok := c.commands[msg.Command]
|
||||
if !ok {
|
||||
c.sendNumeric(
|
||||
|
||||
@@ -369,6 +369,39 @@ func TestPingPong(t *testing.T) {
|
||||
assertContains(t, lines, "PONG", "PONG response")
|
||||
}
|
||||
|
||||
// TestIdleCleanupKeepsActiveUser checks that a user who
|
||||
// sends commands is kept by the idle cleanup, and a user who
|
||||
// sends nothing is not.
|
||||
func TestIdleCleanupKeepsActiveUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const idleTimeout = 500 * time.Millisecond
|
||||
|
||||
env := newTestEnv(t)
|
||||
|
||||
active := env.dial(t)
|
||||
active.register("active")
|
||||
|
||||
idle := env.dial(t)
|
||||
idle.register("idle")
|
||||
|
||||
time.Sleep(idleTimeout)
|
||||
active.sendAndExpect("PING :still here", "PONG")
|
||||
|
||||
// The idle cleanup in internal/handlers removes exactly
|
||||
// the users this returns.
|
||||
stale, err := env.database.GetStaleOrphanSessions(
|
||||
t.Context(), time.Now().Add(-idleTimeout),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("get stale sessions: %v", err)
|
||||
}
|
||||
|
||||
if len(stale) != 1 || stale[0].Nick != "idle" {
|
||||
t.Errorf("cleanup removes %v, want only idle", stale)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinChannel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user