fix(server): raise keepAliveTimeout above ingress-nginx's upstream idle timeout
Node's default keepAliveTimeout (5s) is shorter than ingress-nginx's default upstream keepalive idle timeout (60s). Under that mismatch, nginx can forward a request onto a pooled connection Node has *just* closed, which the browser sees as a 502 — and it never shows up in this process's own traces because Node never received the request. /api/rooms is polled every 5s from idle clients, landing almost exactly on that 5s boundary, which is why it was the one showing up for external users. Also add abort/slow-request logging (tagged with the active trace ID so it can be pivoted to from RUM) and a clientError handler, so a genuine backend-side failure is now visible instead of indistinguishable from the proxy-side race. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5bde1757d6
commit
664417c6da
1 file changed
+33
@@ -7,6 +7,11 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { WebSocketServer } = require('ws');
|
||||
const { trace } = require('@opentelemetry/api');
|
||||
|
||||
function traceId() {
|
||||
return trace.getActiveSpan()?.spanContext().traceId;
|
||||
}
|
||||
|
||||
const PORT = Number(process.env.PORT || 8080);
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
@@ -92,6 +97,20 @@ function serveStatic(req, res) {
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const start = process.hrtime.bigint();
|
||||
const { method, url } = req;
|
||||
res.on('close', () => {
|
||||
const ms = Number(process.hrtime.bigint() - start) / 1e6;
|
||||
// res.writableEnded false here means the client (or ingress) dropped the
|
||||
// connection before we finished writing — the smoking gun for a 502 that
|
||||
// originates on the proxy side rather than in this process.
|
||||
if (!res.writableEnded) {
|
||||
console.warn(`[http] ABORTED ${method} ${url} after ${ms.toFixed(1)}ms (status=${res.statusCode}) trace=${traceId()}`);
|
||||
} else if (ms > 1000) {
|
||||
console.warn(`[http] SLOW ${method} ${url} ${res.statusCode} ${ms.toFixed(1)}ms trace=${traceId()}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (req.url === '/healthz' || req.url === '/readyz' || req.url === '/health') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('ok');
|
||||
@@ -110,6 +129,20 @@ const server = http.createServer((req, res) => {
|
||||
serveStatic(req, res);
|
||||
});
|
||||
|
||||
// Node's default keepAliveTimeout (5s) is shorter than ingress-nginx's default
|
||||
// upstream keepalive idle timeout (60s). Under that mismatch, nginx can pick a
|
||||
// pooled connection Node has *just* closed and forward a request onto it,
|
||||
// which comes back to the browser as a 502 — invisible from inside this
|
||||
// process because Node never saw the request. headersTimeout must stay above
|
||||
// keepAliveTimeout (Node enforces this).
|
||||
server.keepAliveTimeout = 65_000;
|
||||
server.headersTimeout = 66_000;
|
||||
|
||||
server.on('clientError', (err, socket) => {
|
||||
console.warn(`[http] clientError: ${err.message} (code=${err.code})`);
|
||||
if (socket.writable) socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Signaling
|
||||
// client -> server: {type: offer|answer|candidate, to} {type: pos|state}
|
||||
|
||||
Reference in new issue
Block a user