mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 10:57:07 +00:00
fix(server): report and remove a plugin the runtime terminated (#9285)
When a plugin sent an invalid IPC message, the runtime terminated it by disconnecting the IPC channel and then killing the process. Node never emits "close" for a child whose channel the parent disconnected, and "close" is where the runtime handles an exited plugin, so the process died but the plugin stayed in the catalog, its session stayed open, nothing reported the exit, and calls to it failed with "does not contribute RPC" instead of "Plugin is not available". terminatePluginChild() now only kills the process; the channel closes with it, and "close" fires as for any other exit. Co-authored-by: Brandon Elliott <923787+Brandon168@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Brandon Elliott
Claude Opus 5.5
parent
83f4ef9909
commit
ee6a2bba77
@@ -1936,6 +1936,38 @@ export default function contribute(plugin: any) {
|
||||
);
|
||||
});
|
||||
|
||||
it("reports and removes a plugin it terminated for an invalid message", async () => {
|
||||
const directory = await createPlugin(
|
||||
"invalid-message",
|
||||
`export default function contribute(plugin: unknown) {
|
||||
void plugin;
|
||||
setTimeout(() => process.send?.({ type: "not-a-plugin-message" }), 20);
|
||||
return () => undefined;
|
||||
}`,
|
||||
);
|
||||
const sessions = createTrackedSessionHost();
|
||||
const runtime = createTestRuntime({ sessionHost: sessions.host });
|
||||
const exited = new Promise<string>((resolve) => {
|
||||
runtime.subscribe((pluginId, error) => {
|
||||
if (pluginId === "invalid-message" && error) resolve(error);
|
||||
});
|
||||
});
|
||||
await runtime.startPlugin("invalid-message", directory);
|
||||
|
||||
// The runtime kills the process; it must then treat it like any other exited plugin.
|
||||
await expect(exited).resolves.toBe("Plugin process exited: invalid-message");
|
||||
expect(sessions.active.size).toBe(0);
|
||||
expect(runtime.catalog()).toEqual([]);
|
||||
expect(
|
||||
pluginMessages(runtime, "invalid-message").some((message) =>
|
||||
message.includes("sent an invalid message"),
|
||||
),
|
||||
).toBe(true);
|
||||
await expect(runtime.invoke("invalid-message", "anything", {})).rejects.toThrow(
|
||||
"Plugin is not available",
|
||||
);
|
||||
});
|
||||
|
||||
it("re-attaches a plugin session when the daemon closes the socket under a live child", async () => {
|
||||
const directory = await createPlugin(
|
||||
"reattaching",
|
||||
|
||||
@@ -247,8 +247,9 @@ function spawnPluginChild(): PluginChild {
|
||||
}) as PluginChild;
|
||||
}
|
||||
|
||||
// Kill only. Node never emits "close" for a child whose IPC channel the parent disconnected, and
|
||||
// "close" is how the runtime learns that a plugin exited; the channel closes with the process.
|
||||
function terminatePluginChild(child: PluginChild): void {
|
||||
if (child.connected) child.disconnect();
|
||||
if (!child.killed) child.kill();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user