Skip to content

Commit

Permalink
[protocol] connection: buffer messages until reconnect
Browse files Browse the repository at this point in the history
This avoids Error("Connection got disposed") errors which gobble up to the frontend and block users.
  • Loading branch information
geropl committed Sep 21, 2021
1 parent b031cf3 commit fd985fc
Showing 1 changed file with 90 additions and 7 deletions.
97 changes: 90 additions & 7 deletions components/gitpod-protocol/src/messaging/browser/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { listen as doListen, Logger, ConsoleLogger } from "vscode-ws-jsonrpc";
import { Logger, ConsoleLogger, toSocket } from "vscode-ws-jsonrpc";
import { WebSocketMessageReader } from "vscode-ws-jsonrpc/lib/socket/reader";
import { MessageConnection, createMessageConnection } from "vscode-jsonrpc";
import { AbstractMessageWriter } from "vscode-jsonrpc/lib/messageWriter";
import { JsonRpcProxyFactory, JsonRpcProxy } from "../proxy-factory";
import { ConnectionHandler } from "../handler";
import ReconnectingWebSocket from 'reconnecting-websocket';
import ReconnectingWebSocket, { Event } from 'reconnecting-websocket';

export interface WebSocketOptions {
onerror?: (event: Event) => void;
Expand Down Expand Up @@ -64,11 +67,11 @@ export class WebSocketConnectionProvider {
logger.error(JSON.stringify(error));
});
}
doListen({
webSocket,
onConnection: connection => handler.onConnection(connection),
logger
});
doListen(
webSocket as any as ReconnectingWebSocket,
connection => handler.onConnection(connection),
logger,
);
return webSocket;
}

Expand All @@ -91,3 +94,83 @@ export class WebSocketConnectionProvider {
}

}

// The following was extracted from vscode-ws-jsonrpc to make these changes:
// - switch from WebSocket to ReconnectingWebSocket
// - webSocket.onopen: making sure it's only ever called once so we're re-using MessageConnection
// - WebSocketMessageWriter: buffer and re-try messages instead of throwing an error immidiately
function doListen(resocket: ReconnectingWebSocket, onConnection: (connection: MessageConnection) => void, logger: Logger) {
let alreadyOpened = false;
resocket.onopen = () => {
if (alreadyOpened) {
return;
}
alreadyOpened = true;

const connection = createWebSocketConnection(resocket, logger);
onConnection(connection);
};
}

function createWebSocketConnection(resocket: ReconnectingWebSocket, logger: Logger) {
const socket = toSocket(resocket as WebSocket);
const messageReader = new WebSocketMessageReader(socket);
const messageWriter = new BufferingWebSocketMessageWriter(resocket, logger);
const connection = createMessageConnection(messageReader, messageWriter, logger);
connection.onClose(() => connection.dispose());
return connection;
}

/**
* This takes vscode-ws-jsonrpc/lib/socket/writer/WebSocketMessageWriter and adds a buffer
*/
class BufferingWebSocketMessageWriter extends AbstractMessageWriter {
protected readonly socket: ReconnectingWebSocket;
protected readonly logger: Logger;
protected errorCount: number = 0;

protected buffer: any[] = [];

constructor(socket: ReconnectingWebSocket, logger: Logger) {
super();
this.socket = socket;
this.logger = logger;

socket.addEventListener("open", (event: Event) => this.flushBuffer());
}

write(msg: any) {
if (this.socket.readyState !== ReconnectingWebSocket.OPEN) {
this.bufferMsg(msg);
return;
}

try {
const content = JSON.stringify(msg);
this.socket.send(content);
} catch (e) {
this.errorCount++;
this.fireError(e, msg, this.errorCount);

this.bufferMsg(msg);
}
}

protected flushBuffer() {
if (this.buffer.length === 0) {
return
}

const buffer = [...this.buffer];
this.buffer = [];
for (const msg of buffer) {
this.write(msg);
}
this.logger.info(`flushed buffer (${this.buffer.length})`)
}

protected bufferMsg(msg: any) {
this.buffer.push(msg);
this.logger.info(`buffered message (${this.buffer.length})`);
}
}

0 comments on commit fd985fc

Please sign in to comment.