UNPKG

sussudio

Version:

An unofficial VS Code Internal API

1,138 lines (1,137 loc) 45.8 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { IntervalTimer } from "../../../common/async.mjs"; import { VSBuffer } from "../../../common/buffer.mjs"; import { Emitter } from "../../../common/event.mjs"; import { Disposable, DisposableStore } from "../../../common/lifecycle.mjs"; import { IPCClient } from "./ipc.mjs"; export var SocketDiagnosticsEventType; (function (SocketDiagnosticsEventType) { SocketDiagnosticsEventType["Created"] = "created"; SocketDiagnosticsEventType["Read"] = "read"; SocketDiagnosticsEventType["Write"] = "write"; SocketDiagnosticsEventType["Open"] = "open"; SocketDiagnosticsEventType["Error"] = "error"; SocketDiagnosticsEventType["Close"] = "close"; SocketDiagnosticsEventType["BrowserWebSocketBlobReceived"] = "browserWebSocketBlobReceived"; SocketDiagnosticsEventType["NodeEndReceived"] = "nodeEndReceived"; SocketDiagnosticsEventType["NodeEndSent"] = "nodeEndSent"; SocketDiagnosticsEventType["NodeDrainBegin"] = "nodeDrainBegin"; SocketDiagnosticsEventType["NodeDrainEnd"] = "nodeDrainEnd"; SocketDiagnosticsEventType["zlibInflateError"] = "zlibInflateError"; SocketDiagnosticsEventType["zlibInflateData"] = "zlibInflateData"; SocketDiagnosticsEventType["zlibInflateInitialWrite"] = "zlibInflateInitialWrite"; SocketDiagnosticsEventType["zlibInflateInitialFlushFired"] = "zlibInflateInitialFlushFired"; SocketDiagnosticsEventType["zlibInflateWrite"] = "zlibInflateWrite"; SocketDiagnosticsEventType["zlibInflateFlushFired"] = "zlibInflateFlushFired"; SocketDiagnosticsEventType["zlibDeflateError"] = "zlibDeflateError"; SocketDiagnosticsEventType["zlibDeflateData"] = "zlibDeflateData"; SocketDiagnosticsEventType["zlibDeflateWrite"] = "zlibDeflateWrite"; SocketDiagnosticsEventType["zlibDeflateFlushFired"] = "zlibDeflateFlushFired"; SocketDiagnosticsEventType["WebSocketNodeSocketWrite"] = "webSocketNodeSocketWrite"; SocketDiagnosticsEventType["WebSocketNodeSocketPeekedHeader"] = "webSocketNodeSocketPeekedHeader"; SocketDiagnosticsEventType["WebSocketNodeSocketReadHeader"] = "webSocketNodeSocketReadHeader"; SocketDiagnosticsEventType["WebSocketNodeSocketReadData"] = "webSocketNodeSocketReadData"; SocketDiagnosticsEventType["WebSocketNodeSocketUnmaskedData"] = "webSocketNodeSocketUnmaskedData"; SocketDiagnosticsEventType["WebSocketNodeSocketDrainBegin"] = "webSocketNodeSocketDrainBegin"; SocketDiagnosticsEventType["WebSocketNodeSocketDrainEnd"] = "webSocketNodeSocketDrainEnd"; SocketDiagnosticsEventType["ProtocolHeaderRead"] = "protocolHeaderRead"; SocketDiagnosticsEventType["ProtocolMessageRead"] = "protocolMessageRead"; SocketDiagnosticsEventType["ProtocolHeaderWrite"] = "protocolHeaderWrite"; SocketDiagnosticsEventType["ProtocolMessageWrite"] = "protocolMessageWrite"; SocketDiagnosticsEventType["ProtocolWrite"] = "protocolWrite"; })(SocketDiagnosticsEventType || (SocketDiagnosticsEventType = {})); export var SocketDiagnostics; (function (SocketDiagnostics) { SocketDiagnostics.enableDiagnostics = false; SocketDiagnostics.records = []; const socketIds = new WeakMap(); let lastUsedSocketId = 0; function getSocketId(nativeObject, label) { if (!socketIds.has(nativeObject)) { const id = String(++lastUsedSocketId); socketIds.set(nativeObject, id); } return socketIds.get(nativeObject); } function traceSocketEvent(nativeObject, socketDebugLabel, type, data) { if (!SocketDiagnostics.enableDiagnostics) { return; } const id = getSocketId(nativeObject, socketDebugLabel); if (data instanceof VSBuffer || data instanceof Uint8Array || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { const copiedData = VSBuffer.alloc(data.byteLength); copiedData.set(data); SocketDiagnostics.records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, buff: copiedData }); } else { // data is a custom object SocketDiagnostics.records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, data: data }); } } SocketDiagnostics.traceSocketEvent = traceSocketEvent; })(SocketDiagnostics || (SocketDiagnostics = {})); export var SocketCloseEventType; (function (SocketCloseEventType) { SocketCloseEventType[SocketCloseEventType["NodeSocketCloseEvent"] = 0] = "NodeSocketCloseEvent"; SocketCloseEventType[SocketCloseEventType["WebSocketCloseEvent"] = 1] = "WebSocketCloseEvent"; })(SocketCloseEventType || (SocketCloseEventType = {})); let emptyBuffer = null; function getEmptyBuffer() { if (!emptyBuffer) { emptyBuffer = VSBuffer.alloc(0); } return emptyBuffer; } export class ChunkStream { _chunks; _totalLength; get byteLength() { return this._totalLength; } constructor() { this._chunks = []; this._totalLength = 0; } acceptChunk(buff) { this._chunks.push(buff); this._totalLength += buff.byteLength; } read(byteCount) { return this._read(byteCount, true); } peek(byteCount) { return this._read(byteCount, false); } _read(byteCount, advance) { if (byteCount === 0) { return getEmptyBuffer(); } if (byteCount > this._totalLength) { throw new Error(`Cannot read so many bytes!`); } if (this._chunks[0].byteLength === byteCount) { // super fast path, precisely first chunk must be returned const result = this._chunks[0]; if (advance) { this._chunks.shift(); this._totalLength -= byteCount; } return result; } if (this._chunks[0].byteLength > byteCount) { // fast path, the reading is entirely within the first chunk const result = this._chunks[0].slice(0, byteCount); if (advance) { this._chunks[0] = this._chunks[0].slice(byteCount); this._totalLength -= byteCount; } return result; } const result = VSBuffer.alloc(byteCount); let resultOffset = 0; let chunkIndex = 0; while (byteCount > 0) { const chunk = this._chunks[chunkIndex]; if (chunk.byteLength > byteCount) { // this chunk will survive const chunkPart = chunk.slice(0, byteCount); result.set(chunkPart, resultOffset); resultOffset += byteCount; if (advance) { this._chunks[chunkIndex] = chunk.slice(byteCount); this._totalLength -= byteCount; } byteCount -= byteCount; } else { // this chunk will be entirely read result.set(chunk, resultOffset); resultOffset += chunk.byteLength; if (advance) { this._chunks.shift(); this._totalLength -= chunk.byteLength; } else { chunkIndex++; } byteCount -= chunk.byteLength; } } return result; } } var ProtocolMessageType; (function (ProtocolMessageType) { ProtocolMessageType[ProtocolMessageType["None"] = 0] = "None"; ProtocolMessageType[ProtocolMessageType["Regular"] = 1] = "Regular"; ProtocolMessageType[ProtocolMessageType["Control"] = 2] = "Control"; ProtocolMessageType[ProtocolMessageType["Ack"] = 3] = "Ack"; ProtocolMessageType[ProtocolMessageType["Disconnect"] = 5] = "Disconnect"; ProtocolMessageType[ProtocolMessageType["ReplayRequest"] = 6] = "ReplayRequest"; ProtocolMessageType[ProtocolMessageType["Pause"] = 7] = "Pause"; ProtocolMessageType[ProtocolMessageType["Resume"] = 8] = "Resume"; ProtocolMessageType[ProtocolMessageType["KeepAlive"] = 9] = "KeepAlive"; ProtocolMessageType[ProtocolMessageType["LatencyMeasurementRequest"] = 10] = "LatencyMeasurementRequest"; ProtocolMessageType[ProtocolMessageType["LatencyMeasurementResponse"] = 11] = "LatencyMeasurementResponse"; })(ProtocolMessageType || (ProtocolMessageType = {})); function protocolMessageTypeToString(messageType) { switch (messageType) { case 0 /* ProtocolMessageType.None */: return 'None'; case 1 /* ProtocolMessageType.Regular */: return 'Regular'; case 2 /* ProtocolMessageType.Control */: return 'Control'; case 3 /* ProtocolMessageType.Ack */: return 'Ack'; case 5 /* ProtocolMessageType.Disconnect */: return 'Disconnect'; case 6 /* ProtocolMessageType.ReplayRequest */: return 'ReplayRequest'; case 7 /* ProtocolMessageType.Pause */: return 'PauseWriting'; case 8 /* ProtocolMessageType.Resume */: return 'ResumeWriting'; case 9 /* ProtocolMessageType.KeepAlive */: return 'KeepAlive'; case 10 /* ProtocolMessageType.LatencyMeasurementRequest */: return 'LatencyMeasurementRequest'; case 11 /* ProtocolMessageType.LatencyMeasurementResponse */: return 'LatencyMeasurementResponse'; } } export var ProtocolConstants; (function (ProtocolConstants) { ProtocolConstants[ProtocolConstants["HeaderLength"] = 13] = "HeaderLength"; /** * Send an Acknowledge message at most 2 seconds later... */ ProtocolConstants[ProtocolConstants["AcknowledgeTime"] = 2000] = "AcknowledgeTime"; /** * If there is a sent message that has been unacknowledged for 20 seconds, * and we didn't see any incoming server data in the past 20 seconds, * then consider the connection has timed out. */ ProtocolConstants[ProtocolConstants["TimeoutTime"] = 20000] = "TimeoutTime"; /** * If there is no reconnection within this time-frame, consider the connection permanently closed... */ ProtocolConstants[ProtocolConstants["ReconnectionGraceTime"] = 10800000] = "ReconnectionGraceTime"; /** * Maximal grace time between the first and the last reconnection... */ ProtocolConstants[ProtocolConstants["ReconnectionShortGraceTime"] = 300000] = "ReconnectionShortGraceTime"; /** * Send a message every 5 seconds to avoid that the connection is closed by the OS. */ ProtocolConstants[ProtocolConstants["KeepAliveSendTime"] = 5000] = "KeepAliveSendTime"; /** * Measure the latency every 1 minute. */ ProtocolConstants[ProtocolConstants["LatencySampleTime"] = 60000] = "LatencySampleTime"; /** * Keep the last 5 samples for latency measurement. */ ProtocolConstants[ProtocolConstants["LatencySampleCount"] = 5] = "LatencySampleCount"; /** * A latency over 1s will be considered high. */ ProtocolConstants[ProtocolConstants["HighLatencyTimeThreshold"] = 1000] = "HighLatencyTimeThreshold"; /** * Having 3 or more samples with high latency will trigger a high latency event. */ ProtocolConstants[ProtocolConstants["HighLatencySampleThreshold"] = 3] = "HighLatencySampleThreshold"; })(ProtocolConstants || (ProtocolConstants = {})); class ProtocolMessage { type; id; ack; data; writtenTime; constructor(type, id, ack, data) { this.type = type; this.id = id; this.ack = ack; this.data = data; this.writtenTime = 0; } get size() { return this.data.byteLength; } } class ProtocolReader extends Disposable { _socket; _isDisposed; _incomingData; lastReadTime; _onMessage = this._register(new Emitter()); onMessage = this._onMessage.event; _state = { readHead: true, readLen: 13 /* ProtocolConstants.HeaderLength */, messageType: 0 /* ProtocolMessageType.None */, id: 0, ack: 0 }; constructor(socket) { super(); this._socket = socket; this._isDisposed = false; this._incomingData = new ChunkStream(); this._register(this._socket.onData(data => this.acceptChunk(data))); this.lastReadTime = Date.now(); } acceptChunk(data) { if (!data || data.byteLength === 0) { return; } this.lastReadTime = Date.now(); this._incomingData.acceptChunk(data); while (this._incomingData.byteLength >= this._state.readLen) { const buff = this._incomingData.read(this._state.readLen); if (this._state.readHead) { // buff is the header // save new state => next time will read the body this._state.readHead = false; this._state.readLen = buff.readUInt32BE(9); this._state.messageType = buff.readUInt8(0); this._state.id = buff.readUInt32BE(1); this._state.ack = buff.readUInt32BE(5); this._socket.traceSocketEvent("protocolHeaderRead" /* SocketDiagnosticsEventType.ProtocolHeaderRead */, { messageType: protocolMessageTypeToString(this._state.messageType), id: this._state.id, ack: this._state.ack, messageSize: this._state.readLen }); } else { // buff is the body const messageType = this._state.messageType; const id = this._state.id; const ack = this._state.ack; // save new state => next time will read the header this._state.readHead = true; this._state.readLen = 13 /* ProtocolConstants.HeaderLength */; this._state.messageType = 0 /* ProtocolMessageType.None */; this._state.id = 0; this._state.ack = 0; this._socket.traceSocketEvent("protocolMessageRead" /* SocketDiagnosticsEventType.ProtocolMessageRead */, buff); this._onMessage.fire(new ProtocolMessage(messageType, id, ack, buff)); if (this._isDisposed) { // check if an event listener lead to our disposal break; } } } } readEntireBuffer() { return this._incomingData.read(this._incomingData.byteLength); } dispose() { this._isDisposed = true; super.dispose(); } } class ProtocolWriter { _isDisposed; _isPaused; _socket; _data; _totalLength; lastWriteTime; constructor(socket) { this._isDisposed = false; this._isPaused = false; this._socket = socket; this._data = []; this._totalLength = 0; this.lastWriteTime = 0; } dispose() { try { this.flush(); } catch (err) { // ignore error, since the socket could be already closed } this._isDisposed = true; } drain() { this.flush(); return this._socket.drain(); } flush() { // flush this._writeNow(); } pause() { this._isPaused = true; } resume() { this._isPaused = false; this._scheduleWriting(); } write(msg) { if (this._isDisposed) { // ignore: there could be left-over promises which complete and then // decide to write a response, etc... return; } msg.writtenTime = Date.now(); this.lastWriteTime = Date.now(); const header = VSBuffer.alloc(13 /* ProtocolConstants.HeaderLength */); header.writeUInt8(msg.type, 0); header.writeUInt32BE(msg.id, 1); header.writeUInt32BE(msg.ack, 5); header.writeUInt32BE(msg.data.byteLength, 9); this._socket.traceSocketEvent("protocolHeaderWrite" /* SocketDiagnosticsEventType.ProtocolHeaderWrite */, { messageType: protocolMessageTypeToString(msg.type), id: msg.id, ack: msg.ack, messageSize: msg.data.byteLength }); this._socket.traceSocketEvent("protocolMessageWrite" /* SocketDiagnosticsEventType.ProtocolMessageWrite */, msg.data); this._writeSoon(header, msg.data); } _bufferAdd(head, body) { const wasEmpty = this._totalLength === 0; this._data.push(head, body); this._totalLength += head.byteLength + body.byteLength; return wasEmpty; } _bufferTake() { const ret = VSBuffer.concat(this._data, this._totalLength); this._data.length = 0; this._totalLength = 0; return ret; } _writeSoon(header, data) { if (this._bufferAdd(header, data)) { this._scheduleWriting(); } } _writeNowTimeout = null; _scheduleWriting() { if (this._writeNowTimeout) { return; } this._writeNowTimeout = setTimeout(() => { this._writeNowTimeout = null; this._writeNow(); }); } _writeNow() { if (this._totalLength === 0) { return; } if (this._isPaused) { return; } const data = this._bufferTake(); this._socket.traceSocketEvent("protocolWrite" /* SocketDiagnosticsEventType.ProtocolWrite */, { byteLength: data.byteLength }); this._socket.write(data); } } /** * A message has the following format: * ``` * /-------------------------------|------\ * | HEADER | | * |-------------------------------| DATA | * | TYPE | ID | ACK | DATA_LENGTH | | * \-------------------------------|------/ * ``` * The header is 9 bytes and consists of: * - TYPE is 1 byte (ProtocolMessageType) - the message type * - ID is 4 bytes (u32be) - the message id (can be 0 to indicate to be ignored) * - ACK is 4 bytes (u32be) - the acknowledged message id (can be 0 to indicate to be ignored) * - DATA_LENGTH is 4 bytes (u32be) - the length in bytes of DATA * * Only Regular messages are counted, other messages are not counted, nor acknowledged. */ export class Protocol extends Disposable { _socket; _socketWriter; _socketReader; _onMessage = new Emitter(); onMessage = this._onMessage.event; _onDidDispose = new Emitter(); onDidDispose = this._onDidDispose.event; constructor(socket) { super(); this._socket = socket; this._socketWriter = this._register(new ProtocolWriter(this._socket)); this._socketReader = this._register(new ProtocolReader(this._socket)); this._register(this._socketReader.onMessage((msg) => { if (msg.type === 1 /* ProtocolMessageType.Regular */) { this._onMessage.fire(msg.data); } })); this._register(this._socket.onClose(() => this._onDidDispose.fire())); } drain() { return this._socketWriter.drain(); } getSocket() { return this._socket; } sendDisconnect() { // Nothing to do... } send(buffer) { this._socketWriter.write(new ProtocolMessage(1 /* ProtocolMessageType.Regular */, 0, 0, buffer)); } } export class Client extends IPCClient { protocol; static fromSocket(socket, id) { return new Client(new Protocol(socket), id); } get onDidDispose() { return this.protocol.onDidDispose; } constructor(protocol, id, ipcLogger = null) { super(protocol, id, ipcLogger); this.protocol = protocol; } dispose() { super.dispose(); const socket = this.protocol.getSocket(); this.protocol.sendDisconnect(); this.protocol.dispose(); socket.end(); } } /** * Will ensure no messages are lost if there are no event listeners. */ export class BufferedEmitter { _emitter; event; _hasListeners = false; _isDeliveringMessages = false; _bufferedMessages = []; constructor() { this._emitter = new Emitter({ onWillAddFirstListener: () => { this._hasListeners = true; // it is important to deliver these messages after this call, but before // other messages have a chance to be received (to guarantee in order delivery) // that's why we're using here queueMicrotask and not other types of timeouts queueMicrotask(() => this._deliverMessages()); }, onDidRemoveLastListener: () => { this._hasListeners = false; } }); this.event = this._emitter.event; } _deliverMessages() { if (this._isDeliveringMessages) { return; } this._isDeliveringMessages = true; while (this._hasListeners && this._bufferedMessages.length > 0) { this._emitter.fire(this._bufferedMessages.shift()); } this._isDeliveringMessages = false; } fire(event) { if (this._hasListeners) { if (this._bufferedMessages.length > 0) { this._bufferedMessages.push(event); } else { this._emitter.fire(event); } } else { this._bufferedMessages.push(event); } } flushBuffer() { this._bufferedMessages = []; } } class QueueElement { data; next; constructor(data) { this.data = data; this.next = null; } } class Queue { _first; _last; constructor() { this._first = null; this._last = null; } length() { let result = 0; let current = this._first; while (current) { current = current.next; result++; } return result; } peek() { if (!this._first) { return null; } return this._first.data; } toArray() { const result = []; let resultLen = 0; let it = this._first; while (it) { result[resultLen++] = it.data; it = it.next; } return result; } pop() { if (!this._first) { return; } if (this._first === this._last) { this._first = null; this._last = null; return; } this._first = this._first.next; } push(item) { const element = new QueueElement(item); if (!this._first) { this._first = element; this._last = element; return; } this._last.next = element; this._last = element; } } class LoadEstimator { static _HISTORY_LENGTH = 10; static _INSTANCE = null; static getInstance() { if (!LoadEstimator._INSTANCE) { LoadEstimator._INSTANCE = new LoadEstimator(); } return LoadEstimator._INSTANCE; } lastRuns; constructor() { this.lastRuns = []; const now = Date.now(); for (let i = 0; i < LoadEstimator._HISTORY_LENGTH; i++) { this.lastRuns[i] = now - 1000 * i; } setInterval(() => { for (let i = LoadEstimator._HISTORY_LENGTH; i >= 1; i--) { this.lastRuns[i] = this.lastRuns[i - 1]; } this.lastRuns[0] = Date.now(); }, 1000); } /** * returns an estimative number, from 0 (low load) to 1 (high load) */ load() { const now = Date.now(); const historyLimit = (1 + LoadEstimator._HISTORY_LENGTH) * 1000; let score = 0; for (let i = 0; i < LoadEstimator._HISTORY_LENGTH; i++) { if (now - this.lastRuns[i] <= historyLimit) { score++; } } return 1 - score / LoadEstimator._HISTORY_LENGTH; } hasHighLoad() { return this.load() >= 0.5; } } export var ConnectionHealth; (function (ConnectionHealth) { /** * The connection health is considered good when a certain number of recent round trip time measurements are below a certain threshold. * @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold */ ConnectionHealth[ConnectionHealth["Good"] = 0] = "Good"; /** * The connection health is considered poor when a certain number of recent round trip time measurements are above a certain threshold. * @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold */ ConnectionHealth[ConnectionHealth["Poor"] = 1] = "Poor"; })(ConnectionHealth || (ConnectionHealth = {})); export function connectionHealthToString(connectionHealth) { switch (connectionHealth) { case 0 /* ConnectionHealth.Good */: return 'good'; case 1 /* ConnectionHealth.Poor */: return 'poor'; } } /** * An event describing that the connection health has changed. */ export class ConnectionHealthChangedEvent { connectionHealth; constructor(connectionHealth) { this.connectionHealth = connectionHealth; } } /** * An event describing that a round trip time measurement was above a certain threshold. */ export class HighRoundTripTimeEvent { roundTripTime; recentHighRoundTripCount; constructor( /** * The round trip time in milliseconds. */ roundTripTime, /** * The number of recent round trip time measurements that were above the threshold. * @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold */ recentHighRoundTripCount) { this.roundTripTime = roundTripTime; this.recentHighRoundTripCount = recentHighRoundTripCount; } } /** * Same as Protocol, but will actually track messages and acks. * Moreover, it will ensure no messages are lost if there are no event listeners. */ export class PersistentProtocol { _isReconnecting; _outgoingUnackMsg; _outgoingMsgId; _outgoingAckId; _outgoingAckTimeout; _incomingMsgId; _incomingAckId; _incomingMsgLastTime; _incomingAckTimeout; _keepAliveInterval; _lastReplayRequestTime; _lastSocketTimeoutTime; _socket; _socketWriter; _socketReader; _socketLatencyMonitor; _socketDisposables; _loadEstimator; _measureRoundTripTime; _shouldSendKeepAlive; _onControlMessage = new BufferedEmitter(); onControlMessage = this._onControlMessage.event; _onMessage = new BufferedEmitter(); onMessage = this._onMessage.event; _onDidDispose = new BufferedEmitter(); onDidDispose = this._onDidDispose.event; _onSocketClose = new BufferedEmitter(); onSocketClose = this._onSocketClose.event; _onSocketTimeout = new BufferedEmitter(); onSocketTimeout = this._onSocketTimeout.event; _onHighRoundTripTime = new BufferedEmitter(); onHighRoundTripTime = this._onHighRoundTripTime.event; _onDidChangeConnectionHealth = new BufferedEmitter(); onDidChangeConnectionHealth = this._onDidChangeConnectionHealth.event; get unacknowledgedCount() { return this._outgoingMsgId - this._outgoingAckId; } constructor(opts) { this._loadEstimator = opts.loadEstimator ?? LoadEstimator.getInstance(); this._measureRoundTripTime = opts.measureRoundTripTime ?? false; this._shouldSendKeepAlive = opts.sendKeepAlive ?? true; this._isReconnecting = false; this._outgoingUnackMsg = new Queue(); this._outgoingMsgId = 0; this._outgoingAckId = 0; this._outgoingAckTimeout = null; this._incomingMsgId = 0; this._incomingAckId = 0; this._incomingMsgLastTime = 0; this._incomingAckTimeout = null; this._lastReplayRequestTime = 0; this._lastSocketTimeoutTime = Date.now(); this._socketDisposables = new DisposableStore(); this._socket = opts.socket; this._socketWriter = this._socketDisposables.add(new ProtocolWriter(this._socket)); this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket)); this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg))); this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e))); this._socketLatencyMonitor = this._socketDisposables.add(new LatencyMonitor()); // is started immediately this._socketDisposables.add(this._socketLatencyMonitor.onSendLatencyRequest(buffer => this._sendLatencyMeasurementRequest(buffer))); this._socketDisposables.add(this._socketLatencyMonitor.onHighRoundTripTime(e => this._onHighRoundTripTime.fire(e))); this._socketDisposables.add(this._socketLatencyMonitor.onDidChangeConnectionHealth(e => this._onDidChangeConnectionHealth.fire(e))); if (this._measureRoundTripTime) { this._socketLatencyMonitor.start(); } if (opts.initialChunk) { this._socketReader.acceptChunk(opts.initialChunk); } if (this._shouldSendKeepAlive) { this._keepAliveInterval = setInterval(() => { this._sendKeepAlive(); }, 5000 /* ProtocolConstants.KeepAliveSendTime */); } else { this._keepAliveInterval = null; } } dispose() { if (this._outgoingAckTimeout) { clearTimeout(this._outgoingAckTimeout); this._outgoingAckTimeout = null; } if (this._incomingAckTimeout) { clearTimeout(this._incomingAckTimeout); this._incomingAckTimeout = null; } if (this._keepAliveInterval) { clearInterval(this._keepAliveInterval); this._keepAliveInterval = null; } this._socketDisposables.dispose(); } drain() { return this._socketWriter.drain(); } sendDisconnect() { const msg = new ProtocolMessage(5 /* ProtocolMessageType.Disconnect */, 0, 0, getEmptyBuffer()); this._socketWriter.write(msg); this._socketWriter.flush(); } sendPause() { const msg = new ProtocolMessage(7 /* ProtocolMessageType.Pause */, 0, 0, getEmptyBuffer()); this._socketWriter.write(msg); } sendResume() { const msg = new ProtocolMessage(8 /* ProtocolMessageType.Resume */, 0, 0, getEmptyBuffer()); this._socketWriter.write(msg); } pauseSocketWriting() { this._socketWriter.pause(); } getSocket() { return this._socket; } getMillisSinceLastIncomingData() { return Date.now() - this._socketReader.lastReadTime; } beginAcceptReconnection(socket, initialDataChunk) { this._isReconnecting = true; this._socketDisposables.dispose(); this._socketDisposables = new DisposableStore(); this._onControlMessage.flushBuffer(); this._onSocketClose.flushBuffer(); this._onSocketTimeout.flushBuffer(); this._socket.dispose(); this._lastReplayRequestTime = 0; this._lastSocketTimeoutTime = Date.now(); this._socket = socket; this._socketWriter = this._socketDisposables.add(new ProtocolWriter(this._socket)); this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket)); this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg))); this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e))); this._socketLatencyMonitor = this._socketDisposables.add(new LatencyMonitor()); // will be started later this._socketDisposables.add(this._socketLatencyMonitor.onSendLatencyRequest(buffer => this._sendLatencyMeasurementRequest(buffer))); this._socketDisposables.add(this._socketLatencyMonitor.onHighRoundTripTime(e => this._onHighRoundTripTime.fire(e))); this._socketDisposables.add(this._socketLatencyMonitor.onDidChangeConnectionHealth(e => this._onDidChangeConnectionHealth.fire(e))); this._socketReader.acceptChunk(initialDataChunk); } endAcceptReconnection() { this._isReconnecting = false; if (this._measureRoundTripTime) { this._socketLatencyMonitor.start(); } // After a reconnection, let the other party know (again) which messages have been received. // (perhaps the other party didn't receive a previous ACK) this._incomingAckId = this._incomingMsgId; const msg = new ProtocolMessage(3 /* ProtocolMessageType.Ack */, 0, this._incomingAckId, getEmptyBuffer()); this._socketWriter.write(msg); // Send again all unacknowledged messages const toSend = this._outgoingUnackMsg.toArray(); for (let i = 0, len = toSend.length; i < len; i++) { this._socketWriter.write(toSend[i]); } this._recvAckCheck(); } acceptDisconnect() { this._onDidDispose.fire(); } _receiveMessage(msg) { if (msg.ack > this._outgoingAckId) { this._outgoingAckId = msg.ack; do { const first = this._outgoingUnackMsg.peek(); if (first && first.id <= msg.ack) { // this message has been confirmed, remove it this._outgoingUnackMsg.pop(); } else { break; } } while (true); } switch (msg.type) { case 0 /* ProtocolMessageType.None */: { // N/A break; } case 1 /* ProtocolMessageType.Regular */: { if (msg.id > this._incomingMsgId) { if (msg.id !== this._incomingMsgId + 1) { // in case we missed some messages we ask the other party to resend them const now = Date.now(); if (now - this._lastReplayRequestTime > 10000) { // send a replay request at most once every 10s this._lastReplayRequestTime = now; this._socketWriter.write(new ProtocolMessage(6 /* ProtocolMessageType.ReplayRequest */, 0, 0, getEmptyBuffer())); } } else { this._incomingMsgId = msg.id; this._incomingMsgLastTime = Date.now(); this._sendAckCheck(); this._onMessage.fire(msg.data); } } break; } case 2 /* ProtocolMessageType.Control */: { this._onControlMessage.fire(msg.data); break; } case 3 /* ProtocolMessageType.Ack */: { // nothing to do, .ack is handled above already break; } case 5 /* ProtocolMessageType.Disconnect */: { this._onDidDispose.fire(); break; } case 6 /* ProtocolMessageType.ReplayRequest */: { // Send again all unacknowledged messages const toSend = this._outgoingUnackMsg.toArray(); for (let i = 0, len = toSend.length; i < len; i++) { this._socketWriter.write(toSend[i]); } this._recvAckCheck(); break; } case 7 /* ProtocolMessageType.Pause */: { this._socketWriter.pause(); break; } case 8 /* ProtocolMessageType.Resume */: { this._socketWriter.resume(); break; } case 9 /* ProtocolMessageType.KeepAlive */: { // nothing to do break; } case 10 /* ProtocolMessageType.LatencyMeasurementRequest */: { // we just send the data back this._sendLatencyMeasurementResponse(msg.data); break; } case 11 /* ProtocolMessageType.LatencyMeasurementResponse */: { this._socketLatencyMonitor.handleResponse(msg.data); break; } } } readEntireBuffer() { return this._socketReader.readEntireBuffer(); } flush() { this._socketWriter.flush(); } send(buffer) { const myId = ++this._outgoingMsgId; this._incomingAckId = this._incomingMsgId; const msg = new ProtocolMessage(1 /* ProtocolMessageType.Regular */, myId, this._incomingAckId, buffer); this._outgoingUnackMsg.push(msg); if (!this._isReconnecting) { this._socketWriter.write(msg); this._recvAckCheck(); } } /** * Send a message which will not be part of the regular acknowledge flow. * Use this for early control messages which are repeated in case of reconnection. */ sendControl(buffer) { const msg = new ProtocolMessage(2 /* ProtocolMessageType.Control */, 0, 0, buffer); this._socketWriter.write(msg); } _sendAckCheck() { if (this._incomingMsgId <= this._incomingAckId) { // nothink to acknowledge return; } if (this._incomingAckTimeout) { // there will be a check in the near future return; } const timeSinceLastIncomingMsg = Date.now() - this._incomingMsgLastTime; if (timeSinceLastIncomingMsg >= 2000 /* ProtocolConstants.AcknowledgeTime */) { // sufficient time has passed since this message has been received, // and no message from our side needed to be sent in the meantime, // so we will send a message containing only an ack. this._sendAck(); return; } this._incomingAckTimeout = setTimeout(() => { this._incomingAckTimeout = null; this._sendAckCheck(); }, 2000 /* ProtocolConstants.AcknowledgeTime */ - timeSinceLastIncomingMsg + 5); } _recvAckCheck() { if (this._outgoingMsgId <= this._outgoingAckId) { // everything has been acknowledged return; } if (this._outgoingAckTimeout) { // there will be a check in the near future return; } if (this._isReconnecting) { // do not cause a timeout during reconnection, // because messages will not be actually written until `endAcceptReconnection` return; } const oldestUnacknowledgedMsg = this._outgoingUnackMsg.peek(); const timeSinceOldestUnacknowledgedMsg = Date.now() - oldestUnacknowledgedMsg.writtenTime; const timeSinceLastReceivedSomeData = Date.now() - this._socketReader.lastReadTime; const timeSinceLastTimeout = Date.now() - this._lastSocketTimeoutTime; if (timeSinceOldestUnacknowledgedMsg >= 20000 /* ProtocolConstants.TimeoutTime */ && timeSinceLastReceivedSomeData >= 20000 /* ProtocolConstants.TimeoutTime */ && timeSinceLastTimeout >= 20000 /* ProtocolConstants.TimeoutTime */) { // It's been a long time since our sent message was acknowledged // and a long time since we received some data // But this might be caused by the event loop being busy and failing to read messages if (!this._loadEstimator.hasHighLoad()) { // Trash the socket this._lastSocketTimeoutTime = Date.now(); this._onSocketTimeout.fire({ unacknowledgedMsgCount: this._outgoingUnackMsg.length(), timeSinceOldestUnacknowledgedMsg, timeSinceLastReceivedSomeData }); return; } } const minimumTimeUntilTimeout = Math.max(20000 /* ProtocolConstants.TimeoutTime */ - timeSinceOldestUnacknowledgedMsg, 20000 /* ProtocolConstants.TimeoutTime */ - timeSinceLastReceivedSomeData, 20000 /* ProtocolConstants.TimeoutTime */ - timeSinceLastTimeout, 500); this._outgoingAckTimeout = setTimeout(() => { this._outgoingAckTimeout = null; this._recvAckCheck(); }, minimumTimeUntilTimeout); } _sendAck() { if (this._incomingMsgId <= this._incomingAckId) { // nothink to acknowledge return; } this._incomingAckId = this._incomingMsgId; const msg = new ProtocolMessage(3 /* ProtocolMessageType.Ack */, 0, this._incomingAckId, getEmptyBuffer()); this._socketWriter.write(msg); } _sendKeepAlive() { this._incomingAckId = this._incomingMsgId; const msg = new ProtocolMessage(9 /* ProtocolMessageType.KeepAlive */, 0, this._incomingAckId, getEmptyBuffer()); this._socketWriter.write(msg); } _sendLatencyMeasurementRequest(buffer) { this._incomingAckId = this._incomingMsgId; const msg = new ProtocolMessage(10 /* ProtocolMessageType.LatencyMeasurementRequest */, 0, this._incomingAckId, buffer); this._socketWriter.write(msg); } _sendLatencyMeasurementResponse(buffer) { this._incomingAckId = this._incomingMsgId; const msg = new ProtocolMessage(11 /* ProtocolMessageType.LatencyMeasurementResponse */, 0, this._incomingAckId, buffer); this._socketWriter.write(msg); } } class LatencyMonitor extends Disposable { _onSendLatencyRequest = this._register(new Emitter()); onSendLatencyRequest = this._onSendLatencyRequest.event; _onHighRoundTripTime = this._register(new Emitter()); onHighRoundTripTime = this._onHighRoundTripTime.event; _onDidChangeConnectionHealth = this._register(new Emitter()); onDidChangeConnectionHealth = this._onDidChangeConnectionHealth.event; _measureLatencyTimer = this._register(new IntervalTimer()); /** * Timestamp of our last latency request message sent to the other host. */ _lastLatencyMeasurementSent = -1; /** * ID separate from the regular message IDs. Used to match up latency * requests with responses so we know we're timing the right message * even if a reconnection occurs. */ _lastLatencyMeasurementId = 0; /** * Circular buffer of latency measurements */ _latencySamples = Array.from({ length: 5 /* ProtocolConstants.LatencySampleCount */ }, (_) => 0); _latencySampleIndex = 0; _connectionHealth = 0 /* ConnectionHealth.Good */; constructor() { super(); } start() { this._measureLatencyTimer.cancelAndSet(() => { this._lastLatencyMeasurementSent = Date.now(); const measurementId = ++this._lastLatencyMeasurementId; const buffer = VSBuffer.alloc(4); buffer.writeUInt32BE(measurementId, 0); this._onSendLatencyRequest.fire(buffer); }, 60000 /* ProtocolConstants.LatencySampleTime */); } handleResponse(buffer) { if (buffer.byteLength !== 4) { // invalid measurementId return; } const measurementId = buffer.readUInt32BE(0); if (this._lastLatencyMeasurementSent <= 0 || measurementId !== this._lastLatencyMeasurementId) { // invalid measurementId return; } const roundtripTime = Date.now() - this._lastLatencyMeasurementSent; const sampleIndex = this._latencySampleIndex++; this._latencySamples[sampleIndex % this._latencySamples.length] = roundtripTime; const previousConnectionHealth = this._connectionHealth; const highLatencySampleCount = this._latencySamples.filter(s => s >= 1000 /* ProtocolConstants.HighLatencyTimeThreshold */).length; this._connectionHealth = (highLatencySampleCount >= 3 /* ProtocolConstants.HighLatencySampleThreshold */ ? 1 /* ConnectionHealth.Poor */ : 0 /* ConnectionHealth.Good */); if (roundtripTime > 1000 /* ProtocolConstants.HighLatencyTimeThreshold */) { this._onHighRoundTripTime.fire(new HighRoundTripTimeEvent(roundtripTime, highLatencySampleCount)); } if (previousConnectionHealth !== this._connectionHealth) { this._onDidChangeConnectionHealth.fire(this._connectionHealth); } } } // (() => { // if (!SocketDiagnostics.enableDiagnostics) { // return; // } // if (typeof require.__$__nodeRequire !== 'function') { // console.log(`Can only log socket diagnostics on native platforms.`); // return; // } // const type = ( // process.argv.includes('--type=renderer') // ? 'renderer' // : (process.argv.includes('--type=extensionHost') // ? 'extensionHost' // : (process.argv.some(item => item.includes('server-main')) // ? 'server' // : 'unknown' // ) // ) // ); // setTimeout(() => { // SocketDiagnostics.records.forEach(r => { // if (r.buff) { // r.data = Buffer.from(r.buff.buffer).toString('base64'); // r.buff = undefined; // } // }); // const fs = <typeof import('fs')>require.__$__nodeRequire('fs'); // const path = <typeof import('path')>require.__$__nodeRequire('path'); // const logPath = path.join(process.cwd(),`${type}-${process.pid}`); // console.log(`dumping socket diagnostics at ${logPath}`); // fs.writeFileSync(logPath, JSON.stringify(SocketDiagnostics.records)); // }, 20000); // })();