Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

stack: the HTTP gateway closes idle keep-alive connections after 5 s, so a client whose event loop is blocked gets ECONNRESET (`fetch failed`) on its next request

Đang mở Phù hợp với người mới
#6,975 0 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@7ttp đang làm issue này rồi.

Từ ngày 3/10/2026.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
84/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
node.js, typescript
Lĩnh vực
api, cli

Hướng nghiên cứu

Bắt đầu với packages/stack/src/HttpProxy.ts, đặc biệt là createServer(...) và comment hiện có về timeout của upstream Agent. Chạy bản tái hiện keepalive-race.mjs được cung cấp trên native stack và xác nhận rằng việc event loop phía client bị đình trệ lâu hơn năm giây không còn reset request tiếp theo. Keep-alive timeout phía client phải lớn hơn năm giây, đồng thời giữ headersTimeout lớn hơn giá trị đó.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

🐛 Bug supabase/cli
Affected area

Local development

Supabase CLI version

2.119.0

Operating system

macOS 26.7 on Apple silicon, native runtime. First seen on a GitHub Actions ubuntu-latest runner (x64), native runtime.

Installation method

pnpm

Command
supabase start --runtime native   # [experimental] stack = true in config.toml
Actual output

The stack's HTTP gateway (the listener behind API_URL) advertises a five-second idle timeout on every response and closes an idle client connection after it:

$ curl -sI "$API_URL/rest/v1/" -H "apikey: $PUBLISHABLE_KEY"
HTTP/1.1 200 OK
server: postgrest/16.4
Connection: keep-alive
Keep-Alive: timeout=5

A Node client whose event loop is free is not affected: fetch's connection pool drops the idle socket in time, and sequential requests seconds apart all succeed. A client whose event loop is blocked for longer than five seconds between two requests on the same connection (a synchronous child process, a long GC pause) cannot do that; the gateway closes the socket first, the client writes its next request to it, and the request fails:

warm: 200
after 4 s with the event loop blocked: 200
warm: 200
after 6 s with the event loop blocked: fetch failed (ECONNRESET)
warm: 200
after 6 s with the event loop free: 200

Through supabase-js this surfaces as TypeError: fetch failed, with nothing about the cause. It first hit us in CI: an end-to-end seed called the Data API, ran two builds with execFileSync, and called the Data API again. It passed on a laptop, where the two builds took less than five seconds, and failed on an ubuntu-latest runner. The same seed had run unchanged against the Docker stack (Kong) for weeks.

Expected behavior

The gateway keeps an idle client connection long enough that a stall of a few seconds in a client does not become a connection reset, as the Docker stack's gateway did: an explicit keepAliveTimeout well above the runtime's five-second default, with headersTimeout kept above it.

Steps to reproduce
  1. supabase start --runtime native in a project with [experimental] stack = true.
  2. Put API_URL and PUBLISHABLE_KEY from supabase status --env in the environment.
  3. Save the script below as keepalive-race.mjs and run node keepalive-race.mjs (Node 24.21.0 here). The request after a six-second block fails with ECONNRESET; the same gap with the loop free succeeds.
import { execFileSync } from "node:child_process";
const url = `${process.env.API_URL}/rest/v1/`;
const headers = { apikey: process.env.PUBLISHABLE_KEY };
async function get(label) {
  try {
    const response = await fetch(url, { headers });
    await response.text();
    console.log(`${label}: ${response.status}`);
  } catch (error) {
    console.log(`${label}: ${error.message} (${error.cause?.code})`);
  }
}
for (const seconds of [4, 6]) {
  await get("warm");
  execFileSync("sleep", [String(seconds)]); // blocks the event loop, as any synchronous work does
  await get(`after ${seconds} s with the event loop blocked`);
}
await get("warm");
await new Promise((resolve) => setTimeout(resolve, 6000)); // the same gap with the loop free
await get("after 6 s with the event loop free");
Additional context
  • packages/stack/src/HttpProxy.ts (v2.119.0, unchanged on develop) creates the client-facing server with createServer(...) and sets no keepAliveTimeout, so the runtime's default of five seconds applies. The same file already guards the gateway's own upstream connections against this race: new Agent({ keepAlive: true, timeout: 4_000 }), with the comment "Idle sockets close before Node upstreams' default 5 s keep-alive timeout can race a reuse." The client-facing side has no equivalent.
  • #6922 covered the upstream half of the gateway's connection handling; this is the client-facing half.
  • We fixed our side by not blocking the event loop between requests, which is the right shape regardless, so this is a report rather than a blocker.
Ngôn ngữ chính
TypeScript
Star
2.4k
Fork
531
Merge trung bình
1 ngày 4 giờ
Pull request đã merge (30 ngày)
346

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của supabase/cli

Tất cả issue của supabase/cli

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.