In-Flight Requests — Complete Implementation Guide
In this tutorial, you will learn about In. We cover key concepts, practical examples, and best practices to help you master this topic.
In-flight requests are HTTP requests that are currently being processed when a shutdown signal arrives. Handling them correctly ensures users don't see errors and data is not corrupted.
What You'll Learn
By the end of this tutorial, you will know how to track active requests, set a maximum completion time, drain long-running operations, and reject new requests with proper HTTP status codes.
Why It Matters
Every deployment interrupts in-flight requests. If not handled, users see 502 Bad Gateway errors, partial responses, or connection resets. Proper in-flight request handling eliminates these errors.
Real-World Use
DodaTech's payment processing service has a hard 30-second request timeout. During shutdown, it completes all payments that started within the last 25 seconds and rejects new ones with 503.
In-Flight Requests Learning Path
flowchart LR
A[Draining Connections] --> B[In-Flight Requests]
B --> C[Request Tracking]
B --> D[Completion Deadline]
B --> E[Rejection]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Tracking Active Requests
Use a counter or set to track all active requests and wait for them to complete during shutdown.
const http = require("http");
class RequestTracker {
constructor() {
this.active = new Map();
this.counter = 0;
}
start(req) {
const id = ++this.counter;
this.active.set(id, {
method: req.method,
url: req.url,
startTime: Date.now()
});
return id;
}
end(id) {
this.active.delete(id);
}
get activeCount() {
return this.active.size;
}
async waitForCompletion(timeoutMs = 10000) {
if (this.activeCount === 0) return;
console.log(`Waiting for ${this.activeCount} in-flight requests...`);
const start = Date.now();
for (const [id, info] of this.active) {
const elapsed = Date.now() - info.startTime;
console.log(` Request ${id}: ${info.method} ${info.url} (${elapsed}ms elapsed)`);
}
return new Promise((resolve) => {
const check = () => {
if (this.activeCount === 0) {
console.log("All in-flight requests completed");
resolve();
} else if (Date.now() - start > timeoutMs) {
console.log(`Timeout: ${this.activeCount} requests still in-flight`);
resolve();
} else {
setTimeout(check, 200);
}
};
check();
});
}
}
const tracker = new RequestTracker();
const server = http.createServer((req, res) => {
const id = tracker.start(req);
setTimeout(() => {
res.end("Done");
tracker.end(id);
}, 3000);
});
server.listen(3000);
console.log("Server with request tracking started");
// Server with request tracking started
Rejecting New Requests During Shutdown
Once shutdown starts, reject all new requests with a 503 Service Unavailable status.
function createShutdownAwareServer() {
let shuttingDown = false;
const tracker = new RequestTracker();
const server = http.createServer((req, res) => {
if (shuttingDown) {
res.writeHead(503, {
"Content-Type": "text/plain",
"Retry-After": "5"
});
res.end("Server is shutting down, please retry");
return;
}
const id = tracker.start(req);
const timeout = setTimeout(() => {
res.writeHead(504);
res.end("Request timeout");
tracker.end(id);
}, 30000);
req.on("close", () => {
clearTimeout(timeout);
tracker.end(id);
});
simulateWork(req, res, id, tracker);
});
async function shutdown() {
shuttingDown = true;
console.log("Rejecting new requests");
await tracker.waitForCompletion(10000);
server.close(() => process.exit(0));
}
process.on("SIGTERM", shutdown);
return server;
}
function simulateWork(req, res, id, tracker) {
setTimeout(() => {
tracker.end(id);
res.end("Response");
}, 1000);
}
const server = createShutdownAwareServer();
server.listen(3000);
// Rejecting new requests
// Waiting for 3 in-flight requests...
// Request 1: GET /process (5000ms elapsed)
// All in-flight requests completed
Setting a Maximum Request Lifetime
Each request should have a maximum lifetime to prevent infinite processing during shutdown.
class RequestDeadline {
constructor(maxLifetimeMs = 30000) {
this.maxLifetimeMs = maxLifetimeMs;
}
wrap(handler) {
return (req, res) => {
const startTime = Date.now();
let completed = false;
const deadline = setInterval(() => {
const elapsed = Date.now() - startTime;
if (!completed && elapsed > this.maxLifetimeMs) {
console.log(`Request ${req.url} exceeded deadline`);
res.writeHead(503);
res.end("Server shutting down, request terminated");
completed = true;
}
}, 1000);
res.on("finish", () => {
completed = true;
clearInterval(deadline);
});
handler(req, res, startTime);
};
}
}
const deadline = new RequestDeadline(5000);
const server = http.createServer(deadline.wrap((req, res) => {
setTimeout(() => res.end("Done"), 10000);
}));
server.listen(3000);
console.log("Server with request deadlines started");
// Request /submit exceeded deadline
// (5 seconds later, request is terminated)
Grace Period Strategy
A common pattern is to set a grace period after shutdown starts, during which new requests are still accepted but with a reduced timeout.
class GracePeriodServer {
constructor(options = {}) {
this.gracePeriodMs = options.gracePeriodMs || 5000;
this.maxRequestTime = options.maxRequestTime || 30000;
this.shuttingDown = false;
this.shutdownStart = null;
}
middleware(req, res, next) {
if (this.shuttingDown) {
const elapsed = Date.now() - this.shutdownStart;
if (elapsed > this.gracePeriodMs) {
res.writeHead(503);
res.end("Shutdown in progress");
return;
}
res.setHeader("X-Shutdown-Warning", `${this.gracePeriodMs - elapsed}ms`);
}
next();
}
startShutdown() {
this.shuttingDown = true;
this.shutdownStart = Date.now();
console.log(`Grace period: ${this.gracePeriodMs}ms for in-flight requests`);
}
}
const grace = new GracePeriodServer({ gracePeriodMs: 5000 });
console.log("Grace period server configured");
// Grace period server configured
Common Mistakes
Not distinguishing between active and idle connections -- A keep-alive connection with no active request should not block shutdown. Track requests, not just connections.
Using Process.exit() while requests are in-flight -- process.exit() terminates immediately. Always wait for in-flight requests before exiting.
Not setting request timeouts -- Without timeouts, a slow request can delay shutdown indefinitely. Set both per-request and per-shutdown timeouts.
Rejecting requests with 500 instead of 503 -- 500 means server error. 503 means service unavailable with expected recovery. Use 503 for shutdown rejection.
Not including Retry-After header -- The Retry-After header tells clients when to retry. Without it, clients may retry immediately and hit the same shutting-down instance.
Practice Questions
What HTTP status code should you use when rejecting requests during shutdown? 503 Service Unavailable with a Retry-After header indicating when to retry.
How do you track in-flight requests without a global variable? Use a closure or a class instance that tracks requests via a Set or Map, with each request stored by a unique ID.
What is the grace period pattern? A short window after shutdown starts where new requests are still accepted but with a reduced timeout, allowing a smooth transition.
Challenge: Implement a server that assigns each in-flight request a deadline based on its start time and the current shutdown progress.
class DeadlineAwareServer {
constructor(maxRequestMs = 30000) {
this.maxRequestMs = maxRequestMs;
this.deadlines = new Map();
}
async handle(req, res) {
const deadline = Date.now() + this.maxRequestMs;
this.deadlines.set(req, deadline);
const remaining = deadline - Date.now();
const timer = setTimeout(() => {
res.writeHead(503);
res.end("Deadline exceeded");
this.deadlines.delete(req);
}, remaining);
req.on("close", () => {
clearTimeout(timer);
this.deadlines.delete(req);
});
await this.processRequest(req, res);
}
async processRequest(req, res) {
await new Promise(r => setTimeout(r, 1000));
if (!res.headersSent) {
res.end("Completed within deadline");
}
}
}
FAQ
Mini Project
Build a server that tracks in-flight requests, implements a grace period for new requests, sets per-request deadlines, and logs every request outcome (completed, rejected, or timed out).
const http = require("http");
function buildProductionShutdownServer() {
const state = {
active: new Map(),
shuttingDown: false,
stats: { completed: 0, rejected: 0, timedout: 0 }
};
const server = http.createServer((req, res) => {
if (state.shuttingDown) {
state.stats.rejected++;
res.writeHead(503, { "Retry-After": "10" });
res.end("Shutting down");
return;
}
const id = Date.now() + Math.random();
state.active.set(id, { url: req.url, start: Date.now() });
res.on("finish", () => {
state.active.delete(id);
state.stats.completed++;
});
setTimeout(() => {
if (state.active.has(id)) {
state.active.delete(id);
state.stats.timedout++;
res.end("Response");
}
}, 10000);
setTimeout(() => res.end("ok"), 500);
});
async function shutdown() {
state.shuttingDown = true;
console.log("Shutdown initiated");
await new Promise(r => setTimeout(r, 5000));
server.close(() => {
console.log("Stats:", state.stats);
process.exit(0);
});
}
process.on("SIGTERM", shutdown);
return server;
}
buildProductionShutdownServer().listen(3000);
What's Next
Now that you understand in-flight requests, learn how to close database connection pools during shutdown. Then explore closing message queue connections.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro