fix(core): handle self shutdown for plugin workers is orphaned before connections and loading

This commit is contained in:
FrozenPandaz 2024-11-22 13:10:53 -05:00 committed by Jason Jean
parent 0ae87f191c
commit 1bf0e67e1b
3 changed files with 42 additions and 4 deletions

View File

@ -579,6 +579,12 @@ export class DaemonClient {
}
async startInBackground(): Promise<ChildProcess['pid']> {
if (global.NX_PLUGIN_WORKER) {
throw new Error(
'Fatal Error: Something unexpected has occurred. Plugin Workers should not start a new daemon process. Please report this issue.'
);
}
mkdirSync(DAEMON_DIR_FOR_CURRENT_WORKSPACE, { recursive: true });
if (!existsSync(DAEMON_OUTPUT_LOG_FILE)) {
writeFileSync(DAEMON_OUTPUT_LOG_FILE, '');

View File

@ -10,7 +10,7 @@ import { PackageJson } from '../../utils/package-json';
import { nxVersion } from '../../utils/versions';
import { setupWorkspaceContext } from '../../utils/workspace-context';
import { workspaceRoot } from '../../utils/workspace-root';
import { writeDaemonJsonProcessCache } from '../cache';
import { getDaemonProcessIdSync, writeDaemonJsonProcessCache } from '../cache';
import {
getFullOsSocketPath,
isWindows,
@ -518,6 +518,17 @@ export async function startServer(): Promise<Server> {
server.listen(getFullOsSocketPath(), async () => {
try {
serverLogger.log(`Started listening on: ${getFullOsSocketPath()}`);
setInterval(() => {
if (getDaemonProcessIdSync() !== process.pid) {
return handleServerProcessTermination({
server,
reason: 'this process is no longer the current daemon (native)',
sockets: openSockets,
});
}
}).unref();
// this triggers the storage of the lock file hash
daemonIsOutdated();

View File

@ -12,12 +12,23 @@ if (process.env.NX_PERF_LOGGING === 'true') {
}
global.NX_GRAPH_CREATION = true;
global.NX_PLUGIN_WORKER = true;
let connected = false;
let plugin: LoadedNxPlugin;
const socketPath = process.argv[2];
const server = createServer((socket) => {
connected = true;
// This handles cases where the host process was killed
// after the worker connected but before the worker was
// instructed to load the plugin.
const loadTimeout = setTimeout(() => {
console.error(
`Plugin Worker exited because no plugin was loaded within 10 seconds of starting up.`
);
process.exit(1);
}, 10000).unref();
socket.on(
'data',
consumeMessagesFromSocket((raw) => {
@ -27,6 +38,7 @@ const server = createServer((socket) => {
}
return consumeMessage(socket, message, {
load: async ({ plugin: pluginConfiguration, root }) => {
if (loadTimeout) clearTimeout(loadTimeout);
process.chdir(root);
try {
const [promise] = loadNxPlugin(pluginConfiguration, root);
@ -120,6 +132,8 @@ const server = createServer((socket) => {
// since the worker is spawned per host process. As such,
// we can safely close the worker when the host disconnects.
socket.on('end', () => {
// Destroys the socket once it's fully closed.
socket.destroySoon();
// Stops accepting new connections, but existing connections are
// not closed immediately.
server.close(() => {
@ -128,13 +142,20 @@ const server = createServer((socket) => {
} catch (e) {}
process.exit(0);
});
// Destroys the socket once it's fully closed.
socket.destroySoon();
});
});
server.listen(socketPath);
setTimeout(() => {
if (!connected) {
console.error(
'The plugin worker is exiting as it was not connected to within 5 seconds.'
);
process.exit(1);
}
}, 5000).unref();
const exitHandler = (exitCode: number) => () => {
server.close();
try {