Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/windowsPtyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fork } from 'child_process';
import { Socket } from 'net';
import { ArgvOrCommandLine } from './types';
import { ConoutConnection } from './windowsConoutConnection';
import { EventEmitter2, IEvent } from './eventEmitter2';
import { loadNativeModule } from './utils';

let conptyNative: IConptyNative;
Expand All @@ -32,6 +33,9 @@ export class WindowsPtyAgent {
private _exitCode: number | undefined;
private _conoutSocketWorker: ConoutConnection;

private _onError = new EventEmitter2<Error>();
public get onError(): IEvent<Error> { return this._onError.event; }

private _fd: any;
private _pty: number;
private _ptyNative: IConptyNative;
Expand Down Expand Up @@ -124,8 +128,21 @@ export class WindowsPtyAgent {
const { pty, commandLine, cwd, env } = this._pendingPtyInfo;
this._pendingPtyInfo = undefined;

const connect = conptyNative.connect(pty, commandLine, cwd, env, this._useConptyDll, c => this._$onProcessExit(c));
this._innerPid = connect.pid;
try {
const connect = conptyNative.connect(pty, commandLine, cwd, env, this._useConptyDll, c => this._$onProcessExit(c));
this._innerPid = connect.pid;
} catch (err) {
// connect() runs from the conout worker's onReady callback (or its
// timeout fallback), so a throw here would otherwise surface as an
// uncaughtException with no way for the consumer to observe it.
const code = /error code: (\d+)/.exec((err as Error).message)?.[1];
this._exitCode = code ? parseInt(code, 10) : -1;
try { this._ptyNative.kill(this._pty, this._useConptyDll); } catch { /* already gone */ }
this._conoutSocketWorker.dispose();
this._inSocket.destroy();
this._outSocket.destroy();
this._onError.fire(err as Error);
}
}

public resize(cols: number, rows: number): void {
Expand Down
14 changes: 14 additions & 0 deletions src/windowsTerminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,20 @@ if (process.platform === 'win32') {
});
});

describe('connect failure', () => {
it('should emit exit instead of an uncaught exception when CreateProcessW fails', function (done) {
this.timeout(10000);
// Must exist (startProcess validates that) but not be a valid executable.
const notAnExe = path.join(__dirname, '..', 'package.json');
const term = new WindowsTerminal(notAnExe, [], { useConptyDll });
term.on('exit', (code) => {
assert.notStrictEqual(code, 0);
assert.strictEqual(term.pid, 0);
done();
});
});
});

describe('On close', () => {
it('should return process zero exit codes', function (done) {
this.timeout(10000);
Expand Down
6 changes: 6 additions & 0 deletions src/windowsTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export class WindowsTerminal extends Terminal {
// Create new termal.
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConptyDll, opt.conptyInheritCursor);
this._socket = this._agent.outSocket;
// The socket 'close' handler that drives 'exit' is only attached after
// ready_datapipe, which never fires when connect() fails.
this._agent.onError(() => {
this._close();
this.emit('exit', this._agent.exitCode);
});

// Not available until `ready` event emitted.
this._pid = this._agent.innerPid;
Expand Down
Loading