Skip to content

Commit

Permalink
fix(core): propagate errors when daemon isn't able to start
Browse files Browse the repository at this point in the history
  • Loading branch information
vsavkin committed Dec 9, 2021
1 parent 7d13b54 commit 1e6761e
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 29 deletions.
1 change: 0 additions & 1 deletion nx-dev/nx-dev/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nrwl/nx-source",
"version": "13.3.0-beta.22",
"version": "13.3.0-beta.24",
"description": "Smart, Extensible Build Framework",
"homepage": "https://nx.dev",
"private": true,
Expand Down
59 changes: 46 additions & 13 deletions packages/workspace/src/core/project-graph/daemon/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { logger, ProjectGraph } from '@nrwl/devkit';
import { ChildProcess, spawn, spawnSync } from 'child_process';
import { existsSync, openSync } from 'fs';
import { existsSync, openSync, readFileSync } from 'fs';
import { connect } from 'net';
import { performance } from 'perf_hooks';
import {
Expand All @@ -13,6 +13,7 @@ import {
killSocketOrPath,
} from '../socket-utils';
import { DAEMON_OUTPUT_LOG_FILE } from '../tmp-dir';
import { output } from '../../../../utilities/output';

export async function startInBackground(): Promise<ChildProcess['pid']> {
await safelyCleanUpExistingProcess();
Expand All @@ -37,17 +38,45 @@ export async function startInBackground(): Promise<ChildProcess['pid']> {
/**
* Ensure the server is actually available to connect to via IPC before resolving
*/
let attempts = 0;
return new Promise((resolve) => {
const id = setInterval(async () => {
if (await isServerAvailable()) {
clearInterval(id);
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) {
logger.error(err);
process.exit(1);
throw err;
}
}

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);
}
}

Expand All @@ -71,7 +100,7 @@ export function stop(): void {

/**
* 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.
*
* Because of the behavior of named pipes on Windows, we cannot simply treat them as a file and
Expand Down Expand Up @@ -109,18 +138,22 @@ export async function getProjectGraphFromServer(): Promise<ProjectGraph> {
const socket = connect(FULL_OS_SOCKET_PATH);

socket.on('error', (err) => {
let errorMessage: string | undefined;
let error: any;
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 somehow the file descriptor had not been released during a previous shut down.
if (existsSync(FULL_OS_SOCKET_PATH)) {
errorMessage = `Error: A server instance had not been fully shut down. Please try running the command again.`;
killSocketOrPath();
}
error = daemonProcessException(
`A server instance had not been fully shut down. Please try running the command again.`
);
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);
});

/**
Expand Down
37 changes: 23 additions & 14 deletions packages/workspace/src/core/project-graph/daemon/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,21 +268,30 @@ export async function startServer(): Promise<Server> {
if (!isWindows) {
killSocketOrPath();
}
return new Promise((resolve) => {
server.listen(FULL_OS_SOCKET_PATH, async () => {
serverLogger.log(`Started listening on: ${FULL_OS_SOCKET_PATH}`);
// this triggers the storage of the lock file hash
lockFileChanged();

if (!watcherSubscription) {
watcherSubscription = await subscribeToWorkspaceChanges(
handleWorkspaceChanges
);
serverLogger.watcherLog(`Subscribed to changes within: ${appRootPath}`);
}
return new Promise((resolve, reject) => {
try {
server.listen(FULL_OS_SOCKET_PATH, async () => {
try {
serverLogger.log(`Started listening on: ${FULL_OS_SOCKET_PATH}`);
// this triggers the storage of the lock file hash
lockFileChanged();

return resolve(server);
});
if (!watcherSubscription) {
watcherSubscription = await subscribeToWorkspaceChanges(
handleWorkspaceChanges
);
serverLogger.watcherLog(
`Subscribed to changes within: ${appRootPath}`
);
}
return resolve(server);
} catch (err) {
reject(err);
}
});
} catch (err) {
reject(err);
}
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { logger } from '@nrwl/devkit';
import { startServer } from './server';
import * as process from 'process';

(async () => {
try {
await startServer();
} catch (err) {
logger.error(err);
process.exit(1);
}
})();

0 comments on commit 1e6761e

Please sign in to comment.