Skip to content

Commit dab79e5

Browse files
AgentEnderFrozenPandaz
authored andcommitted
fix(core): reject pending promises directly when plugin worker exits unexpectedly (#34588)
When a plugin worker process exits unexpectedly, the exit handler previously sent synthetic `loadResult` messages to all pending response handlers. If any handler was waiting for a different result type (e.g. `createNodesResult`), the type validation would reject with a confusing "Expected createNodesResult, got loadResult" error instead of surfacing the actual cause. Split response handlers into `onMessage` / `onError` callbacks so the exit handler can reject each pending promise directly with a clear "Plugin worker exited unexpectedly" error. Also use unique transaction IDs for `load` messages (via `generateTxId`) to avoid potential handler overwrites during worker restarts. Fixes #34564 (cherry picked from commit 1ecf0fb)
1 parent 372d1d3 commit dab79e5

1 file changed

Lines changed: 49 additions & 35 deletions

File tree

packages/nx/src/project-graph/plugins/isolation/isolated-plugin.ts

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ export class IsolatedPlugin implements LoadedNxPlugin {
8989
// Typed response handlers keyed by transaction ID
9090
private responseHandlers = new Map<
9191
string,
92-
(msg: PluginWorkerResult) => void
92+
{
93+
onMessage: (msg: PluginWorkerResult) => void;
94+
onError: (error: Error) => void;
95+
}
9396
>();
9497

9598
// Configuration for restart
@@ -159,15 +162,11 @@ export class IsolatedPlugin implements LoadedNxPlugin {
159162
this.worker.stderr.unpipe(process.stderr);
160163
}
161164
// Reject all pending requests
162-
for (const handler of this.responseHandlers.values()) {
163-
handler({
164-
type: 'loadResult',
165-
tx: '',
166-
payload: {
167-
success: false,
168-
error: new Error(`Plugin worker ${this.name} exited unexpectedly.`),
169-
},
170-
} as PluginWorkerResult);
165+
const error = new Error(
166+
`Plugin worker "${this.name}" exited unexpectedly.`
167+
);
168+
for (const { onError } of this.responseHandlers.values()) {
169+
onError(error);
171170
}
172171
this.responseHandlers.clear();
173172
};
@@ -204,16 +203,16 @@ export class IsolatedPlugin implements LoadedNxPlugin {
204203
if (!isPluginWorkerResult(message)) {
205204
return;
206205
}
207-
const handler = this.responseHandlers.get(message.tx);
208-
if (handler) {
206+
const pending = this.responseHandlers.get(message.tx);
207+
if (pending) {
209208
this.responseHandlers.delete(message.tx);
210-
handler(message);
209+
pending.onMessage(message);
211210
}
212211
};
213212

214213
private sendLoadMessage(): Promise<LoadResultPayload> {
215214
return new Promise((resolve, reject) => {
216-
const tx = 'load';
215+
const tx = this.generateTxId('load');
217216

218217
const timeout = setTimeout(() => {
219218
this.responseHandlers.delete(tx);
@@ -226,19 +225,25 @@ export class IsolatedPlugin implements LoadedNxPlugin {
226225
);
227226
}, MAX_MESSAGE_WAIT);
228227

229-
this.responseHandlers.set(tx, (msg) => {
230-
clearTimeout(timeout);
231-
if (msg.type !== 'loadResult') {
232-
reject(new Error(`Expected loadResult, got ${msg.type}`));
233-
return;
234-
}
235-
const payload = msg.payload as PluginWorkerLoadResult['payload'];
236-
if (payload.success === false) {
237-
reject(payload.error);
238-
} else {
239-
this._alive = true;
240-
resolve(payload);
241-
}
228+
this.responseHandlers.set(tx, {
229+
onMessage: (msg) => {
230+
clearTimeout(timeout);
231+
if (msg.type !== 'loadResult') {
232+
reject(new Error(`Expected loadResult, got ${msg.type}`));
233+
return;
234+
}
235+
const payload = msg.payload as PluginWorkerLoadResult['payload'];
236+
if (payload.success === false) {
237+
reject(payload.error);
238+
} else {
239+
this._alive = true;
240+
resolve(payload);
241+
}
242+
},
243+
onError: (error) => {
244+
clearTimeout(timeout);
245+
reject(error);
246+
},
242247
});
243248

244249
sendMessageOverSocket(this.socket, {
@@ -387,16 +392,25 @@ export class IsolatedPlugin implements LoadedNxPlugin {
387392
);
388393
}, MAX_MESSAGE_WAIT);
389394

390-
this.responseHandlers.set(tx, (msg) => {
391-
clearTimeout(timeout);
392-
this.pendingCount--;
395+
this.responseHandlers.set(tx, {
396+
onMessage: (msg) => {
397+
clearTimeout(timeout);
398+
this.pendingCount--;
393399

394-
if (msg.type !== expectedResultType) {
395-
reject(new Error(`Expected ${expectedResultType}, got ${msg.type}`));
396-
return;
397-
}
400+
if (msg.type !== expectedResultType) {
401+
reject(
402+
new Error(`Expected ${expectedResultType}, got ${msg.type}`)
403+
);
404+
return;
405+
}
398406

399-
resolve(msg.payload as MessageResult<TType>['payload']);
407+
resolve(msg.payload as MessageResult<TType>['payload']);
408+
},
409+
onError: (error) => {
410+
clearTimeout(timeout);
411+
this.pendingCount--;
412+
reject(error);
413+
},
400414
});
401415

402416
sendMessageOverSocket(this.socket, {

0 commit comments

Comments
 (0)