UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

114 lines (113 loc) 5.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._setEnvForwardTimeoutMs = _setEnvForwardTimeoutMs; exports.handleClientEnv = handleClientEnv; const get_plugins_1 = require("../../project-graph/plugins/get-plugins"); const daemon_environment_1 = require("../client/daemon-environment"); const logger_1 = require("../logger"); const project_graph_incremental_recomputation_1 = require("./project-graph-incremental-recomputation"); // Bounds the wait for worker acknowledgements so a wedged worker cannot hold // every env-carrying client message for the 10-minute plugin-hook timeout. A // healthy worker applies the env synchronously and acks in milliseconds. let envForwardTimeoutMs = 10_000; // Test seam: the production timeout would stall the suite. function _setEnvForwardTimeoutMs(ms) { envForwardTimeoutMs = ms; } let inFlightApply; /** * Applies an env-carrying client message to the daemon. Must be awaited * before dispatching the message's handler: plugin workers key their disk * caches on their own process env, so a graph request must not reach a worker * whose env still reflects the previous client. */ async function handleClientEnv(env) { // A client whose env matches one an in-flight apply already wrote to // process.env sees zero changed keys, yet the graph cache is only discarded // once that apply's forwarding completes. Wait for it so such a client // cannot be served the graph computed under the previous env. while (inFlightApply) { await inFlightApply; } const graphEnvBefore = (0, daemon_environment_1.normalizeDaemonEnvironmentForGraph)(process.env); const previousClientEnv = (0, daemon_environment_1.getAppliedDaemonClientEnv)()?.env; const changedEnvKeys = (0, daemon_environment_1.applyDaemonEnvFromClient)(env); if (changedEnvKeys.length === 0) { return; } // Runtime changes always reach the workers, but only changes that survive // graph normalization (e.g. Yarn Berry's per-invocation BERRY_BIN_FOLDER // does not) invalidate the computed graph. const graphChangedKeys = new Set([ ...(0, daemon_environment_1.getChangedEnvKeys)(graphEnvBefore, (0, daemon_environment_1.normalizeDaemonEnvironmentForGraph)(process.env)), ...(previousClientEnv ? (0, daemon_environment_1.getChangedEnvKeys)((0, daemon_environment_1.normalizeDaemonEnvironmentForGraph)(previousClientEnv), (0, daemon_environment_1.normalizeDaemonEnvironmentForGraph)(env)) : []), ]); if (graphChangedKeys.size > 0) { logger_1.serverLogger.log(`Graph recompute necessary due to env variable refresh. Changed keys: ${[ ...graphChangedKeys, ].join(', ')}`); } else { logger_1.serverLogger.log(`Env variable refresh changed only runtime values (${changedEnvKeys.join(', ')}); graph identity is unchanged, keeping the cached graph.`); } const apply = applyEnvChange(env, graphChangedKeys.size > 0); inFlightApply = apply; try { await apply; } finally { if (inFlightApply === apply) { inFlightApply = undefined; } } } async function applyEnvChange(env, invalidateGraph) { await forwardEnvToPluginWorkers(env); if (!invalidateGraph) { return; } // Discarding the cached graph makes the next request recompute under the // new env, and chains any in-flight compute (started under the old env) to // that successor. (0, project_graph_incremental_recomputation_1.invalidateGraphCache)(); } // Covers committed workers and an in-flight load, whose workers forked under // the previous env before this apply and would otherwise keep it for good. A // load started after this needs no forwarding: its workers fork with the // daemon's already-updated process.env. Each forward settles rather than // rejects so one dead worker (or a failed load) cannot fail every env-carrying // client message. Timing out is safe: each worker socket delivers the already // sent env update before any later graph message, and the plugin cache write // guard drops a pass the update lands in the middle of. async function forwardEnvToPluginWorkers(env) { let timer; const timedOut = await Promise.race([ forwardEnvToPluginWorkersUnbounded(env).then(() => false), new Promise((resolve) => { timer = setTimeout(() => resolve(true), envForwardTimeoutMs); timer.unref(); }), ]).finally(() => clearTimeout(timer)); if (timedOut) { logger_1.serverLogger.log(`Timed out forwarding the new env to plugin workers after ${envForwardTimeoutMs}ms; continuing without their acknowledgement.`); } } async function forwardEnvToPluginWorkersUnbounded(env) { const pluginsPromise = (0, get_plugins_1.getPluginsIfLoadedOrLoading)(); if (!pluginsPromise) { return; } let plugins; try { plugins = await pluginsPromise; } catch { // The load failed, so there are no workers to forward to. return; } await Promise.all(plugins.map((plugin) => plugin.setWorkerEnv?.(env)?.catch((e) => { logger_1.serverLogger.log(`Failed to forward env to plugin worker "${plugin.name}": ${e.message}`); }))); }