refactor(ngcc): notify master process about transformed files before writing (#36626)

With this commit, worker processes will notify the master process about
the transformed files they are about to write to disk before starting
writing them.

In a subsequent commit, this will be used to allow ngcc to recover when
a worker process crashes in the middle of processing a task.

PR Close #36626
This commit is contained in:
George Kalpakas
2020-04-29 21:28:13 +03:00
committed by Andrew Kushnir
parent e367593a26
commit dff5129661
6 changed files with 103 additions and 41 deletions

View File

@ -36,6 +36,12 @@ export interface TaskCompletedMessage extends JsonObject {
message: string|null;
}
/** A message listing the paths to transformed files about to be written to disk. */
export interface TransformedFilesMessage extends JsonObject {
type: 'transformed-files';
files: AbsoluteFsPath[];
}
/** A message requesting the update of a `package.json` file. */
export interface UpdatePackageJsonMessage extends JsonObject {
type: 'update-package-json';
@ -44,7 +50,8 @@ export interface UpdatePackageJsonMessage extends JsonObject {
}
/** The type of messages sent from cluster workers to the cluster master. */
export type MessageFromWorker = ErrorMessage|TaskCompletedMessage|UpdatePackageJsonMessage;
export type MessageFromWorker =
ErrorMessage|TaskCompletedMessage|TransformedFilesMessage|UpdatePackageJsonMessage;
/** The type of messages sent from the cluster master to cluster workers. */
export type MessageToWorker = ProcessTaskMessage;

View File

@ -17,7 +17,7 @@ import {AnalyzeEntryPointsFn} from '../api';
import {CreateTaskCompletedCallback, Task, TaskCompletedCallback, TaskQueue} from '../tasks/api';
import {stringifyTask} from '../tasks/utils';
import {MessageFromWorker, TaskCompletedMessage, UpdatePackageJsonMessage} from './api';
import {MessageFromWorker, TaskCompletedMessage, TransformedFilesMessage, UpdatePackageJsonMessage} from './api';
import {Deferred, sendMessageToWorker} from './utils';
@ -180,6 +180,8 @@ export class ClusterMaster {
throw new Error(`Error on worker #${workerId}: ${msg.error}`);
case 'task-completed':
return this.onWorkerTaskCompleted(workerId, msg);
case 'transformed-files':
return this.onWorkerTransformedFiles(workerId, msg);
case 'update-package-json':
return this.onWorkerUpdatePackageJson(workerId, msg);
default:
@ -220,6 +222,11 @@ export class ClusterMaster {
this.maybeDistributeWork();
}
/** Handle a worker's message regarding the files transformed while processing its task. */
private onWorkerTransformedFiles(workerId: number, msg: TransformedFilesMessage): void {
// TODO: Do something useful with this info.
}
/** Handle a worker's request to update a `package.json` file. */
private onWorkerUpdatePackageJson(workerId: number, msg: UpdatePackageJsonMessage): void {
const task = this.taskAssignments.get(workerId) || null;

View File

@ -44,18 +44,21 @@ export class Deferred<T> {
* (This function should be invoked from cluster workers only.)
*
* @param msg The message to send to the cluster master.
* @return A promise that is resolved once the message has been sent.
*/
export const sendMessageToMaster = (msg: MessageFromWorker): void => {
export const sendMessageToMaster = (msg: MessageFromWorker): Promise<void> => {
if (cluster.isMaster) {
throw new Error('Unable to send message to the master process: Already on the master process.');
}
if (process.send === undefined) {
// Theoretically, this should never happen on a worker process.
throw new Error('Unable to send message to the master process: Missing `process.send()`.');
}
return new Promise((resolve, reject) => {
if (process.send === undefined) {
// Theoretically, this should never happen on a worker process.
throw new Error('Unable to send message to the master process: Missing `process.send()`.');
}
process.send(msg);
process.send(msg, (err: Error|null) => (err === null) ? resolve() : reject(err));
});
};
/**
@ -64,8 +67,9 @@ export const sendMessageToMaster = (msg: MessageFromWorker): void => {
*
* @param workerId The ID of the recipient worker.
* @param msg The message to send to the worker.
* @return A promise that is resolved once the message has been sent.
*/
export const sendMessageToWorker = (workerId: number, msg: MessageToWorker): void => {
export const sendMessageToWorker = (workerId: number, msg: MessageToWorker): Promise<void> => {
if (!cluster.isMaster) {
throw new Error('Unable to send message to worker process: Sender is not the master process.');
}
@ -77,5 +81,7 @@ export const sendMessageToWorker = (workerId: number, msg: MessageToWorker): voi
'Unable to send message to worker process: Recipient does not exist or has disconnected.');
}
worker.send(msg);
return new Promise((resolve, reject) => {
worker.send(msg, (err: Error|null) => (err === null) ? resolve() : reject(err));
});
};

View File

@ -62,7 +62,10 @@ export async function startWorker(logger: Logger, createCompileFn: CreateCompile
}
const compile = createCompileFn(
() => {},
transformedFiles => sendMessageToMaster({
type: 'transformed-files',
files: transformedFiles.map(f => f.path),
}),
(_task, outcome, message) => sendMessageToMaster({type: 'task-completed', outcome, message}));
@ -79,7 +82,7 @@ export async function startWorker(logger: Logger, createCompileFn: CreateCompile
`[Worker #${cluster.worker.id}] Invalid message received: ${JSON.stringify(msg)}`);
}
} catch (err) {
sendMessageToMaster({
await sendMessageToMaster({
type: 'error',
error: (err instanceof Error) ? (err.stack || err.message) : err,
});
@ -88,4 +91,4 @@ export async function startWorker(logger: Logger, createCompileFn: CreateCompile
// Return a promise that is never resolved.
return new Promise(() => undefined);
}
}