Skip to content

fix(client): TTL'd connection pool + resource-leak hardening#158

Merged
Sootopolis merged 1 commit into
mainfrom
wip
Jul 9, 2026
Merged

fix(client): TTL'd connection pool + resource-leak hardening#158
Sootopolis merged 1 commit into
mainfrom
wip

Conversation

@Sootopolis

Copy link
Copy Markdown
Owner

What

Connection-pool and resource-lifecycle hardening, triggered by Netty
PrematureChannelClosureException warnings in the server logs and a follow-up
audit for the same bug class (silently-inherited library defaults / resources
never reclaimed on the long-lived server, whose window Neon scale-to-zero widens).

  • HttpClientLayer — replace zio-http's default Fixed(10) connection pool
    (no idle TTL) with Dynamic(0, 32, ttl = 30s). The Fixed pool kept handing
    out keep-alive connections that Chess.com's Cloudflare edge had already reaped
    server-side, so the next request failed mid-flight (the source of the log
    warnings). A TTL shorter than the edge idle timeout evicts stale connections
    before reuse; the connection-error retry stays the backstop.
  • JobRunner — fix an fd leak on interruption: FileSink.make opened a
    held-open writer whose only close path was wired into the forked child, but the
    interruptible gap before the fork (a DB read that can block on a Neon cold
    start) could leak it. Open sink + register promise + fork now sit inside one
    uninterruptibleMask.
  • CcasServer — set idleTimeout(60s) (default was None), so keep-alive
    clients that vanish without FIN/RST can't pin a Netty channel + fd forever.
    Chosen above JobLogStream.KeepAliveInterval (20s) so log-follow streams aren't
    reaped.
  • ChessComClient — stop leaking one scope finalizer + Fiber shell per
    rate-limit throttle-down; hold the single recovery fiber in a Ref and
    interrupt-and-replace, with one shutdown finalizer.

Fixes

No issue numbers — found via log review + audit this session.

Testing

  • sbt Test/compile clean.
  • Full sbt test green via the pre-push hook.
  • TestChessComClientThrottling (incl. "recovery fibers are interrupted when
    scope closes") and TestJobRunner exercised directly.

🤖 Generated with Claude Code

Investigation started from Netty PrematureChannelClosureException warnings
("channel gone inactive with 1 missing response(s)") in the server logs, then
swept for the same bug class (silently-inherited library defaults / unreclaimed
resources on the long-lived server, whose window Neon scale-to-zero widens).

- HttpClientLayer: zio-http's default connection pool is Fixed(10) with no idle
  TTL, so it keeps handing out keep-alive connections that Chess.com's Cloudflare
  edge already reaped server-side; the next request fails mid-flight. Switch to a
  Dynamic pool with a 30s TTL (< the edge idle timeout) so stale connections are
  evicted before reuse. The connection-error retry stays the backstop.

- JobRunner: FileSink.make acquires a held-open fd whose only close path is wired
  into the forked child's `ensuring`. An interrupt between the open and the fork
  (the DB read in between can block seconds on a Neon cold start) leaked the fd.
  Open the sink, register the promise, and fork all inside one uninterruptibleMask.

- CcasServer: the server inherited Server.Config.default's idleTimeout=None, so a
  keep-alive client that vanished without FIN/RST pinned its channel + fd forever.
  Set idleTimeout(60s), above JobLogStream.KeepAliveInterval so log-follow streams
  aren't reaped.

- ChessComClient: each rate-limit throttle-down registered a new finalizer on the
  layer-lifetime scope, accumulating one closure + Fiber shell per event for the
  whole process lifetime. Hold the single recovery fiber in a Ref and
  interrupt-and-replace, with one shutdown finalizer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Sootopolis Sootopolis merged commit 7e5cf0a into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant