Fix terminal stream stalls after resize

This commit is contained in:
Mohamed Boudra
2026-03-23 15:34:07 +07:00
parent b873523eab
commit 956828fa55
3 changed files with 40 additions and 34 deletions

View File

@@ -40,10 +40,6 @@ export type TerminalStreamControllerOptions = {
const TERMINAL_EXITED_ERROR = "Terminal exited";
function logTerminalStreamController(event: string, details: Record<string, unknown>): void {
console.log("[terminal-stream-controller]", event, details);
}
export class TerminalStreamController {
private readonly decoder = new TextDecoder();
private readonly unsubscribeStreamEvents: () => void;
@@ -69,19 +65,10 @@ export class TerminalStreamController {
setTerminal(input: { terminalId: string | null }): void {
if (this.disposed || input.terminalId === this.terminalId) {
logTerminalStreamController("set_terminal_noop", {
disposed: this.disposed,
currentTerminalId: this.terminalId,
requestedTerminalId: input.terminalId,
});
return;
}
const nextTerminalId = input.terminalId;
const previousTerminalId = this.terminalId;
logTerminalStreamController("set_terminal", {
previousTerminalId,
nextTerminalId,
});
this.terminalId = nextTerminalId;
this.decoder.decode();
if (previousTerminalId) {
@@ -95,11 +82,6 @@ export class TerminalStreamController {
void this.options.client
.subscribeTerminal(nextTerminalId)
.then((payload) => {
logTerminalStreamController("subscribe_resolved", {
nextTerminalId,
payloadTerminalId: payload.terminalId,
error: payload.error ?? null,
});
if (this.disposed || this.terminalId !== nextTerminalId) {
return;
}
@@ -113,10 +95,6 @@ export class TerminalStreamController {
return;
}
const preferredSize = this.options.getPreferredSize();
logTerminalStreamController("preferred_size_after_subscribe", {
nextTerminalId,
preferredSize,
});
if (preferredSize) {
this.options.client.sendTerminalInput(nextTerminalId, {
type: "resize",
@@ -131,10 +109,6 @@ export class TerminalStreamController {
});
})
.catch((error: unknown) => {
logTerminalStreamController("subscribe_rejected", {
nextTerminalId,
error: error instanceof Error ? error.message : "Unable to subscribe to terminal",
});
if (this.disposed || this.terminalId !== nextTerminalId) {
return;
}
@@ -148,10 +122,6 @@ export class TerminalStreamController {
}
handleTerminalExit(input: { terminalId: string }): void {
logTerminalStreamController("terminal_exit", {
currentTerminalId: this.terminalId,
exitedTerminalId: input.terminalId,
});
if (this.disposed || input.terminalId !== this.terminalId) {
return;
}
@@ -168,9 +138,6 @@ export class TerminalStreamController {
if (this.disposed) {
return;
}
logTerminalStreamController("dispose", {
terminalId: this.terminalId,
});
this.disposed = true;
this.decoder.decode();
const terminalId = this.terminalId;

View File

@@ -626,6 +626,45 @@ describe("daemon E2E terminal", () => {
rmSync(cwd, { recursive: true, force: true });
}, 30000);
test("resize does not stall streamed output for an attached client", async () => {
const cwd = tmpCwd();
const created = await ctx.client.createTerminal(cwd);
const terminalId = created.terminal!.id;
const secondClient = await connectClient(
ctx.daemon.port,
`terminal-resize-stream-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
try {
await ctx.client.subscribeTerminal(terminalId);
await secondClient.subscribeTerminal(terminalId);
secondClient.sendTerminalInput(terminalId, {
type: "resize",
rows: 12,
cols: 40,
});
const firstOutput = waitForTerminalOutput(ctx.client, terminalId, (text) =>
text.includes("after-resize-stream"),
);
const secondOutput = waitForTerminalOutput(secondClient, terminalId, (text) =>
text.includes("after-resize-stream"),
);
secondClient.sendTerminalInput(terminalId, {
type: "input",
data: "printf 'after-resize-stream\\n'\r",
});
expect(await firstOutput).toContain("after-resize-stream");
expect(await secondOutput).toContain("after-resize-stream");
} finally {
await secondClient.close();
rmSync(cwd, { recursive: true, force: true });
}
}, 30000);
test("terminal exits notify the client", async () => {
const cwd = tmpCwd();
const created = await ctx.client.createTerminal(cwd);

View File

@@ -1817,7 +1817,6 @@ export class Session {
if (!resize) {
return;
}
activeStream.needsSnapshot = true;
terminal.send({ type: "resize", rows: resize.rows, cols: resize.cols });
return;
}
@@ -7859,6 +7858,7 @@ export class Session {
this.markAllActiveTerminalStreamsForSnapshot();
}
});
this.trySendTerminalSnapshot(activeStream);
return slot;
}