fix(core): propagate errors when daemon isn't able to start

This commit is contained in:
Victor Savkin 2021-12-08 16:19:22 -05:00 committed by Victor Savkin
parent 7d13b54216
commit 1e6761ebc3
5 changed files with 72 additions and 29 deletions

View File

@ -1,5 +1,4 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited

View File

@ -1,6 +1,6 @@
{ {
"name": "@nrwl/nx-source", "name": "@nrwl/nx-source",
"version": "13.3.0-beta.22", "version": "13.3.0-beta.24",
"description": "Smart, Extensible Build Framework", "description": "Smart, Extensible Build Framework",
"homepage": "https://nx.dev", "homepage": "https://nx.dev",
"private": true, "private": true,

View File

@ -1,6 +1,6 @@
import { logger, ProjectGraph } from '@nrwl/devkit'; import { logger, ProjectGraph } from '@nrwl/devkit';
import { ChildProcess, spawn, spawnSync } from 'child_process'; import { ChildProcess, spawn, spawnSync } from 'child_process';
import { existsSync, openSync } from 'fs'; import { existsSync, openSync, readFileSync } from 'fs';
import { connect } from 'net'; import { connect } from 'net';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { import {
@ -13,6 +13,7 @@ import {
killSocketOrPath, killSocketOrPath,
} from '../socket-utils'; } from '../socket-utils';
import { DAEMON_OUTPUT_LOG_FILE } from '../tmp-dir'; import { DAEMON_OUTPUT_LOG_FILE } from '../tmp-dir';
import { output } from '../../../../utilities/output';
export async function startInBackground(): Promise<ChildProcess['pid']> { export async function startInBackground(): Promise<ChildProcess['pid']> {
await safelyCleanUpExistingProcess(); await safelyCleanUpExistingProcess();
@ -37,17 +38,45 @@ export async function startInBackground(): Promise<ChildProcess['pid']> {
/** /**
* Ensure the server is actually available to connect to via IPC before resolving * Ensure the server is actually available to connect to via IPC before resolving
*/ */
let attempts = 0;
return new Promise((resolve) => { return new Promise((resolve) => {
const id = setInterval(async () => { const id = setInterval(async () => {
if (await isServerAvailable()) { if (await isServerAvailable()) {
clearInterval(id); clearInterval(id);
resolve(backgroundProcess.pid); resolve(backgroundProcess.pid);
} else if (attempts > 200) {
// daemon fails to start, the process probably exited
// we print the logs and exit the client
throw daemonProcessException(
'Failed to start the Nx Daemon process.'
);
} else {
attempts++;
} }
}, 500); }, 10);
}); });
} catch (err) { } catch (err) {
logger.error(err); throw err;
process.exit(1); }
}
function daemonProcessException(message: string) {
try {
let log = readFileSync(DAEMON_OUTPUT_LOG_FILE).toString().split('\n');
if (log.length > 20) {
log = log.slice(log.length - 20);
}
return new Error(
[
message,
'Messages from the log:',
...log,
'\n',
`More information: ${DAEMON_OUTPUT_LOG_FILE}`,
].join('\n')
);
} catch (e) {
return new Error(message);
} }
} }
@ -71,7 +100,7 @@ export function stop(): void {
/** /**
* As noted in the comments above the createServer() call, in order to reliably (meaning it works * As noted in the comments above the createServer() call, in order to reliably (meaning it works
* cross-platform) check whether or not the server is availabe to request a project graph from we * cross-platform) check whether the server is available to request a project graph from we
* need to actually attempt connecting to it. * need to actually attempt connecting to it.
* *
* Because of the behavior of named pipes on Windows, we cannot simply treat them as a file and * Because of the behavior of named pipes on Windows, we cannot simply treat them as a file and
@ -109,18 +138,22 @@ export async function getProjectGraphFromServer(): Promise<ProjectGraph> {
const socket = connect(FULL_OS_SOCKET_PATH); const socket = connect(FULL_OS_SOCKET_PATH);
socket.on('error', (err) => { socket.on('error', (err) => {
let errorMessage: string | undefined; let error: any;
if (err.message.startsWith('connect ENOENT')) { if (err.message.startsWith('connect ENOENT')) {
errorMessage = 'Error: The Daemon Server is not running'; error = daemonProcessException('The Daemon Server is not running');
} }
if (err.message.startsWith('connect ECONNREFUSED')) { if (err.message.startsWith('connect ECONNREFUSED')) {
// If somehow the file descriptor had not been released during a previous shut down. error = daemonProcessException(
if (existsSync(FULL_OS_SOCKET_PATH)) { `A server instance had not been fully shut down. Please try running the command again.`
errorMessage = `Error: A server instance had not been fully shut down. Please try running the command again.`; );
killSocketOrPath(); killSocketOrPath();
} }
if (err.message.startsWith('read ECONNRESET')) {
error = daemonProcessException(
`Unable to connect to the daemon process.`
);
} }
return reject(new Error(errorMessage) || err); return reject(error || err);
}); });
/** /**

View File

@ -268,8 +268,10 @@ export async function startServer(): Promise<Server> {
if (!isWindows) { if (!isWindows) {
killSocketOrPath(); killSocketOrPath();
} }
return new Promise((resolve) => { return new Promise((resolve, reject) => {
try {
server.listen(FULL_OS_SOCKET_PATH, async () => { server.listen(FULL_OS_SOCKET_PATH, async () => {
try {
serverLogger.log(`Started listening on: ${FULL_OS_SOCKET_PATH}`); serverLogger.log(`Started listening on: ${FULL_OS_SOCKET_PATH}`);
// this triggers the storage of the lock file hash // this triggers the storage of the lock file hash
lockFileChanged(); lockFileChanged();
@ -278,11 +280,18 @@ export async function startServer(): Promise<Server> {
watcherSubscription = await subscribeToWorkspaceChanges( watcherSubscription = await subscribeToWorkspaceChanges(
handleWorkspaceChanges handleWorkspaceChanges
); );
serverLogger.watcherLog(`Subscribed to changes within: ${appRootPath}`); serverLogger.watcherLog(
`Subscribed to changes within: ${appRootPath}`
);
} }
return resolve(server); return resolve(server);
} catch (err) {
reject(err);
}
}); });
} catch (err) {
reject(err);
}
}); });
} }

View File

@ -1,10 +1,12 @@
import { logger } from '@nrwl/devkit'; import { logger } from '@nrwl/devkit';
import { startServer } from './server'; import { startServer } from './server';
import * as process from 'process';
(async () => { (async () => {
try { try {
await startServer(); await startServer();
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);
process.exit(1);
} }
})(); })();