UNPKG

next

Version:

The React Framework

177 lines (176 loc) 7.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "installUseCacheProbe", { enumerable: true, get: function() { return installUseCacheProbe; } }); const _jestworker = require("next/dist/compiled/jest-worker"); const _usecacheprobeglobals = require("../use-cache/use-cache-probe-globals"); const _requirecache = require("./require-cache"); const _utils = require("../lib/utils"); const _needsexperimentalreact = require("../../lib/needs-experimental-react"); /** * Convert the `encodedArguments` that `use-cache-wrapper.ts` already holds into * the worker-transport shape. Blob values are base64-encoded so the payload * survives the child-process JSON-only fallback transport — the worker_threads * path would handle Blobs via structured clone, but we need one shape that * works for both. */ async function toEncodedArgumentsForProbe(encoded) { if (typeof encoded === 'string') { return { kind: 'string', data: encoded }; } const entries = []; for (const [key, value] of encoded.entries()){ if (typeof value === 'string') { entries.push([ key, value ]); } else { const bytes = Buffer.from(await value.arrayBuffer()).toString('base64'); entries.push([ key, { kind: 'blob', bytes, type: value.type } ]); } } return { kind: 'formdata', entries }; } function installUseCacheProbe(options) { const { distDir, buildId, deploymentId, nextConfig } = options; // The deadlock pattern we're detecting requires the outer render's // dynamic-stage semantics to produce a halted promise that a user-space // `Map<string, Promise>` captures. A probe worker has no outer render, so its // cache-scope fetches resolve normally — the shared module scope can never // accumulate a halted promise that would poison a sibling probe. That's why // reusing workers across probes is safe: isolation between main and probe is // what matters, not between probe invocations. // // Torn down on HMR (stale user modules) and on worker crash. Hung probes // don't trigger teardown — the worker is still able to handle further tasks // (the hung promise just sits in its heap), and that leak is bounded by the // next HMR clear. let pool; const getPool = ()=>{ if (pool) { return pool; } // Strip `--inspect` from any inherited `NODE_OPTIONS` so the worker doesn't // fight the parent for the same debug port. const probeNodeOptions = (0, _utils.getFormattedNodeOptionsWithoutInspect)(); // The worker is shipped as four pre-bundled dev-only artifacts — // {webpack,turbopack} × {stable,experimental} — so the bundler aliases and // react-server layer resolve correctly at Next-build time. Pick the // matching artifact from runtime env. `needsExperimentalReact` is the same // predicate `define-env.ts` uses to wire `__NEXT_EXPERIMENTAL_REACT` for // the user's bundle, so the worker stays in lockstep. const turbo = process.env.TURBOPACK ? '-turbo' : ''; const channel = (0, _needsexperimentalreact.needsExperimentalReact)(nextConfig) ? '-experimental' : ''; const workerPath = require.resolve(`next/dist/compiled/next-server/use-cache-probe-worker${turbo}${channel}.runtime.dev.js`); const worker = new _jestworker.Worker(workerPath, { maxRetries: 0, // jest-worker has no per-task scaling: once the pool is created, all // `numWorkers` workers are alive until pool teardown. Set to absorb // the realistic case of a single dev request fanning out into // multiple concurrent `'use cache'` invocations that each hit the // probe threshold. // TODO: replace with on-demand scaling once // https://github.com/vercel/next.js/pull/90532 lands — workers can // then spawn lazily and shrink back when idle. numWorkers: 4, enableWorkerThreads: nextConfig.experimental.workerThreads, // Listing the methods explicitly tells jest-worker to skip the discovery // `require()` it would otherwise do in the parent process. The bundle has // the full RSC pipeline embedded — loading it in the parent would eagerly // initialize bindings that should only run in the isolated worker. exposedMethods: [ 'probeUseCache' ], forkOptions: { env: { ...process.env, NODE_OPTIONS: probeNodeOptions } } }); worker.getStdout().pipe(process.stdout); worker.getStderr().pipe(process.stderr); pool = worker; return worker; }; const tearDownPool = async ()=>{ const current = pool; if (!current) { return; } pool = undefined; await current.end().catch(()=>{ // The worker process will exit on its own when nothing else holds it // open; a failed `.end()` here just means we couldn't wait cleanly. }); }; const runProbe = async (msg)=>{ let activePool; try { activePool = getPool(); } catch { return false; } try { return await activePool.probeUseCache(msg); } catch { // Worker crash or IPC error: tear down the pool so the next probe starts // fresh. await tearDownPool(); return false; } }; // The dev server can't reach into worker isolates to clear their // `require.cache` or `loadManifest` `sharedCache`, so we drop the whole pool // whenever the parent's caches are invalidated. The next probe lazy-spawns a // fresh worker with empty caches. No path-level bookkeeping — cache // invalidation in dev is infrequent and pool startup is cheap. (0, _requirecache.onCacheInvalidation)(()=>{ void tearDownPool(); }); // Wire the probe hook that `use-cache-wrapper` calls when a cache fill has // been idle long enough to suspect a hang. The forwarded request snapshot // lets the worker rebuild a faithful request store so cache bodies that read // cookies/headers/etc. behave as in a real fill. (0, _usecacheprobeglobals.setUseCacheProbe)(async (args)=>{ const encodedArguments = await toEncodedArgumentsForProbe(args.encodedArguments); return runProbe({ distDir, page: args.page, route: args.route, id: args.id, kind: args.kind, encodedArguments, request: args.request, buildId, deploymentId, nextConfigSerializable: { httpAgentOptions: nextConfig.httpAgentOptions, cacheLifeProfiles: nextConfig.cacheLife, useCacheTimeout: nextConfig.experimental.useCacheTimeout, staticPageGenerationTimeout: nextConfig.staticPageGenerationTimeout }, timeoutMs: args.timeoutMs }); }); } //# sourceMappingURL=use-cache-probe-pool.js.map