mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
fix(stack-files): guard download stream destroy against the supertest in-process close race (#1227)
* fix(stack-files): guard download stream destroy against the supertest in-process close race The download metric was still booking the occasional request as a failure even after PR #1220 moved tracking off res.{finish,close} and onto stream.{end,close}. The remaining race lives one level up: under the in-process supertest transport, req.on("close") can fire as soon as the test consumes the response, before the readable's own end/close pair has been dispatched. The unconditional req.on("close", () => result.stream.destroy()) line forced the readable into a close-without-end state every time that happened. The close handler then booked a failure even though the pipe had delivered every byte. Two layers of defense: - req.on("close") now only destroys the stream while the response is still in flight. If res.writableEnded is already true the pipe has finished and the readable will end on its own; there is nothing to clean up and no synthetic close to provoke. - stream.on("close") falls back to recording success when the response was already fully written. Real disconnects keep recording a failure because res.writableEnded stays false in that case. The semantic stays unchanged: success means the server finished reading the file off disk and pushed it into the pipe. The fix removes the spurious failure path without weakening the disconnect-detection. * chore(stack-files): temporary download diagnostic for the metric race Logs the event order and response state at each handler so the next CI run shows whether req-close fires before stream-end, what res.writable / writableEnded / writableFinished evaluate to at that moment, and which path actually records the failure. Will be removed in the follow-up commit once the race is understood. * chore(stack-files): remove temporary download diagnostic Diagnostic captured the happy-path event order in the previous CI run; removing it now to isolate whether the fix alone is sufficient.
This commit is contained in:
@@ -1527,16 +1527,14 @@ stacksRouter.get('/:stackName/files/download', async (req: Request, res: Respons
|
||||
else res.destroy();
|
||||
recordDownloadOnce(false);
|
||||
});
|
||||
// `end` fires after all bytes are read off disk and pushed into the pipe;
|
||||
// at that point the response has the payload it needs to ship and the
|
||||
// download has succeeded from the server's perspective.
|
||||
result.stream.on('end', () => recordDownloadOnce(true));
|
||||
// `close` is the fallback for a destroyed-mid-stream path (the req-close
|
||||
// handler below tears the stream down on client disconnect). The flag
|
||||
// bails when close fires post-end so a clean completion still records
|
||||
// as success.
|
||||
result.stream.on('close', () => recordDownloadOnce(false));
|
||||
req.on('close', () => result.stream.destroy());
|
||||
result.stream.on('close', () => {
|
||||
if (res.writableEnded) recordDownloadOnce(true);
|
||||
else recordDownloadOnce(false);
|
||||
});
|
||||
req.on('close', () => {
|
||||
if (!res.writableEnded) result.stream.destroy();
|
||||
});
|
||||
logFileDiag('download stream opened', { stackName, relPath, nodeId: req.nodeId, size: result.size, elapsedMs: Date.now() - startedAt });
|
||||
result.stream.pipe(res);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user