refactor(ngcc): keep track of transformed files per task (#36626)
With this commit, the master process will keep track of the transformed files that each worker process is intending to write to disk. In a subsequent commit, this info 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:
parent
dff5129661
commit
ff6e93163f
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import * as cluster from 'cluster';
|
import * as cluster from 'cluster';
|
||||||
|
|
||||||
import {FileSystem} from '../../../../src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem} from '../../../../src/ngtsc/file_system';
|
||||||
import {Logger} from '../../logging/logger';
|
import {Logger} from '../../logging/logger';
|
||||||
import {PackageJsonUpdater} from '../../writing/package_json_updater';
|
import {PackageJsonUpdater} from '../../writing/package_json_updater';
|
||||||
import {AnalyzeEntryPointsFn} from '../api';
|
import {AnalyzeEntryPointsFn} from '../api';
|
||||||
|
@ -28,7 +28,7 @@ import {Deferred, sendMessageToWorker} from './utils';
|
||||||
export class ClusterMaster {
|
export class ClusterMaster {
|
||||||
private finishedDeferred = new Deferred<void>();
|
private finishedDeferred = new Deferred<void>();
|
||||||
private processingStartTime: number = -1;
|
private processingStartTime: number = -1;
|
||||||
private taskAssignments = new Map<number, Task|null>();
|
private taskAssignments = new Map<number, {task: Task, files?: AbsoluteFsPath[]}|null>();
|
||||||
private taskQueue: TaskQueue;
|
private taskQueue: TaskQueue;
|
||||||
private onTaskCompleted: TaskCompletedCallback;
|
private onTaskCompleted: TaskCompletedCallback;
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ export class ClusterMaster {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the next task on the worker.
|
// Process the next task on the worker.
|
||||||
this.taskAssignments.set(workerId, task);
|
this.taskAssignments.set(workerId, {task});
|
||||||
sendMessageToWorker(workerId, {type: 'process-task', task});
|
sendMessageToWorker(workerId, {type: 'process-task', task});
|
||||||
|
|
||||||
isWorkerAvailable = false;
|
isWorkerAvailable = false;
|
||||||
|
@ -145,13 +145,16 @@ export class ClusterMaster {
|
||||||
if (worker.exitedAfterDisconnect) return;
|
if (worker.exitedAfterDisconnect) return;
|
||||||
|
|
||||||
// The worker exited unexpectedly: Determine it's status and take an appropriate action.
|
// The worker exited unexpectedly: Determine it's status and take an appropriate action.
|
||||||
const currentTask = this.taskAssignments.get(worker.id);
|
const assignment = this.taskAssignments.get(worker.id);
|
||||||
|
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Worker #${worker.id} exited unexpectedly (code: ${code} | signal: ${signal}).\n` +
|
`Worker #${worker.id} exited unexpectedly (code: ${code} | signal: ${signal}).\n` +
|
||||||
` Current assignment: ${(currentTask == null) ? '-' : stringifyTask(currentTask)}`);
|
` Current task: ${(assignment == null) ? '-' : stringifyTask(assignment.task)}\n` +
|
||||||
|
` Current phase: ${
|
||||||
|
(assignment == null) ? '-' :
|
||||||
|
(assignment.files == null) ? 'compiling' : 'writing files'}`);
|
||||||
|
|
||||||
if (currentTask == null) {
|
if (assignment == null) {
|
||||||
// The crashed worker process was not in the middle of a task:
|
// The crashed worker process was not in the middle of a task:
|
||||||
// Just spawn another process.
|
// Just spawn another process.
|
||||||
this.logger.debug(`Spawning another worker process to replace #${worker.id}...`);
|
this.logger.debug(`Spawning another worker process to replace #${worker.id}...`);
|
||||||
|
@ -160,6 +163,8 @@ export class ClusterMaster {
|
||||||
} else {
|
} else {
|
||||||
// The crashed worker process was in the middle of a task:
|
// The crashed worker process was in the middle of a task:
|
||||||
// Impossible to know whether we can recover (without ending up with a corrupted entry-point).
|
// Impossible to know whether we can recover (without ending up with a corrupted entry-point).
|
||||||
|
// TODO: Use `assignment.files` to revert any changes and rerun the task worker.
|
||||||
|
const currentTask = assignment.task;
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Process unexpectedly crashed, while processing format property ' +
|
'Process unexpectedly crashed, while processing format property ' +
|
||||||
`${currentTask.formatProperty} for entry-point '${currentTask.entryPoint.path}'.`);
|
`${currentTask.formatProperty} for entry-point '${currentTask.entryPoint.path}'.`);
|
||||||
|
@ -207,38 +212,56 @@ export class ClusterMaster {
|
||||||
|
|
||||||
/** Handle a worker's having completed their assigned task. */
|
/** Handle a worker's having completed their assigned task. */
|
||||||
private onWorkerTaskCompleted(workerId: number, msg: TaskCompletedMessage): void {
|
private onWorkerTaskCompleted(workerId: number, msg: TaskCompletedMessage): void {
|
||||||
const task = this.taskAssignments.get(workerId) || null;
|
const assignment = this.taskAssignments.get(workerId) || null;
|
||||||
|
|
||||||
if (task === null) {
|
if (assignment === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected worker #${workerId} to have a task assigned, while handling message: ` +
|
`Expected worker #${workerId} to have a task assigned, while handling message: ` +
|
||||||
JSON.stringify(msg));
|
JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onTaskCompleted(task, msg.outcome, msg.message);
|
this.onTaskCompleted(assignment.task, msg.outcome, msg.message);
|
||||||
|
|
||||||
this.taskQueue.markAsCompleted(task);
|
this.taskQueue.markAsCompleted(assignment.task);
|
||||||
this.taskAssignments.set(workerId, null);
|
this.taskAssignments.set(workerId, null);
|
||||||
this.maybeDistributeWork();
|
this.maybeDistributeWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handle a worker's message regarding the files transformed while processing its task. */
|
/** Handle a worker's message regarding the files transformed while processing its task. */
|
||||||
private onWorkerTransformedFiles(workerId: number, msg: TransformedFilesMessage): void {
|
private onWorkerTransformedFiles(workerId: number, msg: TransformedFilesMessage): void {
|
||||||
// TODO: Do something useful with this info.
|
const assignment = this.taskAssignments.get(workerId) || null;
|
||||||
}
|
|
||||||
|
|
||||||
/** Handle a worker's request to update a `package.json` file. */
|
if (assignment === null) {
|
||||||
private onWorkerUpdatePackageJson(workerId: number, msg: UpdatePackageJsonMessage): void {
|
|
||||||
const task = this.taskAssignments.get(workerId) || null;
|
|
||||||
|
|
||||||
if (task === null) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected worker #${workerId} to have a task assigned, while handling message: ` +
|
`Expected worker #${workerId} to have a task assigned, while handling message: ` +
|
||||||
JSON.stringify(msg));
|
JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedPackageJsonPath = this.fileSystem.resolve(task.entryPoint.path, 'package.json');
|
const oldFiles = assignment.files;
|
||||||
const parsedPackageJson = task.entryPoint.packageJson;
|
const newFiles = msg.files;
|
||||||
|
|
||||||
|
if (oldFiles !== undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Worker #${workerId} reported transformed files more than once.\n` +
|
||||||
|
` Old files (${oldFiles.length}): [${oldFiles.join(', ')}]\n` +
|
||||||
|
` New files (${newFiles.length}): [${newFiles.join(', ')}]\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
assignment.files = newFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle a worker's request to update a `package.json` file. */
|
||||||
|
private onWorkerUpdatePackageJson(workerId: number, msg: UpdatePackageJsonMessage): void {
|
||||||
|
const assignment = this.taskAssignments.get(workerId) || null;
|
||||||
|
|
||||||
|
if (assignment === null) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected worker #${workerId} to have a task assigned, while handling message: ` +
|
||||||
|
JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryPoint = assignment.task.entryPoint;
|
||||||
|
const expectedPackageJsonPath = this.fileSystem.resolve(entryPoint.path, 'package.json');
|
||||||
|
|
||||||
if (expectedPackageJsonPath !== msg.packageJsonPath) {
|
if (expectedPackageJsonPath !== msg.packageJsonPath) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -254,7 +277,7 @@ export class ClusterMaster {
|
||||||
// In other words, task processing should only rely on the info that was there when the
|
// In other words, task processing should only rely on the info that was there when the
|
||||||
// file was initially parsed (during entry-point analysis) and not on the info that might
|
// file was initially parsed (during entry-point analysis) and not on the info that might
|
||||||
// be added later (during task processing).
|
// be added later (during task processing).
|
||||||
this.pkgJsonUpdater.writeChanges(msg.changes, msg.packageJsonPath, parsedPackageJson);
|
this.pkgJsonUpdater.writeChanges(msg.changes, msg.packageJsonPath, entryPoint.packageJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop all workers and stop listening on cluster events. */
|
/** Stop all workers and stop listening on cluster events. */
|
||||||
|
|
Loading…
Reference in New Issue