Fix terminal PTY teardown races
This commit is contained in:
@@ -32,13 +32,9 @@ afterEach(async () => {
|
|||||||
const terminalsByCwd = await Promise.all(
|
const terminalsByCwd = await Promise.all(
|
||||||
manager.listDirectories().map((cwd) => manager.getTerminals(cwd)),
|
manager.listDirectories().map((cwd) => manager.getTerminals(cwd)),
|
||||||
);
|
);
|
||||||
await Promise.all(
|
for (const terminal of terminalsByCwd.flat()) {
|
||||||
terminalsByCwd
|
await manager.killTerminalAndWait(terminal.id);
|
||||||
.flat()
|
}
|
||||||
.map((terminal) =>
|
|
||||||
manager.killTerminalAndWait(terminal.id, { gracefulTimeoutMs: 50, forceTimeoutMs: 50 }),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
manager.killAll();
|
manager.killAll();
|
||||||
}
|
}
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
@@ -306,13 +302,12 @@ it("kills all terminals and clears state", async () => {
|
|||||||
it("emits cwd snapshots when terminals are created", async () => {
|
it("emits cwd snapshots when terminals are created", async () => {
|
||||||
manager = createTerminalManager();
|
manager = createTerminalManager();
|
||||||
const cwd = realpathSync(tmpdir());
|
const cwd = realpathSync(tmpdir());
|
||||||
const snapshots: Array<{ cwd: string; terminals: Array<{ name: string; title?: string }> }> = [];
|
const snapshots: Array<{ cwd: string; terminals: Array<{ name: string }> }> = [];
|
||||||
const unsubscribe = manager.subscribeTerminalsChanged((input) => {
|
const unsubscribe = manager.subscribeTerminalsChanged((input) => {
|
||||||
snapshots.push({
|
snapshots.push({
|
||||||
cwd: input.cwd,
|
cwd: input.cwd,
|
||||||
terminals: input.terminals.map((terminal) => ({
|
terminals: input.terminals.map((terminal) => ({
|
||||||
name: terminal.name,
|
name: terminal.name,
|
||||||
...(terminal.title ? { title: terminal.title } : {}),
|
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const temporaryDirs: string[] = [];
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
session.kill();
|
await session.killAndWait();
|
||||||
}
|
}
|
||||||
sessions.length = 0;
|
sessions.length = 0;
|
||||||
while (temporaryDirs.length > 0) {
|
while (temporaryDirs.length > 0) {
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ interface EnsureNodePtySpawnHelperExecutableOptions {
|
|||||||
force?: boolean;
|
force?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WindowsPtyProcessReadiness {
|
||||||
|
_agent?: { innerPid?: number };
|
||||||
|
}
|
||||||
|
|
||||||
function resolveNodePtyPackageRoot(): string | null {
|
function resolveNodePtyPackageRoot(): string | null {
|
||||||
try {
|
try {
|
||||||
const packageJsonPath = require.resolve("node-pty/package.json");
|
const packageJsonPath = require.resolve("node-pty/package.json");
|
||||||
@@ -754,6 +758,22 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
|||||||
disposeResources();
|
disposeResources();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function waitForPtyProcessStart(): Promise<void> {
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const started = (): boolean => {
|
||||||
|
const windowsPtyProcess = ptyProcess as unknown as WindowsPtyProcessReadiness;
|
||||||
|
return ptyProcess.pid > 0 || (windowsPtyProcess._agent?.innerPid ?? 0) > 0 || processExited;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deadline = Date.now() + 5000;
|
||||||
|
while (!started() && Date.now() < deadline) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getState(): TerminalState {
|
function getState(): TerminalState {
|
||||||
return {
|
return {
|
||||||
rows: terminal.rows,
|
rows: terminal.rows,
|
||||||
@@ -917,10 +937,16 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
|||||||
function kill(): void {
|
function kill(): void {
|
||||||
if (!killed) {
|
if (!killed) {
|
||||||
killed = true;
|
killed = true;
|
||||||
killPtyProcess();
|
if (!processExited) {
|
||||||
|
killPtyProcess();
|
||||||
|
}
|
||||||
emitExit(buildExitInfo());
|
emitExit(buildExitInfo());
|
||||||
}
|
}
|
||||||
disposeResources();
|
if (processExited) {
|
||||||
|
disposeResources();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void waitForProcessExit(1000).finally(disposeResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
function killPtyProcess(signal?: NodeJS.Signals): void {
|
function killPtyProcess(signal?: NodeJS.Signals): void {
|
||||||
@@ -987,6 +1013,8 @@ export async function createTerminal(options: CreateTerminalOptions): Promise<Te
|
|||||||
kill();
|
kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await waitForPtyProcessStart();
|
||||||
|
|
||||||
// Small delay to let shell initialize
|
// Small delay to let shell initialize
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user