diff options
Diffstat (limited to 'src/js/node')
-rw-r--r-- | src/js/node/http.ts | 132 | ||||
-rw-r--r-- | src/js/node/stream.js | 28 |
2 files changed, 113 insertions, 47 deletions
diff --git a/src/js/node/http.ts b/src/js/node/http.ts index 0c42b247b..c8fbf20d6 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -109,6 +109,28 @@ function isValidTLSArray(obj) { } } +class ERR_INVALID_ARG_TYPE extends TypeError { + constructor(name, expected, actual) { + super(`The ${name} argument must be of type ${expected}. Received type ${typeof actual}`); + this.code = "ERR_INVALID_ARG_TYPE"; + } +} + +function validateMsecs(numberlike: any, field: string) { + if (typeof numberlike !== "number" || numberlike < 0) { + throw new ERR_INVALID_ARG_TYPE(field, "number", numberlike); + } + + return numberlike; +} +function validateFunction(callable: any, field: string) { + if (typeof callable !== "function") { + throw new ERR_INVALID_ARG_TYPE(field, "Function", callable); + } + + return callable; +} + function getHeader(headers, name) { if (!headers) return; const result = headers.get(name); @@ -792,12 +814,13 @@ export class OutgoingMessage extends Writable { headersSent = false; sendDate = true; req; + timeout; #finished = false; [kEndCalled] = false; #fakeSocket; - #timeoutTimer: Timer | null = null; + #timeoutTimer?: Timer; [kAbortController]: AbortController | null = null; // Express "compress" package uses this @@ -894,21 +917,41 @@ export class OutgoingMessage extends Writable { [kClearTimeout]() { if (this.#timeoutTimer) { clearTimeout(this.#timeoutTimer); - this.#timeoutTimer = null; + this.removeAllListeners("timeout"); + this.#timeoutTimer = undefined; } } + #onTimeout() { + this.#timeoutTimer = undefined; + this[kAbortController]?.abort(); + this.emit("timeout"); + } + setTimeout(msecs, callback) { - if (this.#timeoutTimer) return this; - if (callback) { - this.on("timeout", callback); - } + if (this.destroyed) return this; + + this.timeout = msecs = validateMsecs(msecs, "msecs"); - this.#timeoutTimer = setTimeout(async () => { - this.#timeoutTimer = null; - this[kAbortController]?.abort(); - this.emit("timeout"); - }, msecs); + // Attempt to clear an existing timer in both cases - + // even if it will be rescheduled we don't want to leak an existing timer. + clearTimeout(this.#timeoutTimer!); + + if (msecs === 0) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.removeListener("timeout", callback); + } + + this.#timeoutTimer = undefined; + } else { + this.#timeoutTimer = setTimeout(this.#onTimeout.bind(this), msecs).unref(); + + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.once("timeout", callback); + } + } return this; } @@ -1159,7 +1202,7 @@ export class ClientRequest extends OutgoingMessage { #fetchRequest; #signal: AbortSignal | null = null; [kAbortController]: AbortController | null = null; - #timeoutTimer: Timer | null = null; + #timeoutTimer?: Timer = undefined; #options; #finished; @@ -1228,6 +1271,9 @@ export class ClientRequest extends OutgoingMessage { redirect: "manual", verbose: Boolean(__DEBUG__), signal: this[kAbortController].signal, + + // Timeouts are handled via this.setTimeout. + timeout: false, }, ) .then(response => { @@ -1352,8 +1398,6 @@ export class ClientRequest extends OutgoingMessage { this.#socketPath = options.socketPath; - if (options.timeout !== undefined) this.setTimeout(options.timeout, null); - const signal = options.signal; if (signal) { //We still want to control abort function and timeout so signal call our AbortController @@ -1431,7 +1475,12 @@ export class ClientRequest extends OutgoingMessage { this.#reusedSocket = false; this.#host = host; this.#protocol = protocol; - this.#timeoutTimer = null; + + var timeout = options.timeout; + if (timeout !== undefined && timeout !== 0) { + this.setTimeout(timeout, undefined); + } + const headersArray = ArrayIsArray(headers); if (!headersArray) { var headers = options.headers; @@ -1486,17 +1535,8 @@ export class ClientRequest extends OutgoingMessage { // this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders); - var optsWithoutSignal = options; - if (optsWithoutSignal.signal) { - optsWithoutSignal = ObjectAssign({}, options); - delete optsWithoutSignal.signal; - } + var { signal: _signal, ...optsWithoutSignal } = options; this.#options = optsWithoutSignal; - - var timeout = options.timeout; - if (timeout) { - this.setTimeout(timeout); - } } setSocketKeepAlive(enable = true, initialDelay = 0) { @@ -1509,21 +1549,41 @@ export class ClientRequest extends OutgoingMessage { [kClearTimeout]() { if (this.#timeoutTimer) { clearTimeout(this.#timeoutTimer); - this.#timeoutTimer = null; + this.#timeoutTimer = undefined; + this.removeAllListeners("timeout"); } } - setTimeout(msecs, callback?) { - if (this.#timeoutTimer) return this; - if (callback) { - this.on("timeout", callback); - } + #onTimeout() { + this.#timeoutTimer = undefined; + this[kAbortController]?.abort(); + this.emit("timeout"); + } - this.#timeoutTimer = setTimeout(async () => { - this.#timeoutTimer = null; - this[kAbortController]?.abort(); - this.emit("timeout"); - }, msecs); + setTimeout(msecs, callback) { + if (this.destroyed) return this; + + this.timeout = msecs = validateMsecs(msecs, "msecs"); + + // Attempt to clear an existing timer in both cases - + // even if it will be rescheduled we don't want to leak an existing timer. + clearTimeout(this.#timeoutTimer!); + + if (msecs === 0) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.removeListener("timeout", callback); + } + + this.#timeoutTimer = undefined; + } else { + this.#timeoutTimer = setTimeout(this.#onTimeout.bind(this), msecs).unref(); + + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.once("timeout", callback); + } + } return this; } diff --git a/src/js/node/stream.js b/src/js/node/stream.js index 9344fa73f..741b2f65c 100644 --- a/src/js/node/stream.js +++ b/src/js/node/stream.js @@ -1,15 +1,20 @@ // Hardcoded module "node:stream" / "readable-stream" // "readable-stream" npm package // just transpiled -var { isPromise, isCallable, direct, Object } = globalThis[Symbol.for("Bun.lazy")]("primordials"); -import { EventEmitter as EE } from "bun:events_native"; -import { StringDecoder } from "node:string_decoder"; -globalThis.__IDS_TO_TRACK = process.env.DEBUG_TRACK_EE?.length - ? process.env.DEBUG_TRACK_EE.split(",") - : process.env.DEBUG_STREAMS?.length - ? process.env.DEBUG_STREAMS.split(",") - : null; +// This must go at the top of the file, before any side effects. +// IS_BUN_DEVELOPMENT is a bundle-only global variable that is set to true when +// building a development bundle. +const __TRACK_EE__ = IS_BUN_DEVELOPMENT && !!process.env.DEBUG_TRACK_EE; +const __DEBUG__ = IS_BUN_DEVELOPMENT && !!(process.env.DEBUG || process.env.DEBUG_STREAMS || __TRACK_EE__); + +if (__DEBUG__) { + globalThis.__IDS_TO_TRACK = process.env.DEBUG_TRACK_EE?.length + ? process.env.DEBUG_TRACK_EE.split(",") + : process.env.DEBUG_STREAMS?.length + ? process.env.DEBUG_STREAMS.split(",") + : null; +} // Separating DEBUG, DEBUG_STREAMS and DEBUG_TRACK_EE env vars makes it easier to focus on the // events in this file rather than all debug output across all files @@ -18,9 +23,6 @@ globalThis.__IDS_TO_TRACK = process.env.DEBUG_TRACK_EE?.length // The events and/or all of the outputs for the given stream IDs assigned at stream construction // By default, child_process gives -const __TRACK_EE__ = !!process.env.DEBUG_TRACK_EE; -const __DEBUG__ = !!(process.env.DEBUG || process.env.DEBUG_STREAMS || __TRACK_EE__); - var debug = __DEBUG__ ? globalThis.__IDS_TO_TRACK ? // If we are tracking IDs for debug event emitters, we should prefix the debug output with the ID @@ -32,6 +34,10 @@ var debug = __DEBUG__ : (...args) => console.log(...args.slice(0, -1)) : () => {}; +var { isPromise, isCallable, direct, Object } = globalThis[Symbol.for("Bun.lazy")]("primordials"); +import { EventEmitter as EE } from "bun:events_native"; +import { StringDecoder } from "node:string_decoder"; + var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; |