3 * Copyright (c) 2019 The xterm.js authors. All rights reserved.
7 declare const setTimeout: (handler: () => void, timeout?: number) => void;
10 * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
11 * Enable flow control to avoid this limit and make sure that your backend correctly
12 * propagates this to the underlying pty. (see docs for further instructions)
13 * Since this limit is meant as a safety parachute to prevent browser crashs,
14 * it is set to a very high number. Typically xterm.js gets unresponsive with
15 * a 100 times lower number (>500 kB).
17 const DISCARD_WATERMARK = 50000000; // ~50 MB
20 * The max number of ms to spend on writes before allowing the renderer to
21 * catch up with a 0ms setTimeout. A value of < 33 to keep us close to
22 * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
23 * depends on the time it takes for the renderer to draw the frame.
25 const WRITE_TIMEOUT_MS = 12;
28 * Threshold of max held chunks in the write buffer, that were already processed.
29 * This is a tradeoff between extensive write buffer shifts (bad runtime) and high
30 * memory consumption by data thats not used anymore.
32 const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
34 export class WriteBuffer {
35 private _writeBuffer: (string | Uint8Array)[] = [];
36 private _callbacks: ((() => void) | undefined)[] = [];
37 private _pendingData = 0;
38 private _bufferOffset = 0;
40 constructor(private _action: (data: string | Uint8Array) => void) { }
42 public writeSync(data: string | Uint8Array): void {
43 // force sync processing on pending data chunks to avoid in-band data scrambling
44 // does the same as innerWrite but without event loop
45 if (this._writeBuffer.length) {
46 for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) {
47 const data = this._writeBuffer[i];
48 const cb = this._callbacks[i];
52 // reset all to avoid reprocessing of chunks with scheduled innerWrite call
53 this._writeBuffer = [];
55 this._pendingData = 0;
56 // stop scheduled innerWrite by offset > length condition
57 this._bufferOffset = 0x7FFFFFFF;
59 // handle current data chunk
63 public write(data: string | Uint8Array, callback?: () => void): void {
64 if (this._pendingData > DISCARD_WATERMARK) {
65 throw new Error('write data discarded, use flow control to avoid losing data');
68 // schedule chunk processing for next event loop run
69 if (!this._writeBuffer.length) {
70 this._bufferOffset = 0;
71 setTimeout(() => this._innerWrite());
74 this._pendingData += data.length;
75 this._writeBuffer.push(data);
76 this._callbacks.push(callback);
79 protected _innerWrite(): void {
80 const startTime = Date.now();
81 while (this._writeBuffer.length > this._bufferOffset) {
82 const data = this._writeBuffer[this._bufferOffset];
83 const cb = this._callbacks[this._bufferOffset];
87 this._pendingData -= data.length;
90 if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
94 if (this._writeBuffer.length > this._bufferOffset) {
95 // Allow renderer to catch up before processing the next batch
96 // trim already processed chunks if we are above threshold
97 if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
98 this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
99 this._callbacks = this._callbacks.slice(this._bufferOffset);
100 this._bufferOffset = 0;
102 setTimeout(() => this._innerWrite(), 0);
104 this._writeBuffer = [];
105 this._callbacks = [];
106 this._pendingData = 0;
107 this._bufferOffset = 0;