fix(core): daemon process should propagate file watcher errors to client
This commit is contained in:
parent
99164baaa0
commit
738708c11c
@ -48,8 +48,6 @@ describe('serverLogger', () => {
|
|||||||
serverLogger.log('Server started');
|
serverLogger.log('Server started');
|
||||||
serverLogger.watcherLog('Watching started');
|
serverLogger.watcherLog('Watching started');
|
||||||
serverLogger.requestLog('A request has come in');
|
serverLogger.requestLog('A request has come in');
|
||||||
serverLogger.nestedLog('Doing something with the request...');
|
|
||||||
serverLogger.nestedLog('Done with the request');
|
|
||||||
serverLogger.watcherLog('Watching stopped');
|
serverLogger.watcherLog('Watching stopped');
|
||||||
serverLogger.log('Server stopped');
|
serverLogger.log('Server stopped');
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@ -57,8 +55,6 @@ describe('serverLogger', () => {
|
|||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - Server started'],
|
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - Server started'],
|
||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - [WATCHER]: Watching started'],
|
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - [WATCHER]: Watching started'],
|
||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - [REQUEST]: A request has come in'],
|
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - [REQUEST]: A request has come in'],
|
||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - Doing something with the request...'],
|
|
||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - Done with the request'],
|
|
||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - [WATCHER]: Watching stopped'],
|
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - [WATCHER]: Watching stopped'],
|
||||||
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - Server stopped'],
|
['[NX Daemon Server] - 2021-10-11T17:18:45.980Z - Server stopped'],
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -32,10 +32,6 @@ class ServerLogger {
|
|||||||
this.log(`[WATCHER]: ${s.join(' ')}`);
|
this.log(`[WATCHER]: ${s.join(' ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
nestedLog(...s: unknown[]) {
|
|
||||||
this.log(` ${s.join(' ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatLogMessage(message: string) {
|
private formatLogMessage(message: string) {
|
||||||
return `[NX Daemon Server] - ${this.getNow()} - ${message}`;
|
return `[NX Daemon Server] - ${this.getNow()} - ${message}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,8 @@ const collectedDeletedFiles = new Set<string>();
|
|||||||
let waitPeriod = 100;
|
let waitPeriod = 100;
|
||||||
let scheduledTimeoutId;
|
let scheduledTimeoutId;
|
||||||
|
|
||||||
export function getCachedSerializedProjectGraphPromise() {
|
export async function getCachedSerializedProjectGraphPromise() {
|
||||||
|
try {
|
||||||
// recomputing it now on demand. we can ignore the scheduled timeout
|
// recomputing it now on demand. we can ignore the scheduled timeout
|
||||||
if (scheduledTimeoutId) {
|
if (scheduledTimeoutId) {
|
||||||
clearTimeout(scheduledTimeoutId);
|
clearTimeout(scheduledTimeoutId);
|
||||||
@ -52,7 +53,10 @@ export function getCachedSerializedProjectGraphPromise() {
|
|||||||
processCollectedUpdatedAndDeletedFiles();
|
processCollectedUpdatedAndDeletedFiles();
|
||||||
cachedSerializedProjectGraphPromise = createAndSerializeProjectGraph();
|
cachedSerializedProjectGraphPromise = createAndSerializeProjectGraph();
|
||||||
}
|
}
|
||||||
return cachedSerializedProjectGraphPromise;
|
return await cachedSerializedProjectGraphPromise;
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e, serializedProjectGraph: null };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addUpdatedAndDeletedFiles(
|
export function addUpdatedAndDeletedFiles(
|
||||||
@ -96,7 +100,7 @@ function processCollectedUpdatedAndDeletedFiles() {
|
|||||||
);
|
);
|
||||||
defaultFileHasher.incrementalUpdate(updatedFiles, deletedFiles);
|
defaultFileHasher.incrementalUpdate(updatedFiles, deletedFiles);
|
||||||
const workspaceJson = readWorkspaceJson();
|
const workspaceJson = readWorkspaceJson();
|
||||||
serverLogger.nestedLog(
|
serverLogger.requestLog(
|
||||||
`Updated file-hasher based on watched changes, recomputing project graph...`
|
`Updated file-hasher based on watched changes, recomputing project graph...`
|
||||||
);
|
);
|
||||||
// when workspace.json changes we cannot be sure about the correctness of the project file map
|
// when workspace.json changes we cannot be sure about the correctness of the project file map
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { logger, normalizePath } from '@nrwl/devkit';
|
import { logger, normalizePath, stripIndents } from '@nrwl/devkit';
|
||||||
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
||||||
import { createServer, Server, Socket } from 'net';
|
import { createServer, Server, Socket } from 'net';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
@ -24,57 +24,88 @@ import {
|
|||||||
import {
|
import {
|
||||||
addUpdatedAndDeletedFiles,
|
addUpdatedAndDeletedFiles,
|
||||||
getCachedSerializedProjectGraphPromise,
|
getCachedSerializedProjectGraphPromise,
|
||||||
resetInternalState,
|
|
||||||
} from './project-graph-incremental-recomputation';
|
} from './project-graph-incremental-recomputation';
|
||||||
import { statSync } from 'fs';
|
import { statSync } from 'fs';
|
||||||
|
|
||||||
function respondToClient(socket: Socket, message: string) {
|
function respondToClient(socket: Socket, message: string) {
|
||||||
|
return new Promise((res) => {
|
||||||
socket.write(message, () => {
|
socket.write(message, () => {
|
||||||
// Close the connection once all data has been written so that the client knows when to read it.
|
// Close the connection once all data has been written so that the client knows when to read it.
|
||||||
socket.end();
|
socket.end();
|
||||||
serverLogger.nestedLog(`Closed Connection to Client`);
|
serverLogger.log(`Closed Connection to Client`);
|
||||||
|
res(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let watcherSubscription: WatcherSubscription | undefined;
|
let watcherSubscription: WatcherSubscription | undefined;
|
||||||
let performanceObserver: PerformanceObserver | undefined;
|
let performanceObserver: PerformanceObserver | undefined;
|
||||||
|
let watcherError: Error | undefined;
|
||||||
|
|
||||||
const server = createServer((socket) => {
|
async function respondWithErrorAndExit(
|
||||||
|
socket: Socket,
|
||||||
|
description: string,
|
||||||
|
error: Error
|
||||||
|
) {
|
||||||
|
// print some extra stuff in the error message
|
||||||
|
serverLogger.requestLog(
|
||||||
|
`Responding to the client with an error`,
|
||||||
|
description,
|
||||||
|
error.message
|
||||||
|
);
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
error.message = stripIndents`
|
||||||
|
${error.message}
|
||||||
|
${description}
|
||||||
|
Because of the error the Nx daemon process has exited. The next Nx command is going to restart the daemon process.
|
||||||
|
If the error persists, please run "nx reset".
|
||||||
|
`;
|
||||||
|
|
||||||
|
await respondToClient(socket, serializeResult(error, null));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = createServer(async (socket) => {
|
||||||
resetInactivityTimeout(handleInactivityTimeout);
|
resetInactivityTimeout(handleInactivityTimeout);
|
||||||
if (!performanceObserver) {
|
if (!performanceObserver) {
|
||||||
performanceObserver = new PerformanceObserver((list) => {
|
performanceObserver = new PerformanceObserver((list) => {
|
||||||
const entry = list.getEntries()[0];
|
const entry = list.getEntries()[0];
|
||||||
serverLogger.nestedLog(
|
serverLogger.log(`Time taken for '${entry.name}'`, `${entry.duration}ms`);
|
||||||
`Time taken for '${entry.name}'`,
|
|
||||||
`${entry.duration}ms`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
performanceObserver.observe({ entryTypes: ['measure'], buffered: false });
|
performanceObserver.observe({ entryTypes: ['measure'], buffered: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('data', async (data) => {
|
socket.on('data', async (data) => {
|
||||||
|
if (watcherError) {
|
||||||
|
await respondWithErrorAndExit(
|
||||||
|
socket,
|
||||||
|
`File watcher error.`,
|
||||||
|
watcherError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
resetInactivityTimeout(handleInactivityTimeout);
|
resetInactivityTimeout(handleInactivityTimeout);
|
||||||
|
|
||||||
const payload = data.toString();
|
const payload = data.toString();
|
||||||
if (payload !== 'REQUEST_PROJECT_GRAPH_PAYLOAD') {
|
if (payload !== 'REQUEST_PROJECT_GRAPH_PAYLOAD') {
|
||||||
throw new Error(`Unsupported payload sent to daemon server: ${payload}`);
|
await respondWithErrorAndExit(
|
||||||
|
socket,
|
||||||
|
null,
|
||||||
|
new Error(`Unsupported payload sent to daemon server: ${payload}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
performance.mark('server-connection');
|
performance.mark('server-connection');
|
||||||
serverLogger.requestLog('Client Request for Project Graph Received');
|
serverLogger.requestLog('Client Request for Project Graph Received');
|
||||||
|
|
||||||
const result = await getCachedSerializedProjectGraphPromise();
|
const result = await getCachedSerializedProjectGraphPromise();
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
resetInternalState();
|
await respondWithErrorAndExit(
|
||||||
serverLogger.nestedLog(
|
|
||||||
`Error when preparing serialized project graph: ${result.error.message}`
|
|
||||||
);
|
|
||||||
respondToClient(
|
|
||||||
socket,
|
socket,
|
||||||
serializeResult(result.error, result.serializedProjectGraph)
|
`Error when preparing serialized project graph.`,
|
||||||
|
result.error
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializedResult = serializeResult(
|
const serializedResult = serializeResult(
|
||||||
@ -82,18 +113,13 @@ const server = createServer((socket) => {
|
|||||||
result.serializedProjectGraph
|
result.serializedProjectGraph
|
||||||
);
|
);
|
||||||
if (!serializedResult) {
|
if (!serializedResult) {
|
||||||
resetInternalState();
|
await respondWithErrorAndExit(
|
||||||
serverLogger.nestedLog(`Error when serializing project graph result`);
|
|
||||||
respondToClient(
|
|
||||||
socket,
|
socket,
|
||||||
serializeResult(
|
`Error when serializing project graph result.`,
|
||||||
new Error(
|
new Error(
|
||||||
'Critical error when serializing server result, check server logs'
|
'Critical error when serializing server result, check server logs'
|
||||||
),
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
performance.mark('serialized-project-graph-ready');
|
performance.mark('serialized-project-graph-ready');
|
||||||
@ -121,7 +147,7 @@ const server = createServer((socket) => {
|
|||||||
result.serializedProjectGraph,
|
result.serializedProjectGraph,
|
||||||
'utf-8'
|
'utf-8'
|
||||||
);
|
);
|
||||||
serverLogger.nestedLog(
|
serverLogger.requestLog(
|
||||||
`Closed Connection to Client (${bytesWritten} bytes transferred)`
|
`Closed Connection to Client (${bytesWritten} bytes transferred)`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -178,8 +204,8 @@ function resolveCurrentNxVersion(): string | null {
|
|||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
return version;
|
return version;
|
||||||
} catch {
|
} catch (err) {
|
||||||
serverLogger.nestedLog(
|
serverLogger.log(
|
||||||
`Error: Could not determine the current Nx version by inspecting: ${nrwlWorkspacePackageJsonPath}`
|
`Error: Could not determine the current Nx version by inspecting: ${nrwlWorkspacePackageJsonPath}`
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
@ -203,6 +229,14 @@ const handleWorkspaceChanges: SubscribeToWorkspaceChangesCallback = async (
|
|||||||
err,
|
err,
|
||||||
changeEvents
|
changeEvents
|
||||||
) => {
|
) => {
|
||||||
|
if (watcherError) {
|
||||||
|
serverLogger.watcherLog(
|
||||||
|
'Skipping handleWorkspaceChanges because of a previously recorded watcher error.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
resetInactivityTimeout(handleInactivityTimeout);
|
resetInactivityTimeout(handleInactivityTimeout);
|
||||||
|
|
||||||
if (!isNxVersionSame(resolveCurrentNxVersion())) {
|
if (!isNxVersionSame(resolveCurrentNxVersion())) {
|
||||||
@ -215,14 +249,14 @@ const handleWorkspaceChanges: SubscribeToWorkspaceChangesCallback = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (err || !changeEvents || !changeEvents.length) {
|
if (err || !changeEvents || !changeEvents.length) {
|
||||||
serverLogger.watcherLog('Unexpected Error');
|
serverLogger.watcherLog('Unexpected watcher error', err.message);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
watcherError = err;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
serverLogger.watcherLog(convertChangeEventsToLogMessage(changeEvents));
|
serverLogger.watcherLog(convertChangeEventsToLogMessage(changeEvents));
|
||||||
|
|
||||||
try {
|
|
||||||
const filesToHash = [];
|
const filesToHash = [];
|
||||||
const deletedFiles = [];
|
const deletedFiles = [];
|
||||||
for (const event of changeEvents) {
|
for (const event of changeEvents) {
|
||||||
@ -241,8 +275,9 @@ const handleWorkspaceChanges: SubscribeToWorkspaceChangesCallback = async (
|
|||||||
}
|
}
|
||||||
addUpdatedAndDeletedFiles(filesToHash, deletedFiles);
|
addUpdatedAndDeletedFiles(filesToHash, deletedFiles);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
serverLogger.log(`Unexpected Error`);
|
serverLogger.watcherLog(`Unexpected error`, err.message);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
watcherError = err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -21,13 +21,18 @@ export async function handleServerProcessTermination({
|
|||||||
reason,
|
reason,
|
||||||
watcherSubscription,
|
watcherSubscription,
|
||||||
}: HandleServerProcessTerminationParams) {
|
}: HandleServerProcessTerminationParams) {
|
||||||
|
try {
|
||||||
server.close();
|
server.close();
|
||||||
if (watcherSubscription) {
|
if (watcherSubscription) {
|
||||||
await watcherSubscription.unsubscribe();
|
await watcherSubscription.unsubscribe();
|
||||||
serverLogger.watcherLog(`Unsubscribed from changes within: ${appRootPath}`);
|
serverLogger.watcherLog(
|
||||||
|
`Unsubscribed from changes within: ${appRootPath}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
serverLogger.log(`Server stopped because: "${reason}"`);
|
serverLogger.log(`Server stopped because: "${reason}"`);
|
||||||
|
} finally {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverInactivityTimerId: NodeJS.Timeout | undefined;
|
let serverInactivityTimerId: NodeJS.Timeout | undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user