UNPKG

xterm

Version:

Full xterm terminal, in your browser

111 lines (96 loc) 3.9 kB
/** * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ declare const setTimeout: (handler: () => void, timeout?: number) => void; /** * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input. * Enable flow control to avoid this limit and make sure that your backend correctly * propagates this to the underlying pty. (see docs for further instructions) * Since this limit is meant as a safety parachute to prevent browser crashs, * it is set to a very high number. Typically xterm.js gets unresponsive with * a 100 times lower number (>500 kB). */ const DISCARD_WATERMARK = 50000000; // ~50 MB /** * The max number of ms to spend on writes before allowing the renderer to * catch up with a 0ms setTimeout. A value of < 33 to keep us close to * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS * depends on the time it takes for the renderer to draw the frame. */ const WRITE_TIMEOUT_MS = 12; /** * Threshold of max held chunks in the write buffer, that were already processed. * This is a tradeoff between extensive write buffer shifts (bad runtime) and high * memory consumption by data thats not used anymore. */ const WRITE_BUFFER_LENGTH_THRESHOLD = 50; export class WriteBuffer { private _writeBuffer: (string | Uint8Array)[] = []; private _callbacks: ((() => void) | undefined)[] = []; private _pendingData = 0; private _bufferOffset = 0; constructor(private _action: (data: string | Uint8Array) => void) { } public writeSync(data: string | Uint8Array): void { // force sync processing on pending data chunks to avoid in-band data scrambling // does the same as innerWrite but without event loop if (this._writeBuffer.length) { for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) { const data = this._writeBuffer[i]; const cb = this._callbacks[i]; this._action(data); if (cb) cb(); } // reset all to avoid reprocessing of chunks with scheduled innerWrite call this._writeBuffer = []; this._callbacks = []; this._pendingData = 0; // stop scheduled innerWrite by offset > length condition this._bufferOffset = 0x7FFFFFFF; } // handle current data chunk this._action(data); } public write(data: string | Uint8Array, callback?: () => void): void { if (this._pendingData > DISCARD_WATERMARK) { throw new Error('write data discarded, use flow control to avoid losing data'); } // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; setTimeout(() => this._innerWrite()); } this._pendingData += data.length; this._writeBuffer.push(data); this._callbacks.push(callback); } protected _innerWrite(): void { const startTime = Date.now(); while (this._writeBuffer.length > this._bufferOffset) { const data = this._writeBuffer[this._bufferOffset]; const cb = this._callbacks[this._bufferOffset]; this._bufferOffset++; this._action(data); this._pendingData -= data.length; if (cb) cb(); if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { break; } } if (this._writeBuffer.length > this._bufferOffset) { // Allow renderer to catch up before processing the next batch // trim already processed chunks if we are above threshold if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) { this._writeBuffer = this._writeBuffer.slice(this._bufferOffset); this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; } setTimeout(() => this._innerWrite(), 0); } else { this._writeBuffer = []; this._callbacks = []; this._pendingData = 0; this._bufferOffset = 0; } } }