@@ -23,6 +23,13 @@ const DEFAULT_FILE = 'sh';
2323const DEFAULT_NAME = 'xterm' ;
2424const 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+
2633export 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