Skip to content

Commit a0ce44f

Browse files
authored
Merge pull request #835 from lhecker/dev/lhecker/fs-write-fix
Fix string slicing for fs.write
2 parents c2574b3 + 9869586 commit a0ce44f

File tree

1 file changed

+47
-42
lines changed

1 file changed

+47
-42
lines changed

src/unixTerminal.ts

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ const DEFAULT_FILE = 'sh';
2323
const DEFAULT_NAME = 'xterm';
2424
const DESTROY_SOCKET_TIMEOUT_MS = 200;
2525

26+
interface IWriteTask {
27+
/** The buffer being written. */
28+
data: Buffer;
29+
/** The current offset of not yet written data. */
30+
offset: number;
31+
}
32+
2633
export class UnixTerminal extends Terminal {
2734
protected _fd: number;
2835
protected _pty: string;
@@ -36,9 +43,9 @@ export class UnixTerminal extends Terminal {
3643
private _boundClose: boolean = false;
3744
private _emittedClose: boolean = false;
3845

39-
private readonly _writeQueue: (string | Buffer)[] = [];
40-
private _writeInProgress: boolean = false;
46+
private readonly _writeQueue: IWriteTask[] = [];
4147
private _writeTimeout: NodeJS.Timeout | undefined;
48+
private _encoding?: BufferEncoding = undefined;
4249

4350
private _master: net.Socket | undefined;
4451
private _slave: net.Socket | undefined;
@@ -76,6 +83,7 @@ export class UnixTerminal extends Terminal {
7683
const parsedEnv = this._parseEnv(env);
7784

7885
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
86+
this._encoding = (encoding || undefined) as BufferEncoding;
7987

8088
const onexit = (code: number, signal: number): void => {
8189
// XXX Sometimes a data event is emitted after exit. Wait til socket is
@@ -167,58 +175,54 @@ export class UnixTerminal extends Terminal {
167175
this._forwardEvents();
168176
}
169177

170-
protected _write(data: string | Buffer): void {
178+
protected _write(str: string | Buffer): void {
171179
// Writes are put in a queue and processed asynchronously in order to handle
172180
// backpressure from the kernel buffer.
173-
this._writeQueue.push(data);
174-
if (this._writeInProgress) {
175-
return;
181+
const data = typeof str === 'string'
182+
? Buffer.from(str, this._encoding)
183+
: Buffer.from(str);
184+
185+
if (data.byteLength !== 0) {
186+
this._writeQueue.push({ data, offset: 0 });
187+
if (this._writeQueue.length === 1) {
188+
this._processWriteQueue();
189+
}
176190
}
177-
this._writeInProgress = true;
178-
this._processWriteQueue();
179191
}
180192

181-
private async _processWriteQueue(): Promise<void> {
182-
const data = this._writeQueue.shift();
183-
if (!data) {
184-
this._writeInProgress = false;
193+
private _processWriteQueue(): void {
194+
if (this._writeQueue.length === 0) {
185195
return;
186196
}
187197

198+
const task = this._writeQueue[0];
199+
188200
// Write to the underlying file descriptor and handle it directly, rather
189201
// than using the `net.Socket`/`tty.WriteStream` wrappers which swallow the
190202
// errors and cause the thread to block indefinitely.
191-
fs.write(this._fd, data, (err, written) => {
192-
// Requeue any partial writes
193-
if (written < data.length) {
194-
this._writeQueue.unshift(data.slice(written));
195-
}
196-
203+
fs.write(this._fd, task.data, task.offset, (err, written) => {
197204
if (err) {
198-
const errno = (err as any).errno;
199-
switch (errno) {
200-
case -35: // EAGAIN (macOS)
201-
case -11: // EAGAIN (Linux)
202-
// This error appears to get swallowed and translated into
203-
// `ERR_SYSTEM_ERROR` when using tty.WriteStream and not fs.write
204-
// directly.
205-
// This can happen during a regular partial write, but the most
206-
// reliable way to test this is to run `sleep 10` in the shell and
207-
// paste enough data to fill the kernel-level buffer. Once the sleep
208-
// ends, the pty should accept the data again. Re-process after a
209-
// short break.
210-
this._writeTimeout = setTimeout(() => this._processWriteQueue(), 5);
211-
return;
212-
case -5: // EIO
213-
case -32: // EPIPE
214-
// Stop processing writes immediately as the pty is closed.
215-
this._writeInProgress = false;
216-
return;
217-
default:
218-
console.error('Unhandled pty write error', errno, err);
219-
// Fall through as it's important to finish processing the queue
220-
break;
205+
if ((err as any).code === 'EAGAIN') {
206+
// This error appears to get swallowed and translated into
207+
// `ERR_SYSTEM_ERROR` when using tty.WriteStream and not fs.write
208+
// directly.
209+
// The most reliable way to test this is to run `sleep 10` in the
210+
// shell and paste enough data to fill the kernel-level buffer. Once
211+
// the sleep ends, the pty should accept the data again. Re-process
212+
// after a short break.
213+
// TODO: https://github.com/microsoft/node-pty/issues/833#issuecomment-3665099159
214+
this._writeTimeout = setTimeout(() => this._processWriteQueue(), 5);
215+
} else {
216+
// Stop processing immediately on unexpected error and log
217+
this._writeQueue.length = 0;
218+
console.error('Unhandled pty write error', err);
221219
}
220+
return;
221+
}
222+
223+
task.offset += written;
224+
if (task.offset >= task.data.byteLength) {
225+
this._writeQueue.shift();
222226
}
223227

224228
// Using `setImmediate` here appears to corrupt the data, this may be what
@@ -229,6 +233,7 @@ export class UnixTerminal extends Terminal {
229233
// anyway, so use a short timeout when this happens instead. Note that the `drain`
230234
// event does not appear to happen on `net.Socket`/`tty.WriteStream` when
231235
// writing to ptys.
236+
// TODO: https://github.com/microsoft/node-pty/issues/833#issuecomment-3665099159
232237
this._writeTimeout = setTimeout(() => this._processWriteQueue(), 5);
233238
});
234239
}
@@ -322,7 +327,7 @@ export class UnixTerminal extends Terminal {
322327
public get process(): string {
323328
if (process.platform === 'darwin') {
324329
const title = pty.process(this._fd);
325-
return (title !== 'kernel_task' ) ? title : this._file;
330+
return (title !== 'kernel_task') ? title : this._file;
326331
}
327332

328333
return pty.process(this._fd, this._pty) || this._file;

0 commit comments

Comments
 (0)