Skip to content

fix(storage): harden S3 client against stale keep-alive socket hangs - #24

Merged
coopbri merged 1 commit into
masterfrom
fix/s3-keepalive-hang
Sep 1, 2026
Merged

fix(storage): harden S3 client against stale keep-alive socket hangs#24
coopbri merged 1 commit into
masterfrom
fix/s3-keepalive-hang

Conversation

@coopbri

@coopbri coopbri commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

S3StorageProvider (used by backfeed-api, runa-api, moment-api for uploads) built its client with the default @aws-sdk/client-s3 request handler, which pools keep-alive sockets with no aborting request timeout. Against Garage at s3.omni.dev (reached over a NAT/edge that silently idle-drops connections) the pool fills with zombie sockets and every reuse hangs ~30s then 503s. This is the failure that took down halo-api media serving on 2026-09-01.

Fix

The client is now built through a new createResilientS3Client (exported from ./storage and the package root for direct use):

  • keepAlive: false so a socket is never reused (a dropped connection can't become a zombie).
  • throwOnRequestTimeout: true with requestTimeout (a plain requestTimeout on @smithy/node-http-handler v4 only warns and keeps hanging on Bun) + connectionTimeout.

@smithy/node-http-handler added as an optional peer dependency (mirroring @aws-sdk/client-s3) plus a dev dep; both imported lazily to stay off the module load path for non-S3 consumers. Regression test + changeset included.

Consumer propagation

This is a git-ref-pinned library, so backfeed-api, runa-api, and moment-api only pick up the fix once they bump their @omnidotdev/providers ref. (backfeed-api's serve path was hardened directly in a separate PR; this covers its upload path.)

Verification

tsc --noEmit, bun run build, bun test src/storage/requestHandler.test.ts (1 pass), biome, knip: clean.

S3StorageProvider (used by backfeed-api, runa-api, moment-api for uploads)
built its client with the default @aws-sdk/client-s3 request handler, which
pools keep-alive sockets with no aborting request timeout. Against Garage at
s3.omni.dev (reached over a NAT/edge that silently idle-drops connections)
the pool fills with zombie sockets and every reuse hangs ~30s until the
upstream 503s. This is the failure that took down halo-api media on
2026-09-01.

Build the client through a new createResilientS3Client (exported for direct
use too): keepAlive:false so a socket is never reused, plus requestTimeout
with throwOnRequestTimeout (a plain requestTimeout on
@smithy/node-http-handler v4 only warns and keeps hanging on Bun) and a
connectionTimeout. Added @smithy/node-http-handler as an optional peer
(mirroring the @aws-sdk/client-s3 treatment) plus a dev dep, a regression
test, and a changeset. Consumers pick up the fix on their next ref bump.
@coopbri
coopbri merged commit e02f4dd into master Sep 1, 2026
2 of 3 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 1, 2026
@coopbri
coopbri deleted the fix/s3-keepalive-hang branch September 1, 2026 19:41
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant