diff --git a/deps/undici/src/lib/core/diagnostics.js b/deps/undici/src/lib/core/diagnostics.js index e1af3db611..c0b61daab0 100644 --- a/deps/undici/src/lib/core/diagnostics.js +++ b/deps/undici/src/lib/core/diagnostics.js @@ -6,6 +6,12 @@ const undiciDebugLog = util.debuglog('undici') const fetchDebuglog = util.debuglog('fetch') const websocketDebuglog = util.debuglog('websocket') let isClientSet = false +let tracingChannel + +if (diagnosticsChannel.tracingChannel) { + tracingChannel = diagnosticsChannel.tracingChannel('undici:fetch') +} + const channels = { // Client beforeConnect: diagnosticsChannel.channel('undici:client:beforeConnect'), @@ -23,7 +29,9 @@ const channels = { close: diagnosticsChannel.channel('undici:websocket:close'), socketError: diagnosticsChannel.channel('undici:websocket:socket_error'), ping: diagnosticsChannel.channel('undici:websocket:ping'), - pong: diagnosticsChannel.channel('undici:websocket:pong') + pong: diagnosticsChannel.channel('undici:websocket:pong'), + // Fetch channels + tracingChannel } if (undiciDebugLog.enabled || fetchDebuglog.enabled) { diff --git a/deps/undici/src/lib/web/fetch/index.js b/deps/undici/src/lib/web/fetch/index.js index 784e0c2cdb..d6a00f129d 100644 --- a/deps/undici/src/lib/web/fetch/index.js +++ b/deps/undici/src/lib/web/fetch/index.js @@ -70,6 +70,7 @@ const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esb ? 'node' : 'undici' +const channels = require('../../core/diagnostics.js').channels.tracingChannel /** @type {import('buffer').resolveObjectURL} */ let resolveObjectURL @@ -124,12 +125,68 @@ function handleFetchDone (response) { finalizeAndReportTiming(response, 'fetch') } +// This will publish all diagnostic events only when we have subscribers. +function ifSubscribersRunStores (req, input, init, callback) { + const hasSubscribers = subscribersCheck() + + if (hasSubscribers) { + const context = { req, input, init, result: null, error: null } + + return channels.start.runStores(context, () => { + try { + return callback(createInstrumentedDeferredPromise(context)) + } catch (e) { + context.error = e + channels.error.publish(context) + throw e + } finally { + channels.end.publish(context) + } + }) + } else { + return callback(createDeferredPromise()) + } +} + +// subscribersCheck will be called at the beginning of the fetch call +// and will check if we have subscribers +function subscribersCheck () { + return channels && (channels.start.hasSubscribers || + channels.end.hasSubscribers || + channels.asyncStart.hasSubscribers || + channels.asyncEnd.hasSubscribers || + channels.error.hasSubscribers) +} + +function createInstrumentedDeferredPromise (context) { + let res + let rej + const promise = new Promise((resolve, reject) => { + res = function (result) { + context.result = result + channels.asyncStart.runStores(context, () => { + resolve(result) + channels.asyncEnd.publish(context) + }) + } + rej = function (error) { + context.error = error + channels.error.publish(context) + channels.asyncStart.runStores(context, () => { + reject(error) + channels.asyncEnd.publish(context) + }) + } + }) + + return { promise, resolve: res, reject: rej } +} + // https://fetch.spec.whatwg.org/#fetch-method function fetch (input, init = undefined) { webidl.argumentLengthCheck(arguments, 1, 'globalThis.fetch') // 1. Let p be a new promise. - let p = createDeferredPromise() // 2. Let requestObject be the result of invoking the initial value of // Request as constructor with input and init as arguments. If this throws @@ -139,116 +196,117 @@ function fetch (input, init = undefined) { try { requestObject = new Request(input, init) } catch (e) { - p.reject(e) - return p.promise + return Promise.reject(e) } - // 3. Let request be requestObject’s request. - const request = requestObject[kState] + return ifSubscribersRunStores(requestObject, input, init, p => { + // 3. Let request be requestObject’s request. + const request = requestObject[kState] - // 4. If requestObject’s signal’s aborted flag is set, then: - if (requestObject.signal.aborted) { + // 4. If requestObject’s signal’s aborted flag is set, then: + if (requestObject.signal.aborted) { // 1. Abort the fetch() call with p, request, null, and // requestObject’s signal’s abort reason. - abortFetch(p, request, null, requestObject.signal.reason) + abortFetch(p, request, null, requestObject.signal.reason) - // 2. Return p. - return p.promise - } + // 2. Return p. + return p.promise + } - // 5. Let globalObject be request’s client’s global object. - const globalObject = request.client.globalObject + // 5. Let globalObject be request’s client’s global object. + const globalObject = request.client.globalObject - // 6. If globalObject is a ServiceWorkerGlobalScope object, then set - // request’s service-workers mode to "none". - if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') { - request.serviceWorkers = 'none' - } + // 6. If globalObject is a ServiceWorkerGlobalScope object, then set + // request’s service-workers mode to "none". + if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') { + request.serviceWorkers = 'none' + } - // 7. Let responseObject be null. - let responseObject = null + // 7. Let responseObject be null. + let responseObject = null - // 8. Let relevantRealm be this’s relevant Realm. + // 8. Let relevantRealm be this’s relevant Realm. - // 9. Let locallyAborted be false. - let locallyAborted = false + // 9. Let locallyAborted be false. + let locallyAborted = false - // 10. Let controller be null. - let controller = null + // 10. Let controller be null. + let controller = null - // 11. Add the following abort steps to requestObject’s signal: - addAbortListener( - requestObject.signal, - () => { - // 1. Set locallyAborted to true. - locallyAborted = true + // 11. Add the following abort steps to requestObject’s signal: + addAbortListener( + requestObject.signal, + () => { + // 1. Set locallyAborted to true. + locallyAborted = true - // 2. Assert: controller is non-null. - assert(controller != null) + // 2. Assert: controller is non-null. + assert(controller != null) - // 3. Abort controller with requestObject’s signal’s abort reason. - controller.abort(requestObject.signal.reason) + // 3. Abort controller with requestObject’s signal’s abort reason. + controller.abort(requestObject.signal.reason) - const realResponse = responseObject?.deref() + const realResponse = responseObject?.deref() - // 4. Abort the fetch() call with p, request, responseObject, - // and requestObject’s signal’s abort reason. - abortFetch(p, request, realResponse, requestObject.signal.reason) - } - ) + // 4. Abort the fetch() call with p, request, responseObject, + // and requestObject’s signal’s abort reason. + abortFetch(p, request, realResponse, requestObject.signal.reason) + } + ) - // 12. Let handleFetchDone given response response be to finalize and - // report timing with response, globalObject, and "fetch". - // see function handleFetchDone + // 12. Let handleFetchDone given response response be to finalize and + // report timing with response, globalObject, and "fetch". + // see function handleFetchDone - // 13. Set controller to the result of calling fetch given request, - // with processResponseEndOfBody set to handleFetchDone, and processResponse - // given response being these substeps: + // 13. Set controller to the result of calling fetch given request, + // with processResponseEndOfBody set to handleFetchDone, and processResponse + // given response being these substeps: - const processResponse = (response) => { - // 1. If locallyAborted is true, terminate these substeps. - if (locallyAborted) { - return - } + const processResponse = (response) => { + // 1. If locallyAborted is true, terminate these substeps. + if (locallyAborted) { + return + } - // 2. If response’s aborted flag is set, then: - if (response.aborted) { - // 1. Let deserializedError be the result of deserialize a serialized - // abort reason given controller’s serialized abort reason and - // relevantRealm. + // 2. If response’s aborted flag is set, then: + if (response.aborted) { + // 1. Let deserializedError be the result of deserialize a serialized + // abort reason given controller’s serialized abort reason and + // relevantRealm. - // 2. Abort the fetch() call with p, request, responseObject, and - // deserializedError. + // 2. Abort the fetch() call with p, request, responseObject, and + // deserializedError. - abortFetch(p, request, responseObject, controller.serializedAbortReason) - return - } + abortFetch(p, request, responseObject, controller.serializedAbortReason) + return + } - // 3. If response is a network error, then reject p with a TypeError - // and terminate these substeps. - if (response.type === 'error') { - p.reject(new TypeError('fetch failed', { cause: response.error })) - return - } + // 3. If response is a network error, then reject p with a TypeError + // and terminate these substeps. + if (response.type === 'error') { + p.reject(new TypeError('fetch failed', { cause: response.error })) + return + } - // 4. Set responseObject to the result of creating a Response object, - // given response, "immutable", and relevantRealm. - responseObject = new WeakRef(fromInnerResponse(response, 'immutable')) + // 4. Set responseObject to the result of creating a Response object, + // given response, "immutable", and relevantRealm. + responseObject = new WeakRef(fromInnerResponse(response, 'immutable')) - // 5. Resolve p with responseObject. - p.resolve(responseObject.deref()) - p = null - } + // 5. Resolve p with responseObject. + p.resolve(responseObject.deref()) + p = null + } - controller = fetching({ - request, - processResponseEndOfBody: handleFetchDone, - processResponse, - dispatcher: requestObject[kDispatcher] // undici - }) + controller = fetching({ + request, + processResponseEndOfBody: handleFetchDone, + processResponse, + dispatcher: requestObject[kDispatcher] // undici + }) - // 14. Return p. - return p.promise + // 14. Return p. + return p.promise + }) } // https://fetch.spec.whatwg.org/#finalize-and-report-timing @@ -444,7 +502,8 @@ function fetching ({ // 9. If request’s origin is "client", then set request’s origin to request’s // client’s origin. if (request.origin === 'client') { - request.origin = request.client.origin + // TODO: What if request.client is null? + request.origin = request.client?.origin } // 10. If all of the following conditions are true: diff --git a/deps/undici/undici.js b/deps/undici/undici.js index acbece9168..b9230e1252 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -1663,6 +1663,10 @@ var require_diagnostics = __commonJS({ var fetchDebuglog = util.debuglog("fetch"); var websocketDebuglog = util.debuglog("websocket"); var isClientSet = false; + var tracingChannel; + if (diagnosticsChannel.tracingChannel) { + tracingChannel = diagnosticsChannel.tracingChannel("undici:fetch"); + } var channels = { // Client beforeConnect: diagnosticsChannel.channel("undici:client:beforeConnect"), @@ -1680,7 +1684,9 @@ var require_diagnostics = __commonJS({ close: diagnosticsChannel.channel("undici:websocket:close"), socketError: diagnosticsChannel.channel("undici:websocket:socket_error"), ping: diagnosticsChannel.channel("undici:websocket:ping"), - pong: diagnosticsChannel.channel("undici:websocket:pong") + pong: diagnosticsChannel.channel("undici:websocket:pong"), + // Fetch channels + tracingChannel }; if (undiciDebugLog.enabled || fetchDebuglog.enabled) { const debuglog = fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog; @@ -9959,6 +9965,7 @@ var require_fetch = __commonJS({ var { STATUS_CODES } = require("node:http"); var GET_OR_HEAD = ["GET", "HEAD"]; var defaultUserAgent = typeof __UNDICI_IS_NODE__ !== "undefined" || true ? "node" : "undici"; + var channels = require_diagnostics().channels.tracingChannel; var resolveObjectURL; var Fetch = class extends EE { static { @@ -9997,62 +10004,109 @@ var require_fetch = __commonJS({ finalizeAndReportTiming(response, "fetch"); } __name(handleFetchDone, "handleFetchDone"); + function ifSubscribersRunStores(req, input, init, callback) { + const hasSubscribers = subscribersCheck(); + if (hasSubscribers) { + const context = { req, input, init, result: null, error: null }; + return channels.start.runStores(context, () => { + try { + return callback(createInstrumentedDeferredPromise(context)); + } catch (e) { + context.error = e; + channels.error.publish(context); + throw e; + } finally { + channels.end.publish(context); + } + }); + } else { + return callback(createDeferredPromise()); + } + } + __name(ifSubscribersRunStores, "ifSubscribersRunStores"); + function subscribersCheck() { + return channels && (channels.start.hasSubscribers || channels.end.hasSubscribers || channels.asyncStart.hasSubscribers || channels.asyncEnd.hasSubscribers || channels.error.hasSubscribers); + } + __name(subscribersCheck, "subscribersCheck"); + function createInstrumentedDeferredPromise(context) { + let res; + let rej; + const promise = new Promise((resolve, reject) => { + res = /* @__PURE__ */ __name(function(result) { + context.result = result; + channels.asyncStart.runStores(context, () => { + resolve(result); + channels.asyncEnd.publish(context); + }); + }, "res"); + rej = /* @__PURE__ */ __name(function(error) { + context.error = error; + channels.error.publish(context); + channels.asyncStart.runStores(context, () => { + reject(error); + channels.asyncEnd.publish(context); + }); + }, "rej"); + }); + return { promise, resolve: res, reject: rej }; + } + __name(createInstrumentedDeferredPromise, "createInstrumentedDeferredPromise"); function fetch2(input, init = void 0) { webidl.argumentLengthCheck(arguments, 1, "globalThis.fetch"); - let p = createDeferredPromise(); let requestObject; try { requestObject = new Request(input, init); } catch (e) { - p.reject(e); - return p.promise; - } - const request = requestObject[kState]; - if (requestObject.signal.aborted) { - abortFetch(p, request, null, requestObject.signal.reason); - return p.promise; + return Promise.reject(e); } - const globalObject = request.client.globalObject; - if (globalObject?.constructor?.name === "ServiceWorkerGlobalScope") { - request.serviceWorkers = "none"; - } - let responseObject = null; - let locallyAborted = false; - let controller = null; - addAbortListener( - requestObject.signal, - () => { - locallyAborted = true; - assert(controller != null); - controller.abort(requestObject.signal.reason); - const realResponse = responseObject?.deref(); - abortFetch(p, request, realResponse, requestObject.signal.reason); + return ifSubscribersRunStores(requestObject, input, init, (p) => { + const request = requestObject[kState]; + if (requestObject.signal.aborted) { + abortFetch(p, request, null, requestObject.signal.reason); + return p.promise; } - ); - const processResponse = /* @__PURE__ */ __name((response) => { - if (locallyAborted) { - return; - } - if (response.aborted) { - abortFetch(p, request, responseObject, controller.serializedAbortReason); - return; - } - if (response.type === "error") { - p.reject(new TypeError("fetch failed", { cause: response.error })); - return; + const globalObject = request.client.globalObject; + if (globalObject?.constructor?.name === "ServiceWorkerGlobalScope") { + request.serviceWorkers = "none"; } - responseObject = new WeakRef(fromInnerResponse(response, "immutable")); - p.resolve(responseObject.deref()); - p = null; - }, "processResponse"); - controller = fetching({ - request, - processResponseEndOfBody: handleFetchDone, - processResponse, - dispatcher: requestObject[kDispatcher] - // undici + let responseObject = null; + let locallyAborted = false; + let controller = null; + addAbortListener( + requestObject.signal, + () => { + locallyAborted = true; + assert(controller != null); + controller.abort(requestObject.signal.reason); + const realResponse = responseObject?.deref(); + abortFetch(p, request, realResponse, requestObject.signal.reason); + } + ); + const processResponse = /* @__PURE__ */ __name((response) => { + if (locallyAborted) { + return; + } + if (response.aborted) { + abortFetch(p, request, responseObject, controller.serializedAbortReason); + return; + } + if (response.type === "error") { + p.reject(new TypeError("fetch failed", { cause: response.error })); + return; + } + responseObject = new WeakRef(fromInnerResponse(response, "immutable")); + p.resolve(responseObject.deref()); + p = null; + }, "processResponse"); + controller = fetching({ + request, + processResponseEndOfBody: handleFetchDone, + processResponse, + dispatcher: requestObject[kDispatcher] + // undici + }); + return p.promise; }); - return p.promise; } __name(fetch2, "fetch"); function finalizeAndReportTiming(response, initiatorType = "other") { @@ -10154,7 +10208,7 @@ var require_fetch = __commonJS({ request.window = request.client?.globalObject?.constructor?.name === "Window" ? request.client : "no-window"; } if (request.origin === "client") { - request.origin = request.client.origin; + request.origin = request.client?.origin; } if (request.policyContainer === "client") { if (request.client != null) {