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