node-pty 0.10.1 → 0.11.0-beta12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +8 -8
  2. package/binding.gyp +45 -27
  3. package/lib/eventEmitter2.js +2 -1
  4. package/lib/eventEmitter2.js.map +1 -1
  5. package/lib/index.js +1 -0
  6. package/lib/index.js.map +1 -1
  7. package/lib/shared/conout.js +11 -0
  8. package/lib/shared/conout.js.map +1 -0
  9. package/lib/terminal.js +9 -6
  10. package/lib/terminal.js.map +1 -1
  11. package/lib/terminal.test.js +3 -3
  12. package/lib/testUtils.test.js +1 -0
  13. package/lib/testUtils.test.js.map +1 -1
  14. package/lib/unixTerminal.js +13 -4
  15. package/lib/unixTerminal.js.map +1 -1
  16. package/lib/unixTerminal.test.js +31 -0
  17. package/lib/unixTerminal.test.js.map +1 -1
  18. package/lib/utils.js +1 -0
  19. package/lib/utils.js.map +1 -1
  20. package/lib/windowsConoutConnection.js +122 -0
  21. package/lib/windowsConoutConnection.js.map +1 -0
  22. package/lib/windowsPtyAgent.js +16 -15
  23. package/lib/windowsPtyAgent.js.map +1 -1
  24. package/lib/windowsTerminal.js +4 -3
  25. package/lib/windowsTerminal.js.map +1 -1
  26. package/lib/worker/conoutSocketWorker.js +19 -0
  27. package/lib/worker/conoutSocketWorker.js.map +1 -0
  28. package/package.json +6 -6
  29. package/scripts/post-install.js +1 -0
  30. package/src/interfaces.ts +3 -4
  31. package/src/native.d.ts +1 -1
  32. package/src/shared/conout.ts +15 -0
  33. package/src/terminal.ts +3 -1
  34. package/src/unix/comms.h +5 -0
  35. package/src/unix/pty.cc +148 -175
  36. package/src/unix/spawn-helper.cc +70 -0
  37. package/src/unixTerminal.test.ts +28 -0
  38. package/src/unixTerminal.ts +11 -2
  39. package/src/win/conpty.cc +34 -22
  40. package/src/win/path_util.cc +1 -1
  41. package/src/windowsConoutConnection.ts +79 -0
  42. package/src/windowsPtyAgent.ts +10 -10
  43. package/src/worker/conoutSocketWorker.ts +18 -0
  44. package/typings/node-pty.d.ts +9 -3
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Copyright (c) 2020, Microsoft Corporation (MIT License).
3
+ */
4
+
5
+ import { Worker } from 'worker_threads';
6
+ import { Socket } from 'net';
7
+ import { IDisposable } from './types';
8
+ import { IWorkerData, ConoutWorkerMessage, getWorkerPipeName } from './shared/conout';
9
+ import { join } from 'path';
10
+ import { IEvent, EventEmitter2 } from './eventEmitter2';
11
+
12
+ /**
13
+ * The amount of time to wait for additional data after the conpty shell process has exited before
14
+ * shutting down the worker and sockets. The timer will be reset if a new data event comes in after
15
+ * the timer has started.
16
+ */
17
+ const FLUSH_DATA_INTERVAL = 1000;
18
+
19
+ /**
20
+ * Connects to and manages the lifecycle of the conout socket. This socket must be drained on
21
+ * another thread in order to avoid deadlocks where Conpty waits for the out socket to drain
22
+ * when `ClosePseudoConsole` is called. This happens when data is being written to the terminal when
23
+ * the pty is closed.
24
+ *
25
+ * See also:
26
+ * - https://github.com/microsoft/node-pty/issues/375
27
+ * - https://github.com/microsoft/vscode/issues/76548
28
+ * - https://github.com/microsoft/terminal/issues/1810
29
+ * - https://docs.microsoft.com/en-us/windows/console/closepseudoconsole
30
+ */
31
+ export class ConoutConnection implements IDisposable {
32
+ private _worker: Worker;
33
+ private _drainTimeout: NodeJS.Timeout;
34
+ private _isDisposed: boolean = false;
35
+
36
+ private _onReady = new EventEmitter2<void>();
37
+ public get onReady(): IEvent<void> { return this._onReady.event; }
38
+
39
+ constructor(
40
+ private _conoutPipeName: string
41
+ ) {
42
+ const workerData: IWorkerData = { conoutPipeName: _conoutPipeName };
43
+ const scriptPath = __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked');
44
+ this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.js'), { workerData });
45
+ this._worker.on('message', (message: ConoutWorkerMessage) => {
46
+ switch (message) {
47
+ case ConoutWorkerMessage.READY:
48
+ this._onReady.fire();
49
+ return;
50
+ default:
51
+ console.warn('Unexpected ConoutWorkerMessage', message);
52
+ }
53
+ });
54
+ }
55
+
56
+ dispose(): void {
57
+ if (this._isDisposed) {
58
+ return;
59
+ }
60
+ this._isDisposed = true;
61
+ // Drain all data from the socket before closing
62
+ this._drainDataAndClose();
63
+ }
64
+
65
+ connectSocket(socket: Socket): void {
66
+ socket.connect(getWorkerPipeName(this._conoutPipeName));
67
+ }
68
+
69
+ private _drainDataAndClose(): void {
70
+ if (this._drainTimeout) {
71
+ clearTimeout(this._drainTimeout);
72
+ }
73
+ this._drainTimeout = setTimeout(() => this._destroySocket(), FLUSH_DATA_INTERVAL);
74
+ }
75
+
76
+ private async _destroySocket(): Promise<void> {
77
+ await this._worker.terminate();
78
+ }
79
+ }
@@ -10,6 +10,7 @@ import * as path from 'path';
10
10
  import { Socket } from 'net';
11
11
  import { ArgvOrCommandLine } from './types';
12
12
  import { fork } from 'child_process';
13
+ import { ConoutConnection } from './windowsConoutConnection';
13
14
 
14
15
  let conptyNative: IConptyNative;
15
16
  let winptyNative: IWinptyNative;
@@ -19,7 +20,7 @@ let winptyNative: IWinptyNative;
19
20
  * shutting down the socket. The timer will be reset if a new data event comes in after the timer
20
21
  * has started.
21
22
  */
22
- const FLUSH_DATA_INTERVAL = 20;
23
+ const FLUSH_DATA_INTERVAL = 1000;
23
24
 
24
25
  /**
25
26
  * This agent sits between the WindowsTerminal class and provides a common interface for both conpty
@@ -33,6 +34,7 @@ export class WindowsPtyAgent {
33
34
  private _innerPidHandle: number;
34
35
  private _closeTimeout: NodeJS.Timer;
35
36
  private _exitCode: number | undefined;
37
+ private _conoutSocketWorker: ConoutConnection;
36
38
 
37
39
  private _fd: any;
38
40
  private _pty: number;
@@ -116,10 +118,12 @@ export class WindowsPtyAgent {
116
118
  // Create terminal pipe IPC channel and forward to a local unix socket.
117
119
  this._outSocket = new Socket();
118
120
  this._outSocket.setEncoding('utf8');
119
- this._outSocket.connect(term.conout, () => {
120
- // TODO: Emit event on agent instead of socket?
121
-
122
- // Emit ready event.
121
+ // The conout socket must be ready out on another thread to avoid deadlocks
122
+ this._conoutSocketWorker = new ConoutConnection(term.conout);
123
+ this._conoutSocketWorker.onReady(() => {
124
+ this._conoutSocketWorker.connectSocket(this._outSocket);
125
+ });
126
+ this._outSocket.on('connect', () => {
123
127
  this._outSocket.emit('ready_datapipe');
124
128
  });
125
129
 
@@ -130,7 +134,6 @@ export class WindowsPtyAgent {
130
134
  writable: true
131
135
  });
132
136
  this._inSocket.setEncoding('utf8');
133
- // TODO: Wait for ready event?
134
137
 
135
138
  if (this._useConpty) {
136
139
  const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, c => this._$onProcessExit(c));
@@ -151,9 +154,7 @@ export class WindowsPtyAgent {
151
154
 
152
155
  public kill(): void {
153
156
  this._inSocket.readable = false;
154
- this._inSocket.writable = false;
155
157
  this._outSocket.readable = false;
156
- this._outSocket.writable = false;
157
158
  // Tell the agent to kill the pty, this releases handles to the process
158
159
  if (this._useConpty) {
159
160
  this._getConsoleProcessList().then(consoleProcessList => {
@@ -183,6 +184,7 @@ export class WindowsPtyAgent {
183
184
  }
184
185
  });
185
186
  }
187
+ this._conoutSocketWorker.dispose();
186
188
  }
187
189
 
188
190
  private _getConsoleProcessList(): Promise<number[]> {
@@ -238,9 +240,7 @@ export class WindowsPtyAgent {
238
240
 
239
241
  private _cleanUpProcess(): void {
240
242
  this._inSocket.readable = false;
241
- this._inSocket.writable = false;
242
243
  this._outSocket.readable = false;
243
- this._outSocket.writable = false;
244
244
  this._outSocket.destroy();
245
245
  }
246
246
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) 2020, Microsoft Corporation (MIT License).
3
+ */
4
+
5
+ import { parentPort, workerData } from 'worker_threads';
6
+ import { Socket, createServer } from 'net';
7
+ import { ConoutWorkerMessage, IWorkerData, getWorkerPipeName } from '../shared/conout';
8
+
9
+ const conoutPipeName = (workerData as IWorkerData).conoutPipeName;
10
+ const conoutSocket = new Socket();
11
+ conoutSocket.setEncoding('utf8');
12
+ conoutSocket.connect(conoutPipeName, () => {
13
+ const server = createServer(workerSocket => {
14
+ conoutSocket.pipe(workerSocket);
15
+ });
16
+ server.listen(getWorkerPipeName(conoutPipeName));
17
+ parentPort.postMessage(ConoutWorkerMessage.READY);
18
+ });
@@ -42,7 +42,7 @@ declare module 'node-pty' {
42
42
  /**
43
43
  * Environment to be set for the child program.
44
44
  */
45
- env?: { [key: string]: string };
45
+ env?: { [key: string]: string | undefined };
46
46
 
47
47
  /**
48
48
  * String encoding of the underlying pty.
@@ -78,11 +78,17 @@ declare module 'node-pty' {
78
78
 
79
79
  export interface IPtyForkOptions extends IBasePtyForkOptions {
80
80
  /**
81
- * Security warning: use this option with great caution, as opened file descriptors
82
- * with higher privileges might leak to the child program.
81
+ * Security warning: use this option with great caution unless `closeFDs` is also set,
82
+ * as opened file descriptors with higher privileges might leak to the child program.
83
83
  */
84
84
  uid?: number;
85
85
  gid?: number;
86
+
87
+ /**
88
+ * Close all open file after spawning the child process (UNIX only).
89
+ * Carries a performance penalty on Linux.
90
+ */
91
+ closeFDs?: boolean;
86
92
  }
87
93
 
88
94
  export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {