@@ -44,7 +44,7 @@ export class UnixTerminal extends Terminal {
4444 private _emittedClose : boolean = false ;
4545
4646 private readonly _writeQueue : IWriteTask [ ] = [ ] ;
47- private _writeTimeout : NodeJS . Timeout | undefined ;
47+ private _writeImmediate : NodeJS . Immediate | undefined ;
4848 private _encoding ?: BufferEncoding = undefined ;
4949
5050 private _master : net . Socket | undefined ;
@@ -191,27 +191,23 @@ export class UnixTerminal extends Terminal {
191191 }
192192
193193 private _processWriteQueue ( ) : void {
194+ this . _writeImmediate = undefined ;
195+
194196 if ( this . _writeQueue . length === 0 ) {
195197 return ;
196198 }
197199
198200 const task = this . _writeQueue [ 0 ] ;
199201
200202 // Write to the underlying file descriptor and handle it directly, rather
201- // than using the `net.Socket`/`tty.WriteStream` wrappers which swallow the
202- // errors and cause the thread to block indefinitely.
203+ // than using the `net.Socket`/`tty.WriteStream` wrappers which swallow and
204+ // mask errors like EAGAIN and can cause the thread to block indefinitely.
203205 fs . write ( this . _fd , task . data , task . offset , ( err , written ) => {
204206 if ( err ) {
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 ) ;
207+ if ( 'code' in err && err . code === 'EAGAIN' ) {
208+ // `setImmediate` is used to yield to the event loop and re-attempt
209+ // the write later.
210+ this . _writeImmediate = setImmediate ( ( ) => this . _processWriteQueue ( ) ) ;
215211 } else {
216212 // Stop processing immediately on unexpected error and log
217213 this . _writeQueue . length = 0 ;
@@ -225,16 +221,16 @@ export class UnixTerminal extends Terminal {
225221 this . _writeQueue . shift ( ) ;
226222 }
227223
228- // Using `setImmediate` here appears to corrupt the data, this may be what
229- // the interleaving/dropped data comment is about in Node.js' tty module:
230- // https://github.com/nodejs/node/blob/4cac2b94bed4bf02810be054e8f63c0048c66564/lib/tty.js#L106C1-L111C34
224+ // Since there is more room in the kernel buffer, we can continue to write
225+ // until we hit EAGAIN or exhaust the queue.
231226 //
232- // Yielding via `setImmediate` also doesn't seem to drain the buffer much
233- // anyway, so use a short timeout when this happens instead. Note that the `drain`
234- // event does not appear to happen on `net.Socket`/`tty.WriteStream` when
235- // writing to ptys.
236- // TODO: https://github.com/microsoft/node-pty/issues/833#issuecomment-3665099159
237- this . _writeTimeout = setTimeout ( ( ) => this . _processWriteQueue ( ) , 5 ) ;
227+ // Note that old versions of bash, like v3.2 which ships in macOS, appears
228+ // to have a bug in its readline implementation that causes data
229+ // corruption when writes to the pty happens too quickly. Instead of
230+ // trying to workaround that we just accept it so that large pastes are as
231+ // fast as possible.
232+ // Context: https://github.com/microsoft/node-pty/issues/833
233+ this . _processWriteQueue ( ) ;
238234 } ) ;
239235 }
240236
@@ -312,7 +308,8 @@ export class UnixTerminal extends Terminal {
312308
313309 this . _socket . destroy ( ) ;
314310
315- clearTimeout ( this . _writeTimeout ) ;
311+ clearImmediate ( this . _writeImmediate ) ;
312+ this . _writeImmediate = undefined ;
316313 }
317314
318315 public kill ( signal ?: string ) : void {
0 commit comments