Skip to content

Commit b926213

Browse files
authored
feat(ext/http): abort signal when request is cancelled (denoland#26761)
Closes denoland#21653
1 parent 742744d commit b926213

File tree

5 files changed

+57
-5
lines changed

5 files changed

+57
-5
lines changed

ext/fetch/23_request.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,19 +269,25 @@ class Request {
269269
/** @type {AbortSignal} */
270270
get [_signal]() {
271271
const signal = this[_signalCache];
272-
// This signal not been created yet, and the request is still in progress
273-
if (signal === undefined) {
272+
// This signal has not been created yet, but the request has already completed
273+
if (signal === false) {
274274
const signal = newSignal();
275275
this[_signalCache] = signal;
276+
signal[signalAbort](signalAbortError);
276277
return signal;
277278
}
278-
// This signal has not been created yet, but the request has already completed
279-
if (signal === false) {
279+
280+
// This signal not been created yet, and the request is still in progress
281+
if (signal === undefined) {
280282
const signal = newSignal();
281283
this[_signalCache] = signal;
282-
signal[signalAbort](signalAbortError);
283284
return signal;
284285
}
286+
287+
if (!signal.aborted && this[_request].isCancelled) {
288+
signal[signalAbort](signalAbortError);
289+
}
290+
285291
return signal;
286292
}
287293
get [_mimeType]() {

ext/http/00_serve.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
op_http_cancel,
1212
op_http_close,
1313
op_http_close_after_finish,
14+
op_http_get_request_cancelled,
1415
op_http_get_request_headers,
1516
op_http_get_request_method_and_url,
1617
op_http_read_request_body,
@@ -373,6 +374,13 @@ class InnerRequest {
373374
get external() {
374375
return this.#external;
375376
}
377+
378+
get isCancelled() {
379+
if (this.#external === null) {
380+
return true;
381+
}
382+
return op_http_get_request_cancelled(this.#external);
383+
}
376384
}
377385

378386
class CallbackContext {

ext/http/http_next.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,14 @@ fn set_response(
700700
http.complete();
701701
}
702702

703+
#[op2(fast)]
704+
pub fn op_http_get_request_cancelled(external: *const c_void) -> bool {
705+
let http =
706+
// SAFETY: op is called with external.
707+
unsafe { clone_external!(external, "op_http_get_request_cancelled") };
708+
http.cancelled()
709+
}
710+
703711
/// Returned promise resolves when body streaming finishes.
704712
/// Call [`op_http_close_after_finish`] when done with the external.
705713
#[op2(async)]

ext/http/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ deno_core::extension!(
113113
http_next::op_http_get_request_header,
114114
http_next::op_http_get_request_headers,
115115
http_next::op_http_get_request_method_and_url<HTTP>,
116+
http_next::op_http_get_request_cancelled,
116117
http_next::op_http_read_request_body,
117118
http_next::op_http_serve_on<HTTP>,
118119
http_next::op_http_serve<HTTP>,

tests/unit/serve_test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4270,3 +4270,32 @@ Deno.test({
42704270
assertEquals(hostname, "0.0.0.0");
42714271
await server.shutdown();
42724272
});
4273+
4274+
Deno.test({
4275+
name: "AbortSignal aborted when request is cancelled",
4276+
}, async () => {
4277+
const { promise, resolve } = Promise.withResolvers<void>();
4278+
4279+
let cancelled = false;
4280+
4281+
const server = Deno.serve({
4282+
hostname: "0.0.0.0",
4283+
port: servePort,
4284+
onListen: () => resolve(),
4285+
}, async (request) => {
4286+
request.signal.addEventListener("abort", () => cancelled = true);
4287+
assert(!request.signal.aborted);
4288+
await new Promise((resolve) => setTimeout(resolve, 3000)); // abort during waiting
4289+
assert(request.signal.aborted);
4290+
return new Response("Ok");
4291+
});
4292+
4293+
await promise;
4294+
await fetch(`http://localhost:${servePort}/`, {
4295+
signal: AbortSignal.timeout(1000),
4296+
}).catch(() => {});
4297+
4298+
await server.shutdown();
4299+
4300+
assert(cancelled);
4301+
});

0 commit comments

Comments
 (0)