Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View 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);
}
}

View 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View 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();
}
}

View 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);
});
}

View 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();
});
});
});

View 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());
});
});

View 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());
});
});
});
}
});

View 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));

View 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');
}
}