mirror of
https://github.com/coder/code-server.git
synced 2026-05-12 23:37:25 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
29
lib/vscode/src/vs/base/parts/ipc/common/ipc.electron.ts
Normal file
29
lib/vscode/src/vs/base/parts/ipc/common/ipc.electron.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
export interface Sender {
|
||||
send(channel: string, msg: unknown): void;
|
||||
}
|
||||
|
||||
export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
constructor(private sender: Sender, readonly onMessage: Event<VSBuffer>) { }
|
||||
|
||||
send(message: VSBuffer): void {
|
||||
try {
|
||||
this.sender.send('vscode:message', message.buffer);
|
||||
} catch (e) {
|
||||
// systems are going down
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.sender.send('vscode:disconnect', null);
|
||||
}
|
||||
}
|
||||
902
lib/vscode/src/vs/base/parts/ipc/common/ipc.net.ts
Normal file
902
lib/vscode/src/vs/base/parts/ipc/common/ipc.net.ts
Normal file
@@ -0,0 +1,902 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol, IPCClient, IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as process from 'vs/base/common/process';
|
||||
|
||||
export interface ISocket extends IDisposable {
|
||||
onData(listener: (e: VSBuffer) => void): IDisposable;
|
||||
onClose(listener: () => void): IDisposable;
|
||||
onEnd(listener: () => void): IDisposable;
|
||||
write(buffer: VSBuffer): void;
|
||||
end(): void;
|
||||
drain(): Promise<void>;
|
||||
}
|
||||
|
||||
let emptyBuffer: VSBuffer | null = null;
|
||||
function getEmptyBuffer(): VSBuffer {
|
||||
if (!emptyBuffer) {
|
||||
emptyBuffer = VSBuffer.alloc(0);
|
||||
}
|
||||
return emptyBuffer;
|
||||
}
|
||||
|
||||
export class ChunkStream {
|
||||
|
||||
private _chunks: VSBuffer[];
|
||||
private _totalLength: number;
|
||||
|
||||
public get byteLength() {
|
||||
return this._totalLength;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._chunks = [];
|
||||
this._totalLength = 0;
|
||||
}
|
||||
|
||||
public acceptChunk(buff: VSBuffer) {
|
||||
this._chunks.push(buff);
|
||||
this._totalLength += buff.byteLength;
|
||||
}
|
||||
|
||||
public read(byteCount: number): VSBuffer {
|
||||
return this._read(byteCount, true);
|
||||
}
|
||||
|
||||
public peek(byteCount: number): VSBuffer {
|
||||
return this._read(byteCount, false);
|
||||
}
|
||||
|
||||
private _read(byteCount: number, advance: boolean): VSBuffer {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let 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;
|
||||
}
|
||||
}
|
||||
|
||||
const enum ProtocolMessageType {
|
||||
None = 0,
|
||||
Regular = 1,
|
||||
Control = 2,
|
||||
Ack = 3,
|
||||
KeepAlive = 4,
|
||||
Disconnect = 5
|
||||
}
|
||||
|
||||
export const enum ProtocolConstants {
|
||||
HeaderLength = 13,
|
||||
/**
|
||||
* Send an Acknowledge message at most 2 seconds later...
|
||||
*/
|
||||
AcknowledgeTime = 2000, // 2 seconds
|
||||
/**
|
||||
* If there is a message that has been unacknowledged for 10 seconds, consider the connection closed...
|
||||
*/
|
||||
AcknowledgeTimeoutTime = 20000, // 20 seconds
|
||||
/**
|
||||
* Send at least a message every 5s for keep alive reasons.
|
||||
*/
|
||||
KeepAliveTime = 5000, // 5 seconds
|
||||
/**
|
||||
* If there is no message received for 10 seconds, consider the connection closed...
|
||||
*/
|
||||
KeepAliveTimeoutTime = 20000, // 20 seconds
|
||||
/**
|
||||
* If there is no reconnection within this time-frame, consider the connection permanently closed...
|
||||
*/
|
||||
ReconnectionGraceTime = 3 * 60 * 60 * 1000, // 3hrs
|
||||
/**
|
||||
* Maximal grace time between the first and the last reconnection...
|
||||
*/
|
||||
ReconnectionShortGraceTime = 5 * 60 * 1000, // 5min
|
||||
}
|
||||
|
||||
class ProtocolMessage {
|
||||
|
||||
public writtenTime: number;
|
||||
|
||||
constructor(
|
||||
public readonly type: ProtocolMessageType,
|
||||
public readonly id: number,
|
||||
public readonly ack: number,
|
||||
public readonly data: VSBuffer
|
||||
) {
|
||||
this.writtenTime = 0;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.data.byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
class ProtocolReader extends Disposable {
|
||||
|
||||
private readonly _socket: ISocket;
|
||||
private _isDisposed: boolean;
|
||||
private readonly _incomingData: ChunkStream;
|
||||
public lastReadTime: number;
|
||||
|
||||
private readonly _onMessage = this._register(new Emitter<ProtocolMessage>());
|
||||
public readonly onMessage: Event<ProtocolMessage> = this._onMessage.event;
|
||||
|
||||
private readonly _state = {
|
||||
readHead: true,
|
||||
readLen: ProtocolConstants.HeaderLength,
|
||||
messageType: ProtocolMessageType.None,
|
||||
id: 0,
|
||||
ack: 0
|
||||
};
|
||||
|
||||
constructor(socket: ISocket) {
|
||||
super();
|
||||
this._socket = socket;
|
||||
this._isDisposed = false;
|
||||
this._incomingData = new ChunkStream();
|
||||
this._register(this._socket.onData(data => this.acceptChunk(data)));
|
||||
this.lastReadTime = Date.now();
|
||||
}
|
||||
|
||||
public acceptChunk(data: VSBuffer | null): void {
|
||||
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);
|
||||
} 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 = ProtocolConstants.HeaderLength;
|
||||
this._state.messageType = ProtocolMessageType.None;
|
||||
this._state.id = 0;
|
||||
this._state.ack = 0;
|
||||
|
||||
this._onMessage.fire(new ProtocolMessage(messageType, id, ack, buff));
|
||||
|
||||
if (this._isDisposed) {
|
||||
// check if an event listener lead to our disposal
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readEntireBuffer(): VSBuffer {
|
||||
return this._incomingData.read(this._incomingData.byteLength);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ProtocolWriter {
|
||||
|
||||
private _isDisposed: boolean;
|
||||
private readonly _socket: ISocket;
|
||||
private _data: VSBuffer[];
|
||||
private _totalLength: number;
|
||||
public lastWriteTime: number;
|
||||
|
||||
constructor(socket: ISocket) {
|
||||
this._isDisposed = false;
|
||||
this._socket = socket;
|
||||
this._data = [];
|
||||
this._totalLength = 0;
|
||||
this.lastWriteTime = 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.flush();
|
||||
this._isDisposed = true;
|
||||
}
|
||||
|
||||
public drain(): Promise<void> {
|
||||
this.flush();
|
||||
return this._socket.drain();
|
||||
}
|
||||
|
||||
public flush(): void {
|
||||
// flush
|
||||
this._writeNow();
|
||||
}
|
||||
|
||||
public write(msg: ProtocolMessage) {
|
||||
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(ProtocolConstants.HeaderLength);
|
||||
header.writeUInt8(msg.type, 0);
|
||||
header.writeUInt32BE(msg.id, 1);
|
||||
header.writeUInt32BE(msg.ack, 5);
|
||||
header.writeUInt32BE(msg.data.byteLength, 9);
|
||||
this._writeSoon(header, msg.data);
|
||||
}
|
||||
|
||||
private _bufferAdd(head: VSBuffer, body: VSBuffer): boolean {
|
||||
const wasEmpty = this._totalLength === 0;
|
||||
this._data.push(head, body);
|
||||
this._totalLength += head.byteLength + body.byteLength;
|
||||
return wasEmpty;
|
||||
}
|
||||
|
||||
private _bufferTake(): VSBuffer {
|
||||
const ret = VSBuffer.concat(this._data, this._totalLength);
|
||||
this._data.length = 0;
|
||||
this._totalLength = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private _writeSoon(header: VSBuffer, data: VSBuffer): void {
|
||||
if (this._bufferAdd(header, data)) {
|
||||
platform.setImmediate(() => {
|
||||
this._writeNow();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _writeNow(): void {
|
||||
if (this._totalLength === 0) {
|
||||
return;
|
||||
}
|
||||
this._socket.write(this._bufferTake());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 implements IMessagePassingProtocol {
|
||||
|
||||
private _socket: ISocket;
|
||||
private _socketWriter: ProtocolWriter;
|
||||
private _socketReader: ProtocolReader;
|
||||
|
||||
private readonly _onMessage = new Emitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
|
||||
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
readonly onClose: Event<void> = this._onClose.event;
|
||||
|
||||
constructor(socket: ISocket) {
|
||||
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 === ProtocolMessageType.Regular) {
|
||||
this._onMessage.fire(msg.data);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._socket.onClose(() => this._onClose.fire()));
|
||||
}
|
||||
|
||||
drain(): Promise<void> {
|
||||
return this._socketWriter.drain();
|
||||
}
|
||||
|
||||
getSocket(): ISocket {
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
sendDisconnect(): void {
|
||||
// Nothing to do...
|
||||
}
|
||||
|
||||
send(buffer: VSBuffer): void {
|
||||
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.Regular, 0, 0, buffer));
|
||||
}
|
||||
}
|
||||
|
||||
export class Client<TContext = string> extends IPCClient<TContext> {
|
||||
|
||||
static fromSocket<TContext = string>(socket: ISocket, id: TContext): Client<TContext> {
|
||||
return new Client(new Protocol(socket), id);
|
||||
}
|
||||
|
||||
get onClose(): Event<void> { return this.protocol.onClose; }
|
||||
|
||||
constructor(private protocol: Protocol | PersistentProtocol, id: TContext, ipcLogger: IIPCLogger | null = null) {
|
||||
super(protocol, id, ipcLogger);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
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<T> {
|
||||
private _emitter: Emitter<T>;
|
||||
public readonly event: Event<T>;
|
||||
|
||||
private _hasListeners = false;
|
||||
private _isDeliveringMessages = false;
|
||||
private _bufferedMessages: T[] = [];
|
||||
|
||||
constructor() {
|
||||
this._emitter = new Emitter<T>({
|
||||
onFirstListenerAdd: () => {
|
||||
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 nextTick and not other types of timeouts
|
||||
process.nextTick(() => this._deliverMessages());
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
this._hasListeners = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.event = this._emitter.event;
|
||||
}
|
||||
|
||||
private _deliverMessages(): void {
|
||||
if (this._isDeliveringMessages) {
|
||||
return;
|
||||
}
|
||||
this._isDeliveringMessages = true;
|
||||
while (this._hasListeners && this._bufferedMessages.length > 0) {
|
||||
this._emitter.fire(this._bufferedMessages.shift()!);
|
||||
}
|
||||
this._isDeliveringMessages = false;
|
||||
}
|
||||
|
||||
public fire(event: T): void {
|
||||
if (this._hasListeners) {
|
||||
if (this._bufferedMessages.length > 0) {
|
||||
this._bufferedMessages.push(event);
|
||||
} else {
|
||||
this._emitter.fire(event);
|
||||
}
|
||||
} else {
|
||||
this._bufferedMessages.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
public flushBuffer(): void {
|
||||
this._bufferedMessages = [];
|
||||
}
|
||||
}
|
||||
|
||||
class QueueElement<T> {
|
||||
public readonly data: T;
|
||||
public next: QueueElement<T> | null;
|
||||
|
||||
constructor(data: T) {
|
||||
this.data = data;
|
||||
this.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
class Queue<T> {
|
||||
|
||||
private _first: QueueElement<T> | null;
|
||||
private _last: QueueElement<T> | null;
|
||||
|
||||
constructor() {
|
||||
this._first = null;
|
||||
this._last = null;
|
||||
}
|
||||
|
||||
public peek(): T | null {
|
||||
if (!this._first) {
|
||||
return null;
|
||||
}
|
||||
return this._first.data;
|
||||
}
|
||||
|
||||
public toArray(): T[] {
|
||||
let result: T[] = [], resultLen = 0;
|
||||
let it = this._first;
|
||||
while (it) {
|
||||
result[resultLen++] = it.data;
|
||||
it = it.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public pop(): void {
|
||||
if (!this._first) {
|
||||
return;
|
||||
}
|
||||
if (this._first === this._last) {
|
||||
this._first = null;
|
||||
this._last = null;
|
||||
return;
|
||||
}
|
||||
this._first = this._first.next;
|
||||
}
|
||||
|
||||
public push(item: T): void {
|
||||
const element = new QueueElement(item);
|
||||
if (!this._first) {
|
||||
this._first = element;
|
||||
this._last = element;
|
||||
return;
|
||||
}
|
||||
this._last!.next = element;
|
||||
this._last = element;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadEstimator {
|
||||
|
||||
private static _HISTORY_LENGTH = 10;
|
||||
private static _INSTANCE: LoadEstimator | null = null;
|
||||
public static getInstance(): LoadEstimator {
|
||||
if (!LoadEstimator._INSTANCE) {
|
||||
LoadEstimator._INSTANCE = new LoadEstimator();
|
||||
}
|
||||
return LoadEstimator._INSTANCE;
|
||||
}
|
||||
|
||||
private lastRuns: number[];
|
||||
|
||||
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)
|
||||
*/
|
||||
public load(): number {
|
||||
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;
|
||||
}
|
||||
|
||||
public hasHighLoad(): boolean {
|
||||
return this.load() >= 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 implements IMessagePassingProtocol {
|
||||
|
||||
private _isReconnecting: boolean;
|
||||
|
||||
private _outgoingUnackMsg: Queue<ProtocolMessage>;
|
||||
private _outgoingMsgId: number;
|
||||
private _outgoingAckId: number;
|
||||
private _outgoingAckTimeout: any | null;
|
||||
|
||||
private _incomingMsgId: number;
|
||||
private _incomingAckId: number;
|
||||
private _incomingMsgLastTime: number;
|
||||
private _incomingAckTimeout: any | null;
|
||||
|
||||
private _outgoingKeepAliveTimeout: any | null;
|
||||
private _incomingKeepAliveTimeout: any | null;
|
||||
|
||||
private _socket: ISocket;
|
||||
private _socketWriter: ProtocolWriter;
|
||||
private _socketReader: ProtocolReader;
|
||||
private _socketDisposables: IDisposable[];
|
||||
|
||||
private readonly _loadEstimator = LoadEstimator.getInstance();
|
||||
|
||||
private readonly _onControlMessage = new BufferedEmitter<VSBuffer>();
|
||||
readonly onControlMessage: Event<VSBuffer> = this._onControlMessage.event;
|
||||
|
||||
private readonly _onMessage = new BufferedEmitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
|
||||
|
||||
private readonly _onClose = new BufferedEmitter<void>();
|
||||
readonly onClose: Event<void> = this._onClose.event;
|
||||
|
||||
private readonly _onSocketClose = new BufferedEmitter<void>();
|
||||
readonly onSocketClose: Event<void> = this._onSocketClose.event;
|
||||
|
||||
private readonly _onSocketTimeout = new BufferedEmitter<void>();
|
||||
readonly onSocketTimeout: Event<void> = this._onSocketTimeout.event;
|
||||
|
||||
public get unacknowledgedCount(): number {
|
||||
return this._outgoingMsgId - this._outgoingAckId;
|
||||
}
|
||||
|
||||
constructor(socket: ISocket, initialChunk: VSBuffer | null = null) {
|
||||
this._isReconnecting = false;
|
||||
this._outgoingUnackMsg = new Queue<ProtocolMessage>();
|
||||
this._outgoingMsgId = 0;
|
||||
this._outgoingAckId = 0;
|
||||
this._outgoingAckTimeout = null;
|
||||
|
||||
this._incomingMsgId = 0;
|
||||
this._incomingAckId = 0;
|
||||
this._incomingMsgLastTime = 0;
|
||||
this._incomingAckTimeout = null;
|
||||
|
||||
this._outgoingKeepAliveTimeout = null;
|
||||
this._incomingKeepAliveTimeout = null;
|
||||
|
||||
this._socketDisposables = [];
|
||||
this._socket = socket;
|
||||
this._socketWriter = new ProtocolWriter(this._socket);
|
||||
this._socketDisposables.push(this._socketWriter);
|
||||
this._socketReader = new ProtocolReader(this._socket);
|
||||
this._socketDisposables.push(this._socketReader);
|
||||
this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg)));
|
||||
this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire()));
|
||||
if (initialChunk) {
|
||||
this._socketReader.acceptChunk(initialChunk);
|
||||
}
|
||||
|
||||
this._sendKeepAliveCheck();
|
||||
this._recvKeepAliveCheck();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._outgoingAckTimeout) {
|
||||
clearTimeout(this._outgoingAckTimeout);
|
||||
this._outgoingAckTimeout = null;
|
||||
}
|
||||
if (this._incomingAckTimeout) {
|
||||
clearTimeout(this._incomingAckTimeout);
|
||||
this._incomingAckTimeout = null;
|
||||
}
|
||||
if (this._outgoingKeepAliveTimeout) {
|
||||
clearTimeout(this._outgoingKeepAliveTimeout);
|
||||
this._outgoingKeepAliveTimeout = null;
|
||||
}
|
||||
if (this._incomingKeepAliveTimeout) {
|
||||
clearTimeout(this._incomingKeepAliveTimeout);
|
||||
this._incomingKeepAliveTimeout = null;
|
||||
}
|
||||
this._socketDisposables = dispose(this._socketDisposables);
|
||||
}
|
||||
|
||||
drain(): Promise<void> {
|
||||
return this._socketWriter.drain();
|
||||
}
|
||||
|
||||
sendDisconnect(): void {
|
||||
const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer());
|
||||
this._socketWriter.write(msg);
|
||||
this._socketWriter.flush();
|
||||
}
|
||||
|
||||
private _sendKeepAliveCheck(): void {
|
||||
if (this._outgoingKeepAliveTimeout) {
|
||||
// there will be a check in the near future
|
||||
return;
|
||||
}
|
||||
|
||||
const timeSinceLastOutgoingMsg = Date.now() - this._socketWriter.lastWriteTime;
|
||||
if (timeSinceLastOutgoingMsg >= ProtocolConstants.KeepAliveTime) {
|
||||
// sufficient time has passed since last message was written,
|
||||
// and no message from our side needed to be sent in the meantime,
|
||||
// so we will send a message containing only a keep alive.
|
||||
const msg = new ProtocolMessage(ProtocolMessageType.KeepAlive, 0, 0, getEmptyBuffer());
|
||||
this._socketWriter.write(msg);
|
||||
this._sendKeepAliveCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
this._outgoingKeepAliveTimeout = setTimeout(() => {
|
||||
this._outgoingKeepAliveTimeout = null;
|
||||
this._sendKeepAliveCheck();
|
||||
}, ProtocolConstants.KeepAliveTime - timeSinceLastOutgoingMsg + 5);
|
||||
}
|
||||
|
||||
private _recvKeepAliveCheck(): void {
|
||||
if (this._incomingKeepAliveTimeout) {
|
||||
// there will be a check in the near future
|
||||
return;
|
||||
}
|
||||
|
||||
const timeSinceLastIncomingMsg = Date.now() - this._socketReader.lastReadTime;
|
||||
if (timeSinceLastIncomingMsg >= ProtocolConstants.KeepAliveTimeoutTime) {
|
||||
// It's been a long time since we received a server message
|
||||
// But this might be caused by the event loop being busy and failing to read messages
|
||||
if (!this._loadEstimator.hasHighLoad()) {
|
||||
// Trash the socket
|
||||
this._onSocketTimeout.fire(undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._incomingKeepAliveTimeout = setTimeout(() => {
|
||||
this._incomingKeepAliveTimeout = null;
|
||||
this._recvKeepAliveCheck();
|
||||
}, Math.max(ProtocolConstants.KeepAliveTimeoutTime - timeSinceLastIncomingMsg, 0) + 5);
|
||||
}
|
||||
|
||||
public getSocket(): ISocket {
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
public beginAcceptReconnection(socket: ISocket, initialDataChunk: VSBuffer | null): void {
|
||||
this._isReconnecting = true;
|
||||
|
||||
this._socketDisposables = dispose(this._socketDisposables);
|
||||
this._onControlMessage.flushBuffer();
|
||||
this._onSocketClose.flushBuffer();
|
||||
this._onSocketTimeout.flushBuffer();
|
||||
this._socket.dispose();
|
||||
|
||||
this._socket = socket;
|
||||
this._socketWriter = new ProtocolWriter(this._socket);
|
||||
this._socketDisposables.push(this._socketWriter);
|
||||
this._socketReader = new ProtocolReader(this._socket);
|
||||
this._socketDisposables.push(this._socketReader);
|
||||
this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg)));
|
||||
this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire()));
|
||||
this._socketReader.acceptChunk(initialDataChunk);
|
||||
}
|
||||
|
||||
public endAcceptReconnection(): void {
|
||||
this._isReconnecting = false;
|
||||
|
||||
// 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();
|
||||
|
||||
this._sendKeepAliveCheck();
|
||||
this._recvKeepAliveCheck();
|
||||
}
|
||||
|
||||
public acceptDisconnect(): void {
|
||||
this._onClose.fire();
|
||||
}
|
||||
|
||||
private _receiveMessage(msg: ProtocolMessage): void {
|
||||
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);
|
||||
}
|
||||
|
||||
if (msg.type === ProtocolMessageType.Regular) {
|
||||
if (msg.id > this._incomingMsgId) {
|
||||
if (msg.id !== this._incomingMsgId + 1) {
|
||||
console.error(`PROTOCOL CORRUPTION, LAST SAW MSG ${this._incomingMsgId} AND HAVE NOW RECEIVED MSG ${msg.id}`);
|
||||
}
|
||||
this._incomingMsgId = msg.id;
|
||||
this._incomingMsgLastTime = Date.now();
|
||||
this._sendAckCheck();
|
||||
this._onMessage.fire(msg.data);
|
||||
}
|
||||
} else if (msg.type === ProtocolMessageType.Control) {
|
||||
this._onControlMessage.fire(msg.data);
|
||||
} else if (msg.type === ProtocolMessageType.Disconnect) {
|
||||
this._onClose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
readEntireBuffer(): VSBuffer {
|
||||
return this._socketReader.readEntireBuffer();
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this._socketWriter.flush();
|
||||
}
|
||||
|
||||
send(buffer: VSBuffer): void {
|
||||
const myId = ++this._outgoingMsgId;
|
||||
this._incomingAckId = this._incomingMsgId;
|
||||
const msg = new ProtocolMessage(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: VSBuffer): void {
|
||||
const msg = new ProtocolMessage(ProtocolMessageType.Control, 0, 0, buffer);
|
||||
this._socketWriter.write(msg);
|
||||
}
|
||||
|
||||
private _sendAckCheck(): void {
|
||||
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 >= 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();
|
||||
}, ProtocolConstants.AcknowledgeTime - timeSinceLastIncomingMsg + 5);
|
||||
}
|
||||
|
||||
private _recvAckCheck(): void {
|
||||
if (this._outgoingMsgId <= this._outgoingAckId) {
|
||||
// everything has been acknowledged
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._outgoingAckTimeout) {
|
||||
// there will be a check in the near future
|
||||
return;
|
||||
}
|
||||
|
||||
const oldestUnacknowledgedMsg = this._outgoingUnackMsg.peek()!;
|
||||
const timeSinceOldestUnacknowledgedMsg = Date.now() - oldestUnacknowledgedMsg.writtenTime;
|
||||
if (timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.AcknowledgeTimeoutTime) {
|
||||
// It's been a long time since our sent message was acknowledged
|
||||
// But this might be caused by the event loop being busy and failing to read messages
|
||||
if (!this._loadEstimator.hasHighLoad()) {
|
||||
// Trash the socket
|
||||
this._onSocketTimeout.fire(undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._outgoingAckTimeout = setTimeout(() => {
|
||||
this._outgoingAckTimeout = null;
|
||||
this._recvAckCheck();
|
||||
}, Math.max(ProtocolConstants.AcknowledgeTimeoutTime - timeSinceOldestUnacknowledgedMsg, 0) + 5);
|
||||
}
|
||||
|
||||
private _sendAck(): void {
|
||||
if (this._incomingMsgId <= this._incomingAckId) {
|
||||
// nothink to acknowledge
|
||||
return;
|
||||
}
|
||||
|
||||
this._incomingAckId = this._incomingMsgId;
|
||||
const msg = new ProtocolMessage(ProtocolMessageType.Ack, 0, this._incomingAckId, getEmptyBuffer());
|
||||
this._socketWriter.write(msg);
|
||||
}
|
||||
}
|
||||
1216
lib/vscode/src/vs/base/parts/ipc/common/ipc.ts
Normal file
1216
lib/vscode/src/vs/base/parts/ipc/common/ipc.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
interface IIPCEvent {
|
||||
event: { sender: WebContents; };
|
||||
message: Buffer | null;
|
||||
}
|
||||
|
||||
function createScopedOnMessageEvent(senderId: number, eventName: string): Event<VSBuffer | null> {
|
||||
const onMessage = Event.fromNodeEventEmitter<IIPCEvent>(ipcMain, eventName, (event, message) => ({ event, message }));
|
||||
const onMessageFromSender = Event.filter(onMessage, ({ event }) => event.sender.id === senderId);
|
||||
return Event.map(onMessageFromSender, ({ message }) => message ? VSBuffer.wrap(message) : message);
|
||||
}
|
||||
|
||||
export class Server extends IPCServer {
|
||||
|
||||
private static readonly Clients = new Map<number, IDisposable>();
|
||||
|
||||
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
|
||||
const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'vscode:hello', ({ sender }) => sender);
|
||||
|
||||
return Event.map(onHello, webContents => {
|
||||
const id = webContents.id;
|
||||
const client = Server.Clients.get(id);
|
||||
|
||||
if (client) {
|
||||
client.dispose();
|
||||
}
|
||||
|
||||
const onDidClientReconnect = new Emitter<void>();
|
||||
Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire()));
|
||||
|
||||
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<VSBuffer>;
|
||||
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event);
|
||||
const protocol = new Protocol(webContents, onMessage);
|
||||
|
||||
return { protocol, onDidClientDisconnect };
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(Server.getOnDidClientConnect());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
export class Client extends IPCClient implements IDisposable {
|
||||
|
||||
private protocol: Protocol;
|
||||
|
||||
private static createProtocol(): Protocol {
|
||||
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'vscode:message', (_, message) => VSBuffer.wrap(message));
|
||||
ipcRenderer.send('vscode:hello');
|
||||
return new Protocol(ipcRenderer, onMessage);
|
||||
}
|
||||
|
||||
constructor(id: string) {
|
||||
const protocol = Client.createProtocol();
|
||||
super(protocol, id);
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.protocol.dispose();
|
||||
}
|
||||
}
|
||||
288
lib/vscode/src/vs/base/parts/ipc/node/ipc.cp.ts
Normal file
288
lib/vscode/src/vs/base/parts/ipc/node/ipc.cp.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChildProcess, fork, ForkOptions } from 'child_process';
|
||||
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Delayer, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { createQueuedSender } from 'vs/base/node/processes';
|
||||
import { IChannel, ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { isRemoteConsoleLog, log } from 'vs/base/common/console';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
/**
|
||||
* This implementation doesn't perform well since it uses base64 encoding for buffers.
|
||||
* We should move all implementations to use named ipc.net, so we stop depending on cp.fork.
|
||||
*/
|
||||
|
||||
export class Server<TContext extends string> extends IPCServer<TContext> {
|
||||
constructor(ctx: TContext) {
|
||||
super({
|
||||
send: r => {
|
||||
try {
|
||||
if (process.send) {
|
||||
process.send((<Buffer>r.buffer).toString('base64'));
|
||||
}
|
||||
} catch (e) { /* not much to do */ }
|
||||
},
|
||||
onMessage: Event.fromNodeEventEmitter(process, 'message', msg => VSBuffer.wrap(Buffer.from(msg, 'base64')))
|
||||
}, ctx);
|
||||
|
||||
process.once('disconnect', () => this.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
export interface IIPCOptions {
|
||||
|
||||
/**
|
||||
* A descriptive name for the server this connection is to. Used in logging.
|
||||
*/
|
||||
serverName: string;
|
||||
|
||||
/**
|
||||
* Time in millies before killing the ipc process. The next request after killing will start it again.
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* Arguments to the module to execute.
|
||||
*/
|
||||
args?: string[];
|
||||
|
||||
/**
|
||||
* Environment key-value pairs to be passed to the process that gets spawned for the ipc.
|
||||
*/
|
||||
env?: any;
|
||||
|
||||
/**
|
||||
* Allows to assign a debug port for debugging the application executed.
|
||||
*/
|
||||
debug?: number;
|
||||
|
||||
/**
|
||||
* Allows to assign a debug port for debugging the application and breaking it on the first line.
|
||||
*/
|
||||
debugBrk?: number;
|
||||
|
||||
/**
|
||||
* See https://github.com/microsoft/vscode/issues/27665
|
||||
* Allows to pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`.
|
||||
* e.g. Launching the extension host process with `--inspect-brk=xxx` and then forking a process from the extension host
|
||||
* results in the forked process inheriting `--inspect-brk=xxx`.
|
||||
*/
|
||||
freshExecArgv?: boolean;
|
||||
|
||||
/**
|
||||
* Enables our createQueuedSender helper for this Client. Uses a queue when the internal Node.js queue is
|
||||
* full of messages - see notes on that method.
|
||||
*/
|
||||
useQueue?: boolean;
|
||||
}
|
||||
|
||||
export class Client implements IChannelClient, IDisposable {
|
||||
|
||||
private disposeDelayer: Delayer<void> | undefined;
|
||||
private activeRequests = new Set<IDisposable>();
|
||||
private child: ChildProcess | null;
|
||||
private _client: IPCClient | null;
|
||||
private channels = new Map<string, IChannel>();
|
||||
|
||||
private readonly _onDidProcessExit = new Emitter<{ code: number, signal: string }>();
|
||||
readonly onDidProcessExit = this._onDidProcessExit.event;
|
||||
|
||||
constructor(private modulePath: string, private options: IIPCOptions) {
|
||||
const timeout = options && options.timeout ? options.timeout : 60000;
|
||||
this.disposeDelayer = new Delayer<void>(timeout);
|
||||
this.child = null;
|
||||
this._client = null;
|
||||
}
|
||||
|
||||
getChannel<T extends IChannel>(channelName: string): T {
|
||||
const that = this;
|
||||
|
||||
return {
|
||||
call<T>(command: string, arg?: any, cancellationToken?: CancellationToken): Promise<T> {
|
||||
return that.requestPromise<T>(channelName, command, arg, cancellationToken);
|
||||
},
|
||||
listen(event: string, arg?: any) {
|
||||
return that.requestEvent(channelName, event, arg);
|
||||
}
|
||||
} as T;
|
||||
}
|
||||
|
||||
protected requestPromise<T>(channelName: string, name: string, arg?: any, cancellationToken = CancellationToken.None): Promise<T> {
|
||||
if (!this.disposeDelayer) {
|
||||
return Promise.reject(new Error('disposed'));
|
||||
}
|
||||
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return Promise.reject(errors.canceled());
|
||||
}
|
||||
|
||||
this.disposeDelayer.cancel();
|
||||
|
||||
const channel = this.getCachedChannel(channelName);
|
||||
const result = createCancelablePromise(token => channel.call<T>(name, arg, token));
|
||||
const cancellationTokenListener = cancellationToken.onCancellationRequested(() => result.cancel());
|
||||
|
||||
const disposable = toDisposable(() => result.cancel());
|
||||
this.activeRequests.add(disposable);
|
||||
|
||||
result.finally(() => {
|
||||
cancellationTokenListener.dispose();
|
||||
this.activeRequests.delete(disposable);
|
||||
|
||||
if (this.activeRequests.size === 0 && this.disposeDelayer) {
|
||||
this.disposeDelayer.trigger(() => this.disposeClient());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected requestEvent<T>(channelName: string, name: string, arg?: any): Event<T> {
|
||||
if (!this.disposeDelayer) {
|
||||
return Event.None;
|
||||
}
|
||||
|
||||
this.disposeDelayer.cancel();
|
||||
|
||||
let listener: IDisposable;
|
||||
const emitter = new Emitter<any>({
|
||||
onFirstListenerAdd: () => {
|
||||
const channel = this.getCachedChannel(channelName);
|
||||
const event: Event<T> = channel.listen(name, arg);
|
||||
|
||||
listener = event(emitter.fire, emitter);
|
||||
this.activeRequests.add(listener);
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
this.activeRequests.delete(listener);
|
||||
listener.dispose();
|
||||
|
||||
if (this.activeRequests.size === 0 && this.disposeDelayer) {
|
||||
this.disposeDelayer.trigger(() => this.disposeClient());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
private get client(): IPCClient {
|
||||
if (!this._client) {
|
||||
const args = this.options && this.options.args ? this.options.args : [];
|
||||
const forkOpts: ForkOptions = Object.create(null);
|
||||
|
||||
forkOpts.env = { ...deepClone(process.env), 'VSCODE_PARENT_PID': String(process.pid) };
|
||||
|
||||
if (this.options && this.options.env) {
|
||||
forkOpts.env = { ...forkOpts.env, ...this.options.env };
|
||||
}
|
||||
|
||||
if (this.options && this.options.freshExecArgv) {
|
||||
forkOpts.execArgv = [];
|
||||
}
|
||||
|
||||
if (this.options && typeof this.options.debug === 'number') {
|
||||
forkOpts.execArgv = ['--nolazy', '--inspect=' + this.options.debug];
|
||||
}
|
||||
|
||||
if (this.options && typeof this.options.debugBrk === 'number') {
|
||||
forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk];
|
||||
}
|
||||
|
||||
if (isMacintosh && forkOpts.env) {
|
||||
// Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes
|
||||
// See https://github.com/microsoft/vscode/issues/105848
|
||||
delete forkOpts.env['DYLD_LIBRARY_PATH'];
|
||||
}
|
||||
|
||||
this.child = fork(this.modulePath, args, forkOpts);
|
||||
|
||||
const onMessageEmitter = new Emitter<VSBuffer>();
|
||||
const onRawMessage = Event.fromNodeEventEmitter(this.child, 'message', msg => msg);
|
||||
|
||||
onRawMessage(msg => {
|
||||
|
||||
// Handle remote console logs specially
|
||||
if (isRemoteConsoleLog(msg)) {
|
||||
log(msg, `IPC Library: ${this.options.serverName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Anything else goes to the outside
|
||||
onMessageEmitter.fire(VSBuffer.wrap(Buffer.from(msg, 'base64')));
|
||||
});
|
||||
|
||||
const sender = this.options.useQueue ? createQueuedSender(this.child) : this.child;
|
||||
const send = (r: VSBuffer) => this.child && this.child.connected && sender.send((<Buffer>r.buffer).toString('base64'));
|
||||
const onMessage = onMessageEmitter.event;
|
||||
const protocol = { send, onMessage };
|
||||
|
||||
this._client = new IPCClient(protocol);
|
||||
|
||||
const onExit = () => this.disposeClient();
|
||||
process.once('exit', onExit);
|
||||
|
||||
this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err));
|
||||
|
||||
this.child.on('exit', (code: any, signal: any) => {
|
||||
process.removeListener('exit' as 'loaded', onExit); // https://github.com/electron/electron/issues/21475
|
||||
|
||||
this.activeRequests.forEach(r => dispose(r));
|
||||
this.activeRequests.clear();
|
||||
|
||||
if (code !== 0 && signal !== 'SIGTERM') {
|
||||
console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal);
|
||||
}
|
||||
|
||||
if (this.disposeDelayer) {
|
||||
this.disposeDelayer.cancel();
|
||||
}
|
||||
this.disposeClient();
|
||||
this._onDidProcessExit.fire({ code, signal });
|
||||
});
|
||||
}
|
||||
|
||||
return this._client;
|
||||
}
|
||||
|
||||
private getCachedChannel(name: string): IChannel {
|
||||
let channel = this.channels.get(name);
|
||||
|
||||
if (!channel) {
|
||||
channel = this.client.getChannel(name);
|
||||
this.channels.set(name, channel);
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
private disposeClient() {
|
||||
if (this._client) {
|
||||
if (this.child) {
|
||||
this.child.kill();
|
||||
this.child = null;
|
||||
}
|
||||
this._client = null;
|
||||
this.channels.clear();
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._onDidProcessExit.dispose();
|
||||
if (this.disposeDelayer) {
|
||||
this.disposeDelayer.cancel();
|
||||
this.disposeDelayer = undefined;
|
||||
}
|
||||
this.disposeClient();
|
||||
this.activeRequests.clear();
|
||||
}
|
||||
}
|
||||
418
lib/vscode/src/vs/base/parts/ipc/node/ipc.net.ts
Normal file
418
lib/vscode/src/vs/base/parts/ipc/node/ipc.net.ts
Normal file
@@ -0,0 +1,418 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createHash } from 'crypto';
|
||||
import { Socket, Server as NetServer, createConnection, createServer } from 'net';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ISocket, Protocol, Client, ChunkStream } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Platform, platform } from 'vs/base/common/platform';
|
||||
|
||||
export class NodeSocket implements ISocket {
|
||||
public readonly socket: Socket;
|
||||
|
||||
constructor(socket: Socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.destroy();
|
||||
}
|
||||
|
||||
public onData(_listener: (e: VSBuffer) => void): IDisposable {
|
||||
const listener = (buff: Buffer) => _listener(VSBuffer.wrap(buff));
|
||||
this.socket.on('data', listener);
|
||||
return {
|
||||
dispose: () => this.socket.off('data', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
this.socket.on('close', listener);
|
||||
return {
|
||||
dispose: () => this.socket.off('close', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
this.socket.on('end', listener);
|
||||
return {
|
||||
dispose: () => this.socket.off('end', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
// return early if socket has been destroyed in the meantime
|
||||
if (this.socket.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we ignore the returned value from `write` because we would have to cached the data
|
||||
// anyways and nodejs is already doing that for us:
|
||||
// > https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback
|
||||
// > However, the false return value is only advisory and the writable stream will unconditionally
|
||||
// > accept and buffer chunk even if it has not been allowed to drain.
|
||||
try {
|
||||
this.socket.write(<Buffer>buffer.buffer);
|
||||
} catch (err) {
|
||||
if (err.code === 'EPIPE') {
|
||||
// An EPIPE exception at the wrong time can lead to a renderer process crash
|
||||
// so ignore the error since the socket will fire the close event soon anyways:
|
||||
// > https://nodejs.org/api/errors.html#errors_common_system_errors
|
||||
// > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no
|
||||
// > process to read the data. Commonly encountered at the net and http layers,
|
||||
// > indicative that the remote side of the stream being written to has been closed.
|
||||
return;
|
||||
}
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.socket.end();
|
||||
}
|
||||
|
||||
public drain(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.socket.bufferSize === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const finished = () => {
|
||||
this.socket.off('close', finished);
|
||||
this.socket.off('end', finished);
|
||||
this.socket.off('error', finished);
|
||||
this.socket.off('timeout', finished);
|
||||
this.socket.off('drain', finished);
|
||||
resolve();
|
||||
};
|
||||
this.socket.on('close', finished);
|
||||
this.socket.on('end', finished);
|
||||
this.socket.on('error', finished);
|
||||
this.socket.on('timeout', finished);
|
||||
this.socket.on('drain', finished);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const enum Constants {
|
||||
MinHeaderByteSize = 2
|
||||
}
|
||||
|
||||
const enum ReadState {
|
||||
PeekHeader = 1,
|
||||
ReadHeader = 2,
|
||||
ReadBody = 3,
|
||||
Fin = 4
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
*/
|
||||
export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
|
||||
public readonly socket: NodeSocket;
|
||||
private readonly _incomingData: ChunkStream;
|
||||
private readonly _onData = this._register(new Emitter<VSBuffer>());
|
||||
|
||||
private readonly _state = {
|
||||
state: ReadState.PeekHeader,
|
||||
readLen: Constants.MinHeaderByteSize,
|
||||
mask: 0
|
||||
};
|
||||
|
||||
constructor(socket: NodeSocket) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
this._incomingData = new ChunkStream();
|
||||
this._register(this.socket.onData(data => this._acceptChunk(data)));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.dispose();
|
||||
}
|
||||
|
||||
public onData(listener: (e: VSBuffer) => void): IDisposable {
|
||||
return this._onData.event(listener);
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
return this.socket.onClose(listener);
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
return this.socket.onEnd(listener);
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
let headerLen = Constants.MinHeaderByteSize;
|
||||
if (buffer.byteLength < 126) {
|
||||
headerLen += 0;
|
||||
} else if (buffer.byteLength < 2 ** 16) {
|
||||
headerLen += 2;
|
||||
} else {
|
||||
headerLen += 8;
|
||||
}
|
||||
const header = VSBuffer.alloc(headerLen);
|
||||
|
||||
header.writeUInt8(0b10000010, 0);
|
||||
if (buffer.byteLength < 126) {
|
||||
header.writeUInt8(buffer.byteLength, 1);
|
||||
} else if (buffer.byteLength < 2 ** 16) {
|
||||
header.writeUInt8(126, 1);
|
||||
let offset = 1;
|
||||
header.writeUInt8((buffer.byteLength >>> 8) & 0b11111111, ++offset);
|
||||
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
|
||||
} else {
|
||||
header.writeUInt8(127, 1);
|
||||
let offset = 1;
|
||||
header.writeUInt8(0, ++offset);
|
||||
header.writeUInt8(0, ++offset);
|
||||
header.writeUInt8(0, ++offset);
|
||||
header.writeUInt8(0, ++offset);
|
||||
header.writeUInt8((buffer.byteLength >>> 24) & 0b11111111, ++offset);
|
||||
header.writeUInt8((buffer.byteLength >>> 16) & 0b11111111, ++offset);
|
||||
header.writeUInt8((buffer.byteLength >>> 8) & 0b11111111, ++offset);
|
||||
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
|
||||
}
|
||||
|
||||
this.socket.write(VSBuffer.concat([header, buffer]));
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.socket.end();
|
||||
}
|
||||
|
||||
private _acceptChunk(data: VSBuffer): void {
|
||||
if (data.byteLength === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._incomingData.acceptChunk(data);
|
||||
|
||||
while (this._incomingData.byteLength >= this._state.readLen) {
|
||||
|
||||
if (this._state.state === ReadState.PeekHeader) {
|
||||
// peek to see if we can read the entire header
|
||||
const peekHeader = this._incomingData.peek(this._state.readLen);
|
||||
// const firstByte = peekHeader.readUInt8(0);
|
||||
// const finBit = (firstByte & 0b10000000) >>> 7;
|
||||
const secondByte = peekHeader.readUInt8(1);
|
||||
const hasMask = (secondByte & 0b10000000) >>> 7;
|
||||
const len = (secondByte & 0b01111111);
|
||||
|
||||
this._state.state = ReadState.ReadHeader;
|
||||
this._state.readLen = Constants.MinHeaderByteSize + (hasMask ? 4 : 0) + (len === 126 ? 2 : 0) + (len === 127 ? 8 : 0);
|
||||
this._state.mask = 0;
|
||||
|
||||
} else if (this._state.state === ReadState.ReadHeader) {
|
||||
// read entire header
|
||||
const header = this._incomingData.read(this._state.readLen);
|
||||
const secondByte = header.readUInt8(1);
|
||||
const hasMask = (secondByte & 0b10000000) >>> 7;
|
||||
let len = (secondByte & 0b01111111);
|
||||
|
||||
let offset = 1;
|
||||
if (len === 126) {
|
||||
len = (
|
||||
header.readUInt8(++offset) * 2 ** 8
|
||||
+ header.readUInt8(++offset)
|
||||
);
|
||||
} else if (len === 127) {
|
||||
len = (
|
||||
header.readUInt8(++offset) * 0
|
||||
+ header.readUInt8(++offset) * 0
|
||||
+ header.readUInt8(++offset) * 0
|
||||
+ header.readUInt8(++offset) * 0
|
||||
+ header.readUInt8(++offset) * 2 ** 24
|
||||
+ header.readUInt8(++offset) * 2 ** 16
|
||||
+ header.readUInt8(++offset) * 2 ** 8
|
||||
+ header.readUInt8(++offset)
|
||||
);
|
||||
}
|
||||
|
||||
let mask = 0;
|
||||
if (hasMask) {
|
||||
mask = (
|
||||
header.readUInt8(++offset) * 2 ** 24
|
||||
+ header.readUInt8(++offset) * 2 ** 16
|
||||
+ header.readUInt8(++offset) * 2 ** 8
|
||||
+ header.readUInt8(++offset)
|
||||
);
|
||||
}
|
||||
|
||||
this._state.state = ReadState.ReadBody;
|
||||
this._state.readLen = len;
|
||||
this._state.mask = mask;
|
||||
|
||||
} else if (this._state.state === ReadState.ReadBody) {
|
||||
// read body
|
||||
|
||||
const body = this._incomingData.read(this._state.readLen);
|
||||
unmask(body, this._state.mask);
|
||||
|
||||
this._state.state = ReadState.PeekHeader;
|
||||
this._state.readLen = Constants.MinHeaderByteSize;
|
||||
this._state.mask = 0;
|
||||
|
||||
this._onData.fire(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public drain(): Promise<void> {
|
||||
return this.socket.drain();
|
||||
}
|
||||
}
|
||||
|
||||
function unmask(buffer: VSBuffer, mask: number): void {
|
||||
if (mask === 0) {
|
||||
return;
|
||||
}
|
||||
let cnt = buffer.byteLength >>> 2;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const v = buffer.readUInt32BE(i * 4);
|
||||
buffer.writeUInt32BE(v ^ mask, i * 4);
|
||||
}
|
||||
let offset = cnt * 4;
|
||||
let bytesLeft = buffer.byteLength - offset;
|
||||
const m3 = (mask >>> 24) & 0b11111111;
|
||||
const m2 = (mask >>> 16) & 0b11111111;
|
||||
const m1 = (mask >>> 8) & 0b11111111;
|
||||
if (bytesLeft >= 1) {
|
||||
buffer.writeUInt8(buffer.readUInt8(offset) ^ m3, offset);
|
||||
}
|
||||
if (bytesLeft >= 2) {
|
||||
buffer.writeUInt8(buffer.readUInt8(offset + 1) ^ m2, offset + 1);
|
||||
}
|
||||
if (bytesLeft >= 3) {
|
||||
buffer.writeUInt8(buffer.readUInt8(offset + 2) ^ m1, offset + 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Read this before there's any chance it is overwritten
|
||||
// Related to https://github.com/microsoft/vscode/issues/30624
|
||||
export const XDG_RUNTIME_DIR = <string | undefined>process.env['XDG_RUNTIME_DIR'];
|
||||
|
||||
const safeIpcPathLengths: { [platform: number]: number } = {
|
||||
[Platform.Linux]: 107,
|
||||
[Platform.Mac]: 103
|
||||
};
|
||||
|
||||
export function createRandomIPCHandle(): string {
|
||||
const randomSuffix = generateUuid();
|
||||
|
||||
// Windows: use named pipe
|
||||
if (process.platform === 'win32') {
|
||||
return `\\\\.\\pipe\\vscode-ipc-${randomSuffix}-sock`;
|
||||
}
|
||||
|
||||
// Mac/Unix: use socket file and prefer
|
||||
// XDG_RUNTIME_DIR over tmpDir
|
||||
let result: string;
|
||||
if (XDG_RUNTIME_DIR) {
|
||||
result = join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
|
||||
} else {
|
||||
result = join(tmpdir(), `vscode-ipc-${randomSuffix}.sock`);
|
||||
}
|
||||
|
||||
// Validate length
|
||||
validateIPCHandleLength(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createStaticIPCHandle(directoryPath: string, type: string, version: string): string {
|
||||
const scope = createHash('md5').update(directoryPath).digest('hex');
|
||||
|
||||
// Windows: use named pipe
|
||||
if (process.platform === 'win32') {
|
||||
return `\\\\.\\pipe\\${scope}-${version}-${type}-sock`;
|
||||
}
|
||||
|
||||
// Mac/Unix: use socket file and prefer
|
||||
// XDG_RUNTIME_DIR over user data path
|
||||
// unless portable
|
||||
let result: string;
|
||||
if (XDG_RUNTIME_DIR && !process.env['VSCODE_PORTABLE']) {
|
||||
result = join(XDG_RUNTIME_DIR, `vscode-${scope.substr(0, 8)}-${version}-${type}.sock`);
|
||||
} else {
|
||||
result = join(directoryPath, `${version}-${type}.sock`);
|
||||
}
|
||||
|
||||
// Validate length
|
||||
validateIPCHandleLength(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateIPCHandleLength(handle: string): void {
|
||||
const limit = safeIpcPathLengths[platform];
|
||||
if (typeof limit === 'number' && handle.length >= limit) {
|
||||
// https://nodejs.org/api/net.html#net_identifying_paths_for_ipc_connections
|
||||
console.warn(`WARNING: IPC handle "${handle}" is longer than ${limit} chars, try a shorter --user-data-dir`);
|
||||
}
|
||||
}
|
||||
|
||||
export class Server extends IPCServer {
|
||||
|
||||
private static toClientConnectionEvent(server: NetServer): Event<ClientConnectionEvent> {
|
||||
const onConnection = Event.fromNodeEventEmitter<Socket>(server, 'connection');
|
||||
|
||||
return Event.map(onConnection, socket => ({
|
||||
protocol: new Protocol(new NodeSocket(socket)),
|
||||
onDidClientDisconnect: Event.once(Event.fromNodeEventEmitter<void>(socket, 'close'))
|
||||
}));
|
||||
}
|
||||
|
||||
private server: NetServer | null;
|
||||
|
||||
constructor(server: NetServer) {
|
||||
super(Server.toClientConnectionEvent(server));
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function serve(port: number): Promise<Server>;
|
||||
export function serve(namedPipe: string): Promise<Server>;
|
||||
export function serve(hook: any): Promise<Server> {
|
||||
return new Promise<Server>((c, e) => {
|
||||
const server = createServer();
|
||||
|
||||
server.on('error', e);
|
||||
server.listen(hook, () => {
|
||||
server.removeListener('error', e);
|
||||
c(new Server(server));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function connect(options: { host: string, port: number }, clientId: string): Promise<Client>;
|
||||
export function connect(port: number, clientId: string): Promise<Client>;
|
||||
export function connect(namedPipe: string, clientId: string): Promise<Client>;
|
||||
export function connect(hook: any, clientId: string): Promise<Client> {
|
||||
return new Promise<Client>((c, e) => {
|
||||
const socket = createConnection(hook, () => {
|
||||
socket.removeListener('error', e);
|
||||
c(Client.fromSocket(new NodeSocket(socket), clientId));
|
||||
});
|
||||
|
||||
socket.once('error', e);
|
||||
});
|
||||
}
|
||||
493
lib/vscode/src/vs/base/parts/ipc/test/common/ipc.test.ts
Normal file
493
lib/vscode/src/vs/base/parts/ipc/test/common/ipc.test.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
class QueueProtocol implements IMessagePassingProtocol {
|
||||
|
||||
private buffering = true;
|
||||
private buffers: VSBuffer[] = [];
|
||||
|
||||
private readonly _onMessage = new Emitter<VSBuffer>({
|
||||
onFirstListenerDidAdd: () => {
|
||||
for (const buffer of this.buffers) {
|
||||
this._onMessage.fire(buffer);
|
||||
}
|
||||
|
||||
this.buffers = [];
|
||||
this.buffering = false;
|
||||
},
|
||||
onLastListenerRemove: () => {
|
||||
this.buffering = true;
|
||||
}
|
||||
});
|
||||
|
||||
readonly onMessage = this._onMessage.event;
|
||||
other!: QueueProtocol;
|
||||
|
||||
send(buffer: VSBuffer): void {
|
||||
this.other.receive(buffer);
|
||||
}
|
||||
|
||||
protected receive(buffer: VSBuffer): void {
|
||||
if (this.buffering) {
|
||||
this.buffers.push(buffer);
|
||||
} else {
|
||||
this._onMessage.fire(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createProtocolPair(): [IMessagePassingProtocol, IMessagePassingProtocol] {
|
||||
const one = new QueueProtocol();
|
||||
const other = new QueueProtocol();
|
||||
one.other = other;
|
||||
other.other = one;
|
||||
|
||||
return [one, other];
|
||||
}
|
||||
|
||||
class TestIPCClient extends IPCClient<string> {
|
||||
|
||||
private readonly _onDidDisconnect = new Emitter<void>();
|
||||
readonly onDidDisconnect = this._onDidDisconnect.event;
|
||||
|
||||
constructor(protocol: IMessagePassingProtocol, id: string) {
|
||||
super(protocol, id);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidDisconnect.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class TestIPCServer extends IPCServer<string> {
|
||||
|
||||
private readonly onDidClientConnect: Emitter<ClientConnectionEvent>;
|
||||
|
||||
constructor() {
|
||||
const onDidClientConnect = new Emitter<ClientConnectionEvent>();
|
||||
super(onDidClientConnect.event);
|
||||
this.onDidClientConnect = onDidClientConnect;
|
||||
}
|
||||
|
||||
createConnection(id: string): IPCClient<string> {
|
||||
const [pc, ps] = createProtocolPair();
|
||||
const client = new TestIPCClient(pc, id);
|
||||
|
||||
this.onDidClientConnect.fire({
|
||||
protocol: ps,
|
||||
onDidClientDisconnect: client.onDidDisconnect
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
const TestChannelId = 'testchannel';
|
||||
|
||||
interface ITestService {
|
||||
marco(): Promise<string>;
|
||||
error(message: string): Promise<void>;
|
||||
neverComplete(): Promise<void>;
|
||||
neverCompleteCT(cancellationToken: CancellationToken): Promise<void>;
|
||||
buffersLength(buffers: VSBuffer[]): Promise<number>;
|
||||
marshall(uri: URI): Promise<URI>;
|
||||
context(): Promise<unknown>;
|
||||
|
||||
onPong: Event<string>;
|
||||
}
|
||||
|
||||
class TestService implements ITestService {
|
||||
|
||||
private readonly _onPong = new Emitter<string>();
|
||||
readonly onPong = this._onPong.event;
|
||||
|
||||
marco(): Promise<string> {
|
||||
return Promise.resolve('polo');
|
||||
}
|
||||
|
||||
error(message: string): Promise<void> {
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
|
||||
neverComplete(): Promise<void> {
|
||||
return new Promise(_ => { });
|
||||
}
|
||||
|
||||
neverCompleteCT(cancellationToken: CancellationToken): Promise<void> {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
|
||||
return new Promise((_, e) => cancellationToken.onCancellationRequested(() => e(canceled())));
|
||||
}
|
||||
|
||||
buffersLength(buffers: VSBuffer[]): Promise<number> {
|
||||
return Promise.resolve(buffers.reduce((r, b) => r + b.buffer.length, 0));
|
||||
}
|
||||
|
||||
ping(msg: string): void {
|
||||
this._onPong.fire(msg);
|
||||
}
|
||||
|
||||
marshall(uri: URI): Promise<URI> {
|
||||
return Promise.resolve(uri);
|
||||
}
|
||||
|
||||
context(context?: unknown): Promise<unknown> {
|
||||
return Promise.resolve(context);
|
||||
}
|
||||
}
|
||||
|
||||
class TestChannel implements IServerChannel {
|
||||
|
||||
constructor(private service: ITestService) { }
|
||||
|
||||
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
|
||||
switch (command) {
|
||||
case 'marco': return this.service.marco();
|
||||
case 'error': return this.service.error(arg);
|
||||
case 'neverComplete': return this.service.neverComplete();
|
||||
case 'neverCompleteCT': return this.service.neverCompleteCT(cancellationToken);
|
||||
case 'buffersLength': return this.service.buffersLength(arg);
|
||||
default: return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
}
|
||||
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'onPong': return this.service.onPong;
|
||||
default: throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestChannelClient implements ITestService {
|
||||
|
||||
get onPong(): Event<string> {
|
||||
return this.channel.listen('onPong');
|
||||
}
|
||||
|
||||
constructor(private channel: IChannel) { }
|
||||
|
||||
marco(): Promise<string> {
|
||||
return this.channel.call('marco');
|
||||
}
|
||||
|
||||
error(message: string): Promise<void> {
|
||||
return this.channel.call('error', message);
|
||||
}
|
||||
|
||||
neverComplete(): Promise<void> {
|
||||
return this.channel.call('neverComplete');
|
||||
}
|
||||
|
||||
neverCompleteCT(cancellationToken: CancellationToken): Promise<void> {
|
||||
return this.channel.call('neverCompleteCT', undefined, cancellationToken);
|
||||
}
|
||||
|
||||
buffersLength(buffers: VSBuffer[]): Promise<number> {
|
||||
return this.channel.call('buffersLength', buffers);
|
||||
}
|
||||
|
||||
marshall(uri: URI): Promise<URI> {
|
||||
return this.channel.call('marshall', uri);
|
||||
}
|
||||
|
||||
context(): Promise<unknown> {
|
||||
return this.channel.call('context');
|
||||
}
|
||||
}
|
||||
|
||||
suite('Base IPC', function () {
|
||||
|
||||
test('createProtocolPair', async function () {
|
||||
const [clientProtocol, serverProtocol] = createProtocolPair();
|
||||
|
||||
const b1 = VSBuffer.alloc(0);
|
||||
clientProtocol.send(b1);
|
||||
|
||||
const b3 = VSBuffer.alloc(0);
|
||||
serverProtocol.send(b3);
|
||||
|
||||
const b2 = await Event.toPromise(serverProtocol.onMessage);
|
||||
const b4 = await Event.toPromise(clientProtocol.onMessage);
|
||||
|
||||
assert.strictEqual(b1, b2);
|
||||
assert.strictEqual(b3, b4);
|
||||
});
|
||||
|
||||
suite('one to one', function () {
|
||||
let server: IPCServer;
|
||||
let client: IPCClient;
|
||||
let service: TestService;
|
||||
let ipcService: ITestService;
|
||||
|
||||
setup(function () {
|
||||
service = new TestService();
|
||||
const testServer = new TestIPCServer();
|
||||
server = testServer;
|
||||
|
||||
server.registerChannel(TestChannelId, new TestChannel(service));
|
||||
|
||||
client = testServer.createConnection('client1');
|
||||
ipcService = new TestChannelClient(client.getChannel(TestChannelId));
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
client.dispose();
|
||||
server.dispose();
|
||||
});
|
||||
|
||||
test('call success', async function () {
|
||||
const r = await ipcService.marco();
|
||||
return assert.equal(r, 'polo');
|
||||
});
|
||||
|
||||
test('call error', async function () {
|
||||
try {
|
||||
await ipcService.error('nice error');
|
||||
return assert.fail('should not reach here');
|
||||
} catch (err) {
|
||||
return assert.equal(err.message, 'nice error');
|
||||
}
|
||||
});
|
||||
|
||||
test('cancel call with cancelled cancellation token', async function () {
|
||||
try {
|
||||
await ipcService.neverCompleteCT(CancellationToken.Cancelled);
|
||||
return assert.fail('should not reach here');
|
||||
} catch (err) {
|
||||
return assert(err.message === 'Canceled');
|
||||
}
|
||||
});
|
||||
|
||||
test('cancel call with cancellation token (sync)', function () {
|
||||
const cts = new CancellationTokenSource();
|
||||
const promise = ipcService.neverCompleteCT(cts.token).then(
|
||||
_ => assert.fail('should not reach here'),
|
||||
err => assert(err.message === 'Canceled')
|
||||
);
|
||||
|
||||
cts.cancel();
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('cancel call with cancellation token (async)', function () {
|
||||
const cts = new CancellationTokenSource();
|
||||
const promise = ipcService.neverCompleteCT(cts.token).then(
|
||||
_ => assert.fail('should not reach here'),
|
||||
err => assert(err.message === 'Canceled')
|
||||
);
|
||||
|
||||
setTimeout(() => cts.cancel());
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('listen to events', async function () {
|
||||
const messages: string[] = [];
|
||||
|
||||
ipcService.onPong(msg => messages.push(msg));
|
||||
await timeout(0);
|
||||
|
||||
assert.deepEqual(messages, []);
|
||||
service.ping('hello');
|
||||
await timeout(0);
|
||||
|
||||
assert.deepEqual(messages, ['hello']);
|
||||
service.ping('world');
|
||||
await timeout(0);
|
||||
|
||||
assert.deepEqual(messages, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test('buffers in arrays', async function () {
|
||||
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
|
||||
return assert.equal(r, 5);
|
||||
});
|
||||
});
|
||||
|
||||
suite('one to one (proxy)', function () {
|
||||
let server: IPCServer;
|
||||
let client: IPCClient;
|
||||
let service: TestService;
|
||||
let ipcService: ITestService;
|
||||
|
||||
setup(function () {
|
||||
service = new TestService();
|
||||
const testServer = new TestIPCServer();
|
||||
server = testServer;
|
||||
|
||||
server.registerChannel(TestChannelId, createChannelReceiver(service));
|
||||
|
||||
client = testServer.createConnection('client1');
|
||||
ipcService = createChannelSender(client.getChannel(TestChannelId));
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
client.dispose();
|
||||
server.dispose();
|
||||
});
|
||||
|
||||
test('call success', async function () {
|
||||
const r = await ipcService.marco();
|
||||
return assert.equal(r, 'polo');
|
||||
});
|
||||
|
||||
test('call error', async function () {
|
||||
try {
|
||||
await ipcService.error('nice error');
|
||||
return assert.fail('should not reach here');
|
||||
} catch (err) {
|
||||
return assert.equal(err.message, 'nice error');
|
||||
}
|
||||
});
|
||||
|
||||
test('listen to events', async function () {
|
||||
const messages: string[] = [];
|
||||
|
||||
ipcService.onPong(msg => messages.push(msg));
|
||||
await timeout(0);
|
||||
|
||||
assert.deepEqual(messages, []);
|
||||
service.ping('hello');
|
||||
await timeout(0);
|
||||
|
||||
assert.deepEqual(messages, ['hello']);
|
||||
service.ping('world');
|
||||
await timeout(0);
|
||||
|
||||
assert.deepEqual(messages, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test('marshalling uri', async function () {
|
||||
const uri = URI.file('foobar');
|
||||
const r = await ipcService.marshall(uri);
|
||||
assert.ok(r instanceof URI);
|
||||
return assert.ok(isEqual(r, uri));
|
||||
});
|
||||
|
||||
test('buffers in arrays', async function () {
|
||||
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
|
||||
return assert.equal(r, 5);
|
||||
});
|
||||
});
|
||||
|
||||
suite('one to one (proxy, extra context)', function () {
|
||||
let server: IPCServer;
|
||||
let client: IPCClient;
|
||||
let service: TestService;
|
||||
let ipcService: ITestService;
|
||||
|
||||
setup(function () {
|
||||
service = new TestService();
|
||||
const testServer = new TestIPCServer();
|
||||
server = testServer;
|
||||
|
||||
server.registerChannel(TestChannelId, createChannelReceiver(service));
|
||||
|
||||
client = testServer.createConnection('client1');
|
||||
ipcService = createChannelSender(client.getChannel(TestChannelId), { context: 'Super Context' });
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
client.dispose();
|
||||
server.dispose();
|
||||
});
|
||||
|
||||
test('call extra context', async function () {
|
||||
const r = await ipcService.context();
|
||||
return assert.equal(r, 'Super Context');
|
||||
});
|
||||
});
|
||||
|
||||
suite('one to many', function () {
|
||||
test('all clients get pinged', async function () {
|
||||
const service = new TestService();
|
||||
const channel = new TestChannel(service);
|
||||
const server = new TestIPCServer();
|
||||
server.registerChannel('channel', channel);
|
||||
|
||||
let client1GotPinged = false;
|
||||
const client1 = server.createConnection('client1');
|
||||
const ipcService1 = new TestChannelClient(client1.getChannel('channel'));
|
||||
ipcService1.onPong(() => client1GotPinged = true);
|
||||
|
||||
let client2GotPinged = false;
|
||||
const client2 = server.createConnection('client2');
|
||||
const ipcService2 = new TestChannelClient(client2.getChannel('channel'));
|
||||
ipcService2.onPong(() => client2GotPinged = true);
|
||||
|
||||
await timeout(1);
|
||||
service.ping('hello');
|
||||
|
||||
await timeout(1);
|
||||
assert(client1GotPinged, 'client 1 got pinged');
|
||||
assert(client2GotPinged, 'client 2 got pinged');
|
||||
|
||||
client1.dispose();
|
||||
client2.dispose();
|
||||
server.dispose();
|
||||
});
|
||||
|
||||
test('server gets pings from all clients (broadcast channel)', async function () {
|
||||
const server = new TestIPCServer();
|
||||
|
||||
const client1 = server.createConnection('client1');
|
||||
const clientService1 = new TestService();
|
||||
const clientChannel1 = new TestChannel(clientService1);
|
||||
client1.registerChannel('channel', clientChannel1);
|
||||
|
||||
const pings: string[] = [];
|
||||
const channel = server.getChannel('channel', () => true);
|
||||
const service = new TestChannelClient(channel);
|
||||
service.onPong(msg => pings.push(msg));
|
||||
|
||||
await timeout(1);
|
||||
clientService1.ping('hello 1');
|
||||
|
||||
await timeout(1);
|
||||
assert.deepEqual(pings, ['hello 1']);
|
||||
|
||||
const client2 = server.createConnection('client2');
|
||||
const clientService2 = new TestService();
|
||||
const clientChannel2 = new TestChannel(clientService2);
|
||||
client2.registerChannel('channel', clientChannel2);
|
||||
|
||||
await timeout(1);
|
||||
clientService2.ping('hello 2');
|
||||
|
||||
await timeout(1);
|
||||
assert.deepEqual(pings, ['hello 1', 'hello 2']);
|
||||
|
||||
client1.dispose();
|
||||
clientService1.ping('hello 1');
|
||||
|
||||
await timeout(1);
|
||||
assert.deepEqual(pings, ['hello 1', 'hello 2']);
|
||||
|
||||
await timeout(1);
|
||||
clientService2.ping('hello again 2');
|
||||
|
||||
await timeout(1);
|
||||
assert.deepEqual(pings, ['hello 1', 'hello 2', 'hello again 2']);
|
||||
|
||||
client2.dispose();
|
||||
server.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
78
lib/vscode/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts
Normal file
78
lib/vscode/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { TestServiceClient } from './testService';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
|
||||
function createClient(): Client {
|
||||
return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), {
|
||||
serverName: 'TestServer',
|
||||
env: { AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true }
|
||||
});
|
||||
}
|
||||
|
||||
suite('IPC, Child Process', () => {
|
||||
test('createChannel', () => {
|
||||
const client = createClient();
|
||||
const channel = client.getChannel('test');
|
||||
const service = new TestServiceClient(channel);
|
||||
|
||||
const result = service.pong('ping').then(r => {
|
||||
assert.equal(r.incoming, 'ping');
|
||||
assert.equal(r.outgoing, 'pong');
|
||||
});
|
||||
|
||||
return result.finally(() => client.dispose());
|
||||
});
|
||||
|
||||
test('events', () => {
|
||||
const client = createClient();
|
||||
const channel = client.getChannel('test');
|
||||
const service = new TestServiceClient(channel);
|
||||
|
||||
const event = new Promise((c, e) => {
|
||||
service.onMarco(({ answer }) => {
|
||||
try {
|
||||
assert.equal(answer, 'polo');
|
||||
c(undefined);
|
||||
} catch (err) {
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const request = service.marco();
|
||||
const result = Promise.all([request, event]);
|
||||
|
||||
return result.finally(() => client.dispose());
|
||||
});
|
||||
|
||||
test('event dispose', () => {
|
||||
const client = createClient();
|
||||
const channel = client.getChannel('test');
|
||||
const service = new TestServiceClient(channel);
|
||||
|
||||
let count = 0;
|
||||
const disposable = service.onMarco(() => count++);
|
||||
|
||||
const result = service.marco().then(async answer => {
|
||||
assert.equal(answer, 'polo');
|
||||
assert.equal(count, 1);
|
||||
|
||||
const answer_1 = await service.marco();
|
||||
assert.equal(answer_1, 'polo');
|
||||
assert.equal(count, 2);
|
||||
disposable.dispose();
|
||||
|
||||
const answer_2 = await service.marco();
|
||||
assert.equal(answer_2, 'polo');
|
||||
assert.equal(count, 2);
|
||||
});
|
||||
|
||||
return result.finally(() => client.dispose());
|
||||
});
|
||||
});
|
||||
258
lib/vscode/src/vs/base/parts/ipc/test/node/ipc.net.test.ts
Normal file
258
lib/vscode/src/vs/base/parts/ipc/test/node/ipc.net.test.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { createServer, Socket } from 'net';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Protocol, PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { tmpdir } from 'os';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
class MessageStream {
|
||||
|
||||
private _currentComplete: ((data: VSBuffer) => void) | null;
|
||||
private _messages: VSBuffer[];
|
||||
|
||||
constructor(x: Protocol | PersistentProtocol) {
|
||||
this._currentComplete = null;
|
||||
this._messages = [];
|
||||
x.onMessage(data => {
|
||||
this._messages.push(data);
|
||||
this._trigger();
|
||||
});
|
||||
}
|
||||
|
||||
private _trigger(): void {
|
||||
if (!this._currentComplete) {
|
||||
return;
|
||||
}
|
||||
if (this._messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
const complete = this._currentComplete;
|
||||
const msg = this._messages.shift()!;
|
||||
|
||||
this._currentComplete = null;
|
||||
complete(msg);
|
||||
}
|
||||
|
||||
public waitForOne(): Promise<VSBuffer> {
|
||||
return new Promise<VSBuffer>((complete) => {
|
||||
this._currentComplete = complete;
|
||||
this._trigger();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class EtherStream extends EventEmitter {
|
||||
constructor(
|
||||
private readonly _ether: Ether,
|
||||
private readonly _name: 'a' | 'b'
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
write(data: Buffer, cb?: Function): boolean {
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
throw new Error(`Invalid data`);
|
||||
}
|
||||
this._ether.write(this._name, data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Ether {
|
||||
|
||||
private readonly _a: EtherStream;
|
||||
private readonly _b: EtherStream;
|
||||
|
||||
private _ab: Buffer[];
|
||||
private _ba: Buffer[];
|
||||
|
||||
public get a(): Socket {
|
||||
return <any>this._a;
|
||||
}
|
||||
|
||||
public get b(): Socket {
|
||||
return <any>this._b;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._a = new EtherStream(this, 'a');
|
||||
this._b = new EtherStream(this, 'b');
|
||||
this._ab = [];
|
||||
this._ba = [];
|
||||
}
|
||||
|
||||
public write(from: 'a' | 'b', data: Buffer): void {
|
||||
if (from === 'a') {
|
||||
this._ab.push(data);
|
||||
} else {
|
||||
this._ba.push(data);
|
||||
}
|
||||
|
||||
setImmediate(() => this._deliver());
|
||||
}
|
||||
|
||||
private _deliver(): void {
|
||||
|
||||
if (this._ab.length > 0) {
|
||||
const data = Buffer.concat(this._ab);
|
||||
this._ab.length = 0;
|
||||
this._b.emit('data', data);
|
||||
setImmediate(() => this._deliver());
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._ba.length > 0) {
|
||||
const data = Buffer.concat(this._ba);
|
||||
this._ba.length = 0;
|
||||
this._a.emit('data', data);
|
||||
setImmediate(() => this._deliver());
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
suite('IPC, Socket Protocol', () => {
|
||||
|
||||
let ether: Ether;
|
||||
|
||||
setup(() => {
|
||||
ether = new Ether();
|
||||
});
|
||||
|
||||
test('read/write', async () => {
|
||||
|
||||
const a = new Protocol(new NodeSocket(ether.a));
|
||||
const b = new Protocol(new NodeSocket(ether.b));
|
||||
const bMessages = new MessageStream(b);
|
||||
|
||||
a.send(VSBuffer.fromString('foobarfarboo'));
|
||||
const msg1 = await bMessages.waitForOne();
|
||||
assert.equal(msg1.toString(), 'foobarfarboo');
|
||||
|
||||
const buffer = VSBuffer.alloc(1);
|
||||
buffer.writeUInt8(123, 0);
|
||||
a.send(buffer);
|
||||
const msg2 = await bMessages.waitForOne();
|
||||
assert.equal(msg2.readUInt8(0), 123);
|
||||
});
|
||||
|
||||
|
||||
test('read/write, object data', async () => {
|
||||
|
||||
const a = new Protocol(new NodeSocket(ether.a));
|
||||
const b = new Protocol(new NodeSocket(ether.b));
|
||||
const bMessages = new MessageStream(b);
|
||||
|
||||
const data = {
|
||||
pi: Math.PI,
|
||||
foo: 'bar',
|
||||
more: true,
|
||||
data: 'Hello World'.split('')
|
||||
};
|
||||
|
||||
a.send(VSBuffer.fromString(JSON.stringify(data)));
|
||||
const msg = await bMessages.waitForOne();
|
||||
assert.deepEqual(JSON.parse(msg.toString()), data);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('PersistentProtocol reconnection', () => {
|
||||
let ether: Ether;
|
||||
|
||||
setup(() => {
|
||||
ether = new Ether();
|
||||
});
|
||||
|
||||
test('acks get piggybacked with messages', async () => {
|
||||
const a = new PersistentProtocol(new NodeSocket(ether.a));
|
||||
const aMessages = new MessageStream(a);
|
||||
const b = new PersistentProtocol(new NodeSocket(ether.b));
|
||||
const bMessages = new MessageStream(b);
|
||||
|
||||
a.send(VSBuffer.fromString('a1'));
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
|
||||
a.send(VSBuffer.fromString('a2'));
|
||||
assert.equal(a.unacknowledgedCount, 2);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
|
||||
a.send(VSBuffer.fromString('a3'));
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
|
||||
const a1 = await bMessages.waitForOne();
|
||||
assert.equal(a1.toString(), 'a1');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
|
||||
const a2 = await bMessages.waitForOne();
|
||||
assert.equal(a2.toString(), 'a2');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
|
||||
const a3 = await bMessages.waitForOne();
|
||||
assert.equal(a3.toString(), 'a3');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
|
||||
b.send(VSBuffer.fromString('b1'));
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
|
||||
const b1 = await aMessages.waitForOne();
|
||||
assert.equal(b1.toString(), 'b1');
|
||||
assert.equal(a.unacknowledgedCount, 0);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
|
||||
a.send(VSBuffer.fromString('a4'));
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
|
||||
const b2 = await bMessages.waitForOne();
|
||||
assert.equal(b2.toString(), 'a4');
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
suite('IPC, create handle', () => {
|
||||
|
||||
test('createRandomIPCHandle', async () => {
|
||||
return testIPCHandle(createRandomIPCHandle());
|
||||
});
|
||||
|
||||
test('createStaticIPCHandle', async () => {
|
||||
return testIPCHandle(createStaticIPCHandle(tmpdir(), 'test', product.version));
|
||||
});
|
||||
|
||||
function testIPCHandle(handle: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const pipeName = createRandomIPCHandle();
|
||||
|
||||
const server = createServer();
|
||||
|
||||
server.on('error', () => {
|
||||
return new Promise(() => server.close(() => reject()));
|
||||
});
|
||||
|
||||
server.listen(pipeName, () => {
|
||||
server.removeListener('error', reject);
|
||||
|
||||
return new Promise(() => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
11
lib/vscode/src/vs/base/parts/ipc/test/node/testApp.ts
Normal file
11
lib/vscode/src/vs/base/parts/ipc/test/node/testApp.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { TestChannel, TestService } from './testService';
|
||||
|
||||
const server = new Server('test');
|
||||
const service = new TestService();
|
||||
server.registerChannel('test', new TestChannel(service));
|
||||
79
lib/vscode/src/vs/base/parts/ipc/test/node/testService.ts
Normal file
79
lib/vscode/src/vs/base/parts/ipc/test/node/testService.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
export interface IMarcoPoloEvent {
|
||||
answer: string;
|
||||
}
|
||||
|
||||
export interface ITestService {
|
||||
onMarco: Event<IMarcoPoloEvent>;
|
||||
marco(): Promise<string>;
|
||||
pong(ping: string): Promise<{ incoming: string, outgoing: string }>;
|
||||
cancelMe(): Promise<boolean>;
|
||||
}
|
||||
|
||||
export class TestService implements ITestService {
|
||||
|
||||
private readonly _onMarco = new Emitter<IMarcoPoloEvent>();
|
||||
onMarco: Event<IMarcoPoloEvent> = this._onMarco.event;
|
||||
|
||||
marco(): Promise<string> {
|
||||
this._onMarco.fire({ answer: 'polo' });
|
||||
return Promise.resolve('polo');
|
||||
}
|
||||
|
||||
pong(ping: string): Promise<{ incoming: string, outgoing: string }> {
|
||||
return Promise.resolve({ incoming: ping, outgoing: 'pong' });
|
||||
}
|
||||
|
||||
cancelMe(): Promise<boolean> {
|
||||
return Promise.resolve(timeout(100)).then(() => true);
|
||||
}
|
||||
}
|
||||
|
||||
export class TestChannel implements IServerChannel {
|
||||
|
||||
constructor(private testService: ITestService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'marco': return this.testService.onMarco;
|
||||
}
|
||||
|
||||
throw new Error('Event not found');
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, ...args: any[]): Promise<any> {
|
||||
switch (command) {
|
||||
case 'pong': return this.testService.pong(args[0]);
|
||||
case 'cancelMe': return this.testService.cancelMe();
|
||||
case 'marco': return this.testService.marco();
|
||||
default: return Promise.reject(new Error(`command not found: ${command}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TestServiceClient implements ITestService {
|
||||
|
||||
get onMarco(): Event<IMarcoPoloEvent> { return this.channel.listen('marco'); }
|
||||
|
||||
constructor(private channel: IChannel) { }
|
||||
|
||||
marco(): Promise<string> {
|
||||
return this.channel.call('marco');
|
||||
}
|
||||
|
||||
pong(ping: string): Promise<{ incoming: string, outgoing: string }> {
|
||||
return this.channel.call('pong', ping);
|
||||
}
|
||||
|
||||
cancelMe(): Promise<boolean> {
|
||||
return this.channel.call('cancelMe');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user