Skip to content

Commit 2ef8e00

Browse files
authored
fix connection leaks in aborted fetch() requests (#924)
- in doCancel(), use abort controller and call abort(), instead of body.cancel() - ensure doCancel() is called when a WARC record is not written, eg. is a dupe, as stream is likely not consumed - also call IO.close() when uses browser network reader - fixes #923 - also adds missing dupe check to async resources queued from behaviors (were being deduped on write, but were still fetched unnecessarily)
1 parent 8658df3 commit 2ef8e00

File tree

1 file changed

+42
-16
lines changed

1 file changed

+42
-16
lines changed

src/util/recorder.ts

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -866,17 +866,29 @@ export class Recorder extends EventEmitter {
866866
}
867867

868868
addExternalFetch(url: string, cdp: CDPSession) {
869-
logger.debug(
870-
"Handling fetch from behavior",
871-
{ url, ...this.logDetails },
872-
"recorder",
873-
);
874869
const reqresp = new RequestResponseInfo("0");
875870
reqresp.url = url;
876871
reqresp.method = "GET";
877872
reqresp.frameId = this.mainFrameId || undefined;
878-
this.addAsyncFetch({ reqresp, recorder: this, cdp });
879-
// return true if successful
873+
874+
const details = { url, ...this.logDetails };
875+
876+
const fetchIfNotDupe = async () => {
877+
if (await this.isDupeFetch(reqresp)) {
878+
logger.debug("Skipping dupe fetch from behavior", details, "recorder");
879+
return false;
880+
}
881+
882+
logger.debug("Handling fetch from behavior", details, "recorder");
883+
884+
this.addAsyncFetch({ reqresp, recorder: this, cdp });
885+
};
886+
887+
void fetchIfNotDupe().catch(() =>
888+
logger.warn("Error fetching URL from behavior", details, "recorder"),
889+
);
890+
891+
// return true to indicate no need for in-browser fetch
880892
return true;
881893
}
882894

@@ -1451,6 +1463,16 @@ export class Recorder extends EventEmitter {
14511463
"recorder",
14521464
);
14531465
reqresp.truncated = "disconnect";
1466+
} finally {
1467+
try {
1468+
await cdp.send("IO.close", { handle: stream });
1469+
} catch (e) {
1470+
logger.warn(
1471+
"takeStream close failed",
1472+
{ url: reqresp.url, ...this.logDetails },
1473+
"recorder",
1474+
);
1475+
}
14541476
}
14551477
}
14561478

@@ -1662,6 +1684,7 @@ class AsyncFetcher {
16621684

16631685
stream?: string;
16641686
resp?: Response;
1687+
abort?: AbortController;
16651688

16661689
maxFetchSize: number;
16671690

@@ -1753,7 +1776,11 @@ class AsyncFetcher {
17531776
throw new Error("resp body missing");
17541777
}
17551778

1756-
return await recorder.serializeToWARC(reqresp, iter);
1779+
if (!(await recorder.serializeToWARC(reqresp, iter))) {
1780+
await this.doCancel();
1781+
return false;
1782+
}
1783+
return true;
17571784
} catch (e) {
17581785
logger.warn(
17591786
"Async load body failed",
@@ -1765,14 +1792,10 @@ class AsyncFetcher {
17651792
}
17661793

17671794
async doCancel() {
1768-
const { resp, useBrowserNetwork } = this;
1769-
if (!useBrowserNetwork && resp) {
1770-
if (resp.status >= 300 && resp.status < 400) {
1771-
await resp.arrayBuffer();
1772-
} else {
1773-
// otherwise, just cancel
1774-
resp.body?.cancel().catch(() => {});
1775-
}
1795+
const { abort } = this;
1796+
if (abort) {
1797+
abort.abort();
1798+
this.abort = undefined;
17761799
}
17771800
}
17781801

@@ -1796,12 +1819,15 @@ class AsyncFetcher {
17961819
});
17971820
}
17981821

1822+
this.abort = new AbortController();
1823+
17991824
const resp = await fetch(url!, {
18001825
method,
18011826
headers,
18021827
body: reqresp.postData || undefined,
18031828
redirect: this.manualRedirect ? "manual" : "follow",
18041829
dispatcher,
1830+
signal: this.abort.signal,
18051831
});
18061832

18071833
if (

0 commit comments

Comments
 (0)