@rvoh/psychic 3.4.1 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -138,11 +138,37 @@ export default class PsychicServer {
|
|
|
138
138
|
await this.stop();
|
|
139
139
|
process.exit();
|
|
140
140
|
}
|
|
141
|
-
async stop({ bypassClosingDbConnections = false } = {}) {
|
|
141
|
+
async stop({ bypassClosingDbConnections = false, gracefulShutdownTimeoutMillis = 10_000, } = {}) {
|
|
142
142
|
for (const hook of PsychicApp.getOrFail().specialHooks.serverShutdown) {
|
|
143
143
|
await hook(this);
|
|
144
144
|
}
|
|
145
|
-
this.httpServer
|
|
145
|
+
if (this.httpServer) {
|
|
146
|
+
const httpServer = this.httpServer;
|
|
147
|
+
await new Promise(resolve => {
|
|
148
|
+
// Bounded grace period: if still-active requests haven't finished by
|
|
149
|
+
// the timeout, force the remaining sockets so shutdown can never hang
|
|
150
|
+
// forever (the original bug). `closeIdle/AllConnections` are Node
|
|
151
|
+
// >= 18.2; on older runtimes the optional calls no-op and prior
|
|
152
|
+
// (potentially hanging) behavior is retained rather than throwing.
|
|
153
|
+
const forceTimer = setTimeout(() => httpServer.closeAllConnections?.(), gracefulShutdownTimeoutMillis);
|
|
154
|
+
forceTimer.unref?.();
|
|
155
|
+
// `close` stops accepting new connections and fires its callback only
|
|
156
|
+
// once every existing connection has ended.
|
|
157
|
+
httpServer.close(() => {
|
|
158
|
+
clearTimeout(forceTimer);
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
// Immediately drop *idle* keep-alive sockets. These (browsers, fetch
|
|
162
|
+
// agents, reverse proxies sitting between requests) are what kept
|
|
163
|
+
// `close()`'s callback from ever firing — and, transitively, a leased
|
|
164
|
+
// DB pool client from being released — causing shutdown to hang (a
|
|
165
|
+
// SIGTERM drain that never completes; a feature-spec `afterAll` that
|
|
166
|
+
// blocks for the full hook timeout). Dropping only *idle* sockets
|
|
167
|
+
// does NOT abort in-flight requests, so a normal graceful shutdown
|
|
168
|
+
// still lets active handlers finish within the grace period above.
|
|
169
|
+
httpServer.closeIdleConnections?.();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
146
172
|
if (!bypassClosingDbConnections) {
|
|
147
173
|
await closeAllDbConnections();
|
|
148
174
|
}
|
|
@@ -138,11 +138,37 @@ export default class PsychicServer {
|
|
|
138
138
|
await this.stop();
|
|
139
139
|
process.exit();
|
|
140
140
|
}
|
|
141
|
-
async stop({ bypassClosingDbConnections = false } = {}) {
|
|
141
|
+
async stop({ bypassClosingDbConnections = false, gracefulShutdownTimeoutMillis = 10_000, } = {}) {
|
|
142
142
|
for (const hook of PsychicApp.getOrFail().specialHooks.serverShutdown) {
|
|
143
143
|
await hook(this);
|
|
144
144
|
}
|
|
145
|
-
this.httpServer
|
|
145
|
+
if (this.httpServer) {
|
|
146
|
+
const httpServer = this.httpServer;
|
|
147
|
+
await new Promise(resolve => {
|
|
148
|
+
// Bounded grace period: if still-active requests haven't finished by
|
|
149
|
+
// the timeout, force the remaining sockets so shutdown can never hang
|
|
150
|
+
// forever (the original bug). `closeIdle/AllConnections` are Node
|
|
151
|
+
// >= 18.2; on older runtimes the optional calls no-op and prior
|
|
152
|
+
// (potentially hanging) behavior is retained rather than throwing.
|
|
153
|
+
const forceTimer = setTimeout(() => httpServer.closeAllConnections?.(), gracefulShutdownTimeoutMillis);
|
|
154
|
+
forceTimer.unref?.();
|
|
155
|
+
// `close` stops accepting new connections and fires its callback only
|
|
156
|
+
// once every existing connection has ended.
|
|
157
|
+
httpServer.close(() => {
|
|
158
|
+
clearTimeout(forceTimer);
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
// Immediately drop *idle* keep-alive sockets. These (browsers, fetch
|
|
162
|
+
// agents, reverse proxies sitting between requests) are what kept
|
|
163
|
+
// `close()`'s callback from ever firing — and, transitively, a leased
|
|
164
|
+
// DB pool client from being released — causing shutdown to hang (a
|
|
165
|
+
// SIGTERM drain that never completes; a feature-spec `afterAll` that
|
|
166
|
+
// blocks for the full hook timeout). Dropping only *idle* sockets
|
|
167
|
+
// does NOT abort in-flight requests, so a normal graceful shutdown
|
|
168
|
+
// still lets active handlers finish within the grace period above.
|
|
169
|
+
httpServer.closeIdleConnections?.();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
146
172
|
if (!bypassClosingDbConnections) {
|
|
147
173
|
await closeAllDbConnections();
|
|
148
174
|
}
|
|
@@ -17,8 +17,9 @@ export default class PsychicServer {
|
|
|
17
17
|
attach(id: string, obj: any): void;
|
|
18
18
|
$attached: Record<string, any>;
|
|
19
19
|
private shutdownAndExit;
|
|
20
|
-
stop({ bypassClosingDbConnections }?: {
|
|
20
|
+
stop({ bypassClosingDbConnections, gracefulShutdownTimeoutMillis, }?: {
|
|
21
21
|
bypassClosingDbConnections?: boolean;
|
|
22
|
+
gracefulShutdownTimeoutMillis?: number;
|
|
22
23
|
}): Promise<void>;
|
|
23
24
|
serveForRequestSpecs(block: () => void | Promise<void>): Promise<boolean>;
|
|
24
25
|
buildApp(): void;
|