diff --git a/src/server/routes/webview.ts b/src/server/routes/webview.ts
index d89db2c..1385b62 100644
--- a/src/server/routes/webview.ts
+++ b/src/server/routes/webview.ts
@@ -172,10 +172,10 @@ async function webView(req: express.Request, res: express.Response) {
const html = `
Content${body}
`;
res.contentType("text/html").send(html);
} catch {
- f.send(res);
+ await f.send(res);
}
} else {
- f.send(res);
+ await f.send(res);
}
} catch (error) {
handleError(error, res, req);
diff --git a/src/streamer/route.ts b/src/streamer/route.ts
index 49cb1ce..b1238e5 100644
--- a/src/streamer/route.ts
+++ b/src/streamer/route.ts
@@ -39,6 +39,7 @@ router.post(
);
} catch (error) {
handleError(error, res);
+ if (res.headersSent && !res.writableEnded) res.destroy();
}
}
);
@@ -111,6 +112,8 @@ router.post("/", async (req: express.Request, res: express.Response) => {
content.destroy();
}
handleError(error, res);
+ if (res.headersSent && !res.writableEnded) res.destroy();
+ anonymizer.destroy();
}
content
.on("error", handleStreamError)
diff --git a/test/production-regressions.test.js b/test/production-regressions.test.js
index f7d9800..0cd8cc8 100644
--- a/test/production-regressions.test.js
+++ b/test/production-regressions.test.js
@@ -232,6 +232,21 @@ describe("production regressions", function () {
expect(res.headers["Content-Security-Policy"]).to.include("sandbox");
expect(res.headers["Content-Security-Policy"]).not.to.include("allow-same-origin");
});
+ it("terminates the streamer response after a late upstream error", async function () {
+ const input = new (require("stream").PassThrough)();
+ stub(GitHubStream.prototype, "getFileContentCache", async () => input);
+ stub(require("../src/server/routes/route-utils"), "handleError", () => {});
+ const res = new (require("stream").PassThrough)();
+ res.header = res.contentType = () => res;
+ res.headersSent = true;
+ res.resume();
+ const handler = require("../src/streamer/route").default.stack.find(x => x.route.path === "/").route.stack[0].handle;
+ await handler({ body: { token: "token", repoFullName: "owner/repo", repoId: "repo", filePath: "file.txt", anonymizerOptions: { filePath: "file.txt", image: true, link: true } } }, res);
+ input.write("partial data");
+ input.destroy(new Error("upstream broke"));
+ await new Promise(resolve => setImmediate(resolve));
+ expect(res.destroyed).to.equal(true);
+ });
it("keeps a conference eligible for retry when repository expiration fails", async function () {
const Conference = require("../src/core/Conference").default;
const model = { status: "ready", save: async () => {} };