velocious 1.0.682 → 1.0.684
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.
- package/README.md +1 -0
- package/build/background-jobs/pooled-runner-child.js +102 -0
- package/build/background-jobs/types.js +21 -0
- package/build/background-jobs/worker.js +111 -1
- package/build/http-server/client/index.js +15 -0
- package/build/http-server/client/request.js +44 -0
- package/build/http-server/client/websocket-request.js +45 -0
- package/build/http-server/worker-handler/in-process.js +7 -1
- package/build/http-server/worker-handler/worker-thread.js +5 -0
- package/build/src/background-jobs/pooled-runner-child.js +95 -1
- package/build/src/background-jobs/types.d.ts +71 -0
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +22 -1
- package/build/src/background-jobs/worker.d.ts +40 -1
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +109 -2
- package/build/src/http-server/client/index.d.ts +9 -0
- package/build/src/http-server/client/index.d.ts.map +1 -1
- package/build/src/http-server/client/index.js +16 -1
- package/build/src/http-server/client/request.d.ts +27 -0
- package/build/src/http-server/client/request.d.ts.map +1 -1
- package/build/src/http-server/client/request.js +42 -1
- package/build/src/http-server/client/websocket-request.d.ts +28 -0
- package/build/src/http-server/client/websocket-request.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-request.js +43 -1
- package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/in-process.js +8 -2
- package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/worker-thread.js +6 -1
- package/package.json +1 -1
- package/src/background-jobs/pooled-runner-child.js +102 -0
- package/src/background-jobs/types.js +21 -0
- package/src/background-jobs/worker.js +111 -1
- package/src/http-server/client/index.js +15 -0
- package/src/http-server/client/request.js +44 -0
- package/src/http-server/client/websocket-request.js +45 -0
- package/src/http-server/worker-handler/in-process.js +7 -1
- package/src/http-server/worker-handler/worker-thread.js +5 -0
|
@@ -73,6 +73,27 @@
|
|
|
73
73
|
* @property {number | null} shutdownRequestedAtMs - Parent request timestamp when supplied over IPC.
|
|
74
74
|
* @property {import("node:child_process").ChildProcess["signalCode"]} signal - Requested or observed signal when available.
|
|
75
75
|
*/
|
|
76
|
+
/**
|
|
77
|
+
* Bounded pooled-child memory observation sent over the child IPC channel.
|
|
78
|
+
* The pooled child's stdio is ignored by the worker fork, so this observation
|
|
79
|
+
* (sent periodically while jobs run, plus on demand) is how a memory problem
|
|
80
|
+
* in a running child names itself. `heapStatistics` and `memoryUsage`
|
|
81
|
+
* distinguish V8-heap growth from external/array-buffer (native resource)
|
|
82
|
+
* growth; `activeJobIds` ties the sample to the work that was in flight.
|
|
83
|
+
* @typedef {object} PooledChildMemoryObservation
|
|
84
|
+
* @property {string[]} activeJobIds - In-flight job ids, bounded.
|
|
85
|
+
* @property {number} activeJobIdsTruncatedCount - In-flight job ids omitted by the bound.
|
|
86
|
+
* @property {string} childInstanceId - Stable identity reported by the child.
|
|
87
|
+
* @property {number} childPid - Child process id.
|
|
88
|
+
* @property {number} childUptimeMs - Child process uptime in ms.
|
|
89
|
+
* @property {ReturnType<typeof import("node:v8").getHeapStatistics>} heapStatistics - V8 heap-stat breakdown at the sample.
|
|
90
|
+
* @property {number} jobCount - In-flight job count.
|
|
91
|
+
* @property {ReturnType<typeof import("node:process").memoryUsage>} memoryUsage - Process memory breakdown at the sample.
|
|
92
|
+
* @property {number} observedAtMs - Epoch ms the child sampled.
|
|
93
|
+
* @property {number} rssBytes - Resident set size in bytes at the sample.
|
|
94
|
+
* @property {"pooled-child-memory"} type - Discriminator.
|
|
95
|
+
* @property {number} uptimeMs - Process uptime in ms.
|
|
96
|
+
*/
|
|
76
97
|
/**
|
|
77
98
|
* @typedef {object} LocalBackgroundJobsClock
|
|
78
99
|
* @property {() => number} now - Current epoch milliseconds.
|
|
@@ -41,6 +41,7 @@ import { POOLED_RUNNER_INFLIGHT_JOB_ID_LIMIT, boundedPooledRunnerInflightJobIds,
|
|
|
41
41
|
* @property {boolean} retiring - Whether this child is draining before retirement.
|
|
42
42
|
* @property {boolean} [started] - Whether the child completed its startup handshake.
|
|
43
43
|
* @property {boolean} [settling] - Whether failure handling already owns this child.
|
|
44
|
+
* @property {import("./types.js").PooledChildMemoryObservation} [lastMemoryObservation] - Latest memory observation received from this child.
|
|
44
45
|
* @property {number} [ipcDisconnectedAtMs] - Parent observation of IPC disconnect.
|
|
45
46
|
* @property {import("./types.js").PooledChildShutdownObservation} [shutdownObservation] - Child observation sent before teardown.
|
|
46
47
|
* @property {import("./types.js").PooledChildShutdownReason} [shutdownReason] - Exact parent-requested shutdown reason.
|
|
@@ -132,6 +133,38 @@ function isChildShutdownObservationMessage(message) {
|
|
|
132
133
|
&& isPooledChildShutdownSignal(record.signal)
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Checks whether an IPC value is a pooled child's bounded memory observation.
|
|
138
|
+
* The pooled child's stdio is ignored by the worker fork, so this observation
|
|
139
|
+
* (periodic while jobs run, plus on demand) is how a memory problem in a
|
|
140
|
+
* running child names itself. The worker logs it (its stderr reaches the prod
|
|
141
|
+
* log) and forwards it to the optional `onPooledRunnerMemoryObservation` hook.
|
|
142
|
+
* @param {ReturnType<typeof JSON.parse>} message - IPC message.
|
|
143
|
+
* @returns {message is import("./types.js").PooledChildMemoryObservation} - Whether this is a valid memory observation.
|
|
144
|
+
*/
|
|
145
|
+
function isPooledChildMemoryObservationMessage(message) {
|
|
146
|
+
if (!message || typeof message !== "object") return false
|
|
147
|
+
const record = /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (message)
|
|
148
|
+
const heapStatistics = /** @type {Record<string, ReturnType<typeof JSON.parse>> | undefined} */ (record.heapStatistics)
|
|
149
|
+
const memoryUsage = /** @type {Record<string, ReturnType<typeof JSON.parse>> | undefined} */ (record.memoryUsage)
|
|
150
|
+
|
|
151
|
+
return record.type === "pooled-child-memory"
|
|
152
|
+
&& typeof record.childInstanceId === "string"
|
|
153
|
+
&& Number.isInteger(record.childPid)
|
|
154
|
+
&& typeof record.rssBytes === "number"
|
|
155
|
+
&& Number.isFinite(record.rssBytes)
|
|
156
|
+
&& Number.isInteger(record.jobCount)
|
|
157
|
+
&& record.jobCount >= 0
|
|
158
|
+
&& Array.isArray(record.activeJobIds)
|
|
159
|
+
&& record.activeJobIds.every((jobId) => typeof jobId === "string")
|
|
160
|
+
&& Number.isInteger(record.activeJobIdsTruncatedCount)
|
|
161
|
+
&& record.activeJobIdsTruncatedCount >= 0
|
|
162
|
+
&& typeof record.observedAtMs === "number"
|
|
163
|
+
&& Number.isFinite(record.observedAtMs)
|
|
164
|
+
&& heapStatistics !== undefined && typeof heapStatistics === "object"
|
|
165
|
+
&& memoryUsage !== undefined && typeof memoryUsage === "object"
|
|
166
|
+
}
|
|
167
|
+
|
|
135
168
|
/**
|
|
136
169
|
* Normalizes a candidate pooled-runner resource limit.
|
|
137
170
|
* @param {number | undefined} value - Candidate positive number.
|
|
@@ -166,8 +199,9 @@ export default class BackgroundJobsWorker {
|
|
|
166
199
|
* @param {() => void | Promise<void>} [args.onStopped] - Lifecycle hook invoked after the worker finishes stopping.
|
|
167
200
|
* @param {() => void} [args.onGenerationAccepted] - Explicit generation-acceptance observation hook.
|
|
168
201
|
* @param {() => void} [args.onRetireMessage] - Explicit retire-message observation hook.
|
|
202
|
+
* @param {(observation: import("./types.js").PooledChildMemoryObservation) => void | Promise<void>} [args.onPooledRunnerMemoryObservation] - Explicit pooled-child memory observation hook. Every validated observation (periodic while a child has in-flight jobs, plus on demand) is forwarded here, in addition to the worker's compact stderr log line, so an application can route memory diagnostics (e.g. to a bug reporter) without parsing logs.
|
|
169
203
|
*/
|
|
170
|
-
constructor({configuration, host, port, generationId, workerInstanceId, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, generationHandshakeTimeoutMs = DEFAULT_GENERATION_HANDSHAKE_TIMEOUT_MS, reconnectDelayMs = 1000, jobTimeoutMs, closeDatabaseConnectionsOnStop = true, onStopped, onGenerationAccepted, onRetireMessage} = {}) {
|
|
204
|
+
constructor({configuration, host, port, generationId, workerInstanceId, maxConcurrentForkedJobs, maxConcurrentInlineJobs, pooledRunnerCount, pooledRunnerConcurrency, pooledRunnerMaxJobs, pooledRunnerMaxRssBytes, pooledRunnerMaxLifetimeMs, forkedChildSigkillGraceMs, heartbeatIntervalMs, generationHandshakeTimeoutMs = DEFAULT_GENERATION_HANDSHAKE_TIMEOUT_MS, reconnectDelayMs = 1000, jobTimeoutMs, closeDatabaseConnectionsOnStop = true, onStopped, onGenerationAccepted, onRetireMessage, onPooledRunnerMemoryObservation} = {}) {
|
|
171
205
|
/**
|
|
172
206
|
* Narrows the runtime value to the documented type.
|
|
173
207
|
* @type {Promise<import("../configuration.js").default>} */
|
|
@@ -186,6 +220,7 @@ export default class BackgroundJobsWorker {
|
|
|
186
220
|
this.onStopped = onStopped
|
|
187
221
|
this.onGenerationAccepted = onGenerationAccepted
|
|
188
222
|
this.onRetireMessage = onRetireMessage
|
|
223
|
+
this.onPooledRunnerMemoryObservation = onPooledRunnerMemoryObservation
|
|
189
224
|
/**
|
|
190
225
|
* Constructor override for the inline-job concurrency cap. When unset
|
|
191
226
|
* the cap is read from `configuration.getBackgroundJobsConfig()` in
|
|
@@ -1393,6 +1428,10 @@ export default class BackgroundJobsWorker {
|
|
|
1393
1428
|
this._reportChildAccepted(message)
|
|
1394
1429
|
return
|
|
1395
1430
|
}
|
|
1431
|
+
if (isPooledChildMemoryObservationMessage(message)) {
|
|
1432
|
+
this._handlePooledChildMemoryObservation({child, message})
|
|
1433
|
+
return
|
|
1434
|
+
}
|
|
1396
1435
|
if (record.type !== "job-outcome" || !state || state.settling || typeof record.jobId !== "string") return
|
|
1397
1436
|
state.started = true
|
|
1398
1437
|
const entry = state.inflight.get(record.jobId)
|
|
@@ -1456,6 +1495,77 @@ export default class BackgroundJobsWorker {
|
|
|
1456
1495
|
})
|
|
1457
1496
|
}
|
|
1458
1497
|
|
|
1498
|
+
/**
|
|
1499
|
+
* Handles a pooled child's bounded memory observation. The pooled child's
|
|
1500
|
+
* stdio is ignored by the worker fork, so this IPC observation is how a
|
|
1501
|
+
* memory problem in a running child names itself. The worker (a) records the
|
|
1502
|
+
* latest observation on the child's state for later correlation, (b) logs one
|
|
1503
|
+
* compact line to its own stderr (which reaches the prod log, unlike the
|
|
1504
|
+
* child's ignored stdio), and (c) forwards the full observation to the
|
|
1505
|
+
* optional `onPooledRunnerMemoryObservation` hook so an application can route
|
|
1506
|
+
* it (e.g. to a bug reporter) without parsing logs. The heap-stat breakdown
|
|
1507
|
+
* distinguishes V8-heap growth from external/array-buffer (native) growth. A
|
|
1508
|
+
* hook failure is swallowed — diagnostics must never take down the worker or
|
|
1509
|
+
* fail the jobs running on that child.
|
|
1510
|
+
* @param {object} args - Message details.
|
|
1511
|
+
* @param {import("node:child_process").ChildProcess} args.child - Pooled child.
|
|
1512
|
+
* @param {import("./types.js").PooledChildMemoryObservation} args.message - Validated memory observation.
|
|
1513
|
+
* @returns {void}
|
|
1514
|
+
*/
|
|
1515
|
+
_handlePooledChildMemoryObservation({child, message}) {
|
|
1516
|
+
const state = this.pooledChildStates.get(child)
|
|
1517
|
+
if (state) state.lastMemoryObservation = message
|
|
1518
|
+
|
|
1519
|
+
const heap = message.heapStatistics
|
|
1520
|
+
console.error(
|
|
1521
|
+
JSON.stringify({
|
|
1522
|
+
event: "pooled-child-memory",
|
|
1523
|
+
childInstanceId: state?.childInstanceId ?? message.childInstanceId,
|
|
1524
|
+
childPid: message.childPid,
|
|
1525
|
+
childUptimeS: Math.round(message.childUptimeMs / 1000),
|
|
1526
|
+
rssMb: Math.round(message.rssBytes / (1024 * 1024)),
|
|
1527
|
+
heapUsedMb: Math.round(heap.used_heap_size / (1024 * 1024)),
|
|
1528
|
+
heapTotalMb: Math.round(heap.total_heap_size / (1024 * 1024)),
|
|
1529
|
+
heapLimitMb: Math.round(heap.heap_size_limit / (1024 * 1024)),
|
|
1530
|
+
externalMb: Math.round(message.memoryUsage.external / (1024 * 1024)),
|
|
1531
|
+
arrayBuffersMb: Math.round(message.memoryUsage.arrayBuffers / (1024 * 1024)),
|
|
1532
|
+
jobs: message.jobCount,
|
|
1533
|
+
activeJobIds: message.activeJobIds
|
|
1534
|
+
})
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
if (this.onPooledRunnerMemoryObservation) {
|
|
1538
|
+
try {
|
|
1539
|
+
const result = this.onPooledRunnerMemoryObservation(message)
|
|
1540
|
+
if (result && typeof result.catch === "function") result.catch((error) => {
|
|
1541
|
+
console.error("Pooled runner memory observation hook failed:", error)
|
|
1542
|
+
})
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
console.error("Pooled runner memory observation hook failed:", error)
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* Requests an immediate memory observation from one pooled child. The child
|
|
1551
|
+
* replies over IPC with its current snapshot (the same shape as the periodic
|
|
1552
|
+
* sampler), which the worker records, logs, and forwards to the
|
|
1553
|
+
* `onPooledRunnerMemoryObservation` hook. Use this to pull a snapshot on
|
|
1554
|
+
* suspicion (e.g. after an OOM report) without waiting for the next periodic
|
|
1555
|
+
* sample. A no-op when the child is gone or its IPC channel is closed.
|
|
1556
|
+
* @param {import("node:child_process").ChildProcess} child - Pooled child to sample.
|
|
1557
|
+
* @returns {void}
|
|
1558
|
+
*/
|
|
1559
|
+
requestPooledChildMemoryObservation(child) {
|
|
1560
|
+
if (!child.connected) return
|
|
1561
|
+
|
|
1562
|
+
try {
|
|
1563
|
+
child.send({type: "memory-observation-request"})
|
|
1564
|
+
} catch {
|
|
1565
|
+
// The IPC channel is already gone; the disconnect/exit handler owns teardown.
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1459
1569
|
/**
|
|
1460
1570
|
* Marks a pooled child for retirement and — when the pool is below its hard
|
|
1461
1571
|
* cap — eagerly spawns a single replacement (1-for-1) so its capacity is
|
|
@@ -803,6 +803,21 @@ export default class VeoliciousHttpServerClient {
|
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Marks every in-flight request as client-disconnected and runs its
|
|
808
|
+
* disconnect callbacks. The worker handler calls this when the underlying
|
|
809
|
+
* socket tears down, so a handler whose response is still buffered (e.g.
|
|
810
|
+
* waiting in an admission queue) can observe the client leaving without
|
|
811
|
+
* waiting for the response to be sent.
|
|
812
|
+
* @returns {void}
|
|
813
|
+
*/
|
|
814
|
+
notifyClientDisconnect() {
|
|
815
|
+
for (const requestRunner of this.requestRunners) {
|
|
816
|
+
if (requestRunner.getState() !== "running") continue
|
|
817
|
+
requestRunner.getRequest().markClientDisconnected()
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
806
821
|
/**
|
|
807
822
|
* Runs the close callbacks of a finished or aborted stream exactly once.
|
|
808
823
|
* @param {import("./response.js").default} response - Finished stream.
|
|
@@ -19,6 +19,17 @@ export default class VelociousHttpServerClientRequest {
|
|
|
19
19
|
this.client = client
|
|
20
20
|
this.configuration = configuration
|
|
21
21
|
this.requestParser = new RequestParser({configuration})
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether the owning client connection was torn down while this request
|
|
25
|
+
* was still running. Set once by the client on socket teardown and read
|
|
26
|
+
* by handlers that need to settle in-flight work (e.g. admission queue
|
|
27
|
+
* positions) without waiting for the response to be sent.
|
|
28
|
+
* @type {boolean} */
|
|
29
|
+
this.clientDisconnected = false
|
|
30
|
+
|
|
31
|
+
/** @type {Set<() => void>} */
|
|
32
|
+
this.clientDisconnectCallbacks = new Set()
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
baseURL() { return `${this.protocol()}://${this.hostWithPort()}` }
|
|
@@ -114,6 +125,39 @@ export default class VelociousHttpServerClientRequest {
|
|
|
114
125
|
rawBody() { return this.getRequestBuffer().getRawBody() }
|
|
115
126
|
socketRemoteAddress() { return this.client?.remoteAddress }
|
|
116
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Marks this request as client-disconnected and runs every registered
|
|
130
|
+
* disconnect callback exactly once. The owning client invokes it when the
|
|
131
|
+
* socket tears down; handlers that register afterwards learn of the
|
|
132
|
+
* disconnect through the `clientDisconnected` field instead.
|
|
133
|
+
* @returns {void}
|
|
134
|
+
*/
|
|
135
|
+
markClientDisconnected() {
|
|
136
|
+
if (this.clientDisconnected) return
|
|
137
|
+
this.clientDisconnected = true
|
|
138
|
+
for (const callback of this.clientDisconnectCallbacks) {
|
|
139
|
+
callback()
|
|
140
|
+
}
|
|
141
|
+
this.clientDisconnectCallbacks.clear()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Registers a callback that fires once when the client connection tears
|
|
146
|
+
* down while this request is still running. Buffered requests cannot rely
|
|
147
|
+
* on the streaming response's `onStreamClose` for that: no stream has
|
|
148
|
+
* been opened yet, so a queued request's queue position is otherwise
|
|
149
|
+
* stranded until its admission deadline.
|
|
150
|
+
* @param {() => void} callback - Disconnect callback.
|
|
151
|
+
* @returns {void}
|
|
152
|
+
*/
|
|
153
|
+
onClientDisconnect(callback) {
|
|
154
|
+
if (this.clientDisconnected) {
|
|
155
|
+
callback()
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
this.clientDisconnectCallbacks.add(callback)
|
|
159
|
+
}
|
|
160
|
+
|
|
117
161
|
getRequestBuffer() { return this.getRequestParser().getRequestBuffer() }
|
|
118
162
|
getRequestParser() { return this.requestParser }
|
|
119
163
|
}
|
|
@@ -50,6 +50,51 @@ export default class VelociousHttpServerClientWebsocketRequest {
|
|
|
50
50
|
const queryParams = this._parseQueryParams()
|
|
51
51
|
|
|
52
52
|
this.paramsObject = {...queryParams, ...this.paramsObject}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether the owning client connection was torn down while this request
|
|
56
|
+
* was still running. Websocket requests are session-owned and never
|
|
57
|
+
* enter the client's in-flight request list, so this stays false for
|
|
58
|
+
* them; the field exists so the request union shares one surface.
|
|
59
|
+
* @type {boolean} */
|
|
60
|
+
this.clientDisconnected = false
|
|
61
|
+
|
|
62
|
+
/** @type {Set<() => void>} */
|
|
63
|
+
this.clientDisconnectCallbacks = new Set()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Marks this request as client-disconnected and runs every registered
|
|
68
|
+
* disconnect callback exactly once. The owning client invokes it when the
|
|
69
|
+
* socket tears down; handlers that register afterwards learn of the
|
|
70
|
+
* disconnect through the `clientDisconnected` field instead. Websocket
|
|
71
|
+
* requests are session-owned and never enter the client's in-flight
|
|
72
|
+
* request list, so this is inert for them; the shared surface keeps the
|
|
73
|
+
* request union uniform.
|
|
74
|
+
* @returns {void}
|
|
75
|
+
*/
|
|
76
|
+
markClientDisconnected() {
|
|
77
|
+
if (this.clientDisconnected) return
|
|
78
|
+
this.clientDisconnected = true
|
|
79
|
+
for (const callback of this.clientDisconnectCallbacks) {
|
|
80
|
+
callback()
|
|
81
|
+
}
|
|
82
|
+
this.clientDisconnectCallbacks.clear()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Registers a callback that fires once when the client connection tears
|
|
87
|
+
* down while this request is still running. See the HTTP request's
|
|
88
|
+
* {@linkcode markClientDisconnected} for the shared-surface note.
|
|
89
|
+
* @param {() => void} callback - Disconnect callback.
|
|
90
|
+
* @returns {void}
|
|
91
|
+
*/
|
|
92
|
+
onClientDisconnect(callback) {
|
|
93
|
+
if (this.clientDisconnected) {
|
|
94
|
+
callback()
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
this.clientDisconnectCallbacks.add(callback)
|
|
53
98
|
}
|
|
54
99
|
|
|
55
100
|
baseURL() {
|
|
@@ -116,7 +116,13 @@ export default class VelociousHttpServerInProcessHandler {
|
|
|
116
116
|
httpClient.abortPendingFileResponses(),
|
|
117
117
|
httpClient.abortStreamResponses()
|
|
118
118
|
])
|
|
119
|
-
.then(() => {
|
|
119
|
+
.then(() => {
|
|
120
|
+
// Buffered responses have no stream to abort, so their in-flight
|
|
121
|
+
// handlers never hear about the socket teardown through the
|
|
122
|
+
// streaming path: notify the running requests directly so they can
|
|
123
|
+
// settle resources (e.g. admission queue positions) in-process.
|
|
124
|
+
httpClient.notifyClientDisconnect()
|
|
125
|
+
})
|
|
120
126
|
.catch((error) => {
|
|
121
127
|
this.logger.warn("Failed to abort responses after client close", error)
|
|
122
128
|
})
|
|
@@ -220,6 +220,11 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
|
|
|
220
220
|
if (client) {
|
|
221
221
|
settlements.push(client.abortPendingFileResponses())
|
|
222
222
|
settlements.push(client.abortStreamResponses())
|
|
223
|
+
// Buffered responses have no stream to abort, so their in-flight
|
|
224
|
+
// handlers never hear about the socket teardown through the streaming
|
|
225
|
+
// path: notify the running requests directly so they can settle
|
|
226
|
+
// resources (e.g. admission queue positions) in-process.
|
|
227
|
+
client.notifyClientDisconnect()
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
for (const [transferId, transfer] of this.fileTransfers) {
|