Skip to content

Commit 7137c2d

Browse files
committed
Improve protocol class
- Move destroy logic into the class itself - Improve logging a bit - Remove the record option; we should always do this when using permessage-deflate. - Let debug port be null (it can be null in the message args). - Add setSocket so we don't have to initiate a connection to set it. - Move inflate bytes logic into the class itself.
1 parent cbc2e8b commit 7137c2d

File tree

4 files changed

+72
-32
lines changed

4 files changed

+72
-32
lines changed

lib/vscode/src/vs/base/parts/ipc/common/ipc.net.ts

+5
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,11 @@ export class PersistentProtocol implements IMessagePassingProtocol {
743743
}, Math.max(ProtocolConstants.KeepAliveTimeoutTime - timeSinceLastIncomingMsg, 0) + 5);
744744
}
745745

746+
// NOTE@coder: Set the socket without initiating a reconnect.
747+
public setSocket(socket: ISocket): void {
748+
this._socket = socket;
749+
}
750+
746751
public getSocket(): ISocket {
747752
return this._socket;
748753
}

lib/vscode/src/vs/server/node/connection.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { VSBuffer } from 'vs/base/common/buffer';
44
import { Emitter } from 'vs/base/common/event';
55
import { FileAccess } from 'vs/base/common/network';
66
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
7-
import { WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
87
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
98
import { getNlsConfiguration } from 'vs/server/node/nls';
109
import { Protocol } from 'vs/server/node/protocol';
@@ -59,9 +58,7 @@ export class ManagementConnection extends Connection {
5958
}
6059

6160
protected doDispose(): void {
62-
this.protocol.sendDisconnect();
63-
this.protocol.dispose();
64-
this.protocol.getUnderlyingSocket().destroy();
61+
this.protocol.destroy();
6562
}
6663

6764
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
@@ -99,35 +96,34 @@ export class ExtensionHostConnection extends Connection {
9996
}
10097

10198
protected doDispose(): void {
99+
this.protocol.destroy();
102100
if (this.process) {
103101
this.process.kill();
104102
}
105-
this.protocol.getUnderlyingSocket().destroy();
106103
}
107104

108105
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
109-
// This is just to set the new socket.
110-
this.protocol.beginAcceptReconnection(socket, null);
106+
this.protocol.setSocket(socket);
111107
this.protocol.dispose();
112108
this.sendInitMessage(buffer);
113109
}
114110

115111
private sendInitMessage(buffer: VSBuffer): void {
116-
const socket = this.protocol.getUnderlyingSocket();
117-
socket.pause();
112+
if (!this.process) {
113+
throw new Error("Tried to initialize VS Code before spawning");
114+
}
115+
116+
this.protocol.getUnderlyingSocket().pause();
118117

119-
const wrapperSocket = this.protocol.getSocket();
118+
this.logger.debug('Sending socket');
120119

121-
this.logger.trace('Sending socket');
122-
this.process!.send({ // Process must be set at this point.
120+
this.process.send({
123121
type: 'VSCODE_EXTHOST_IPC_SOCKET',
124122
initialDataChunk: Buffer.from(buffer.buffer).toString('base64'),
125-
skipWebSocketFrames: !(wrapperSocket instanceof WebSocketNodeSocket),
123+
skipWebSocketFrames: this.protocol.options.skipWebSocketFrames,
126124
permessageDeflate: this.protocol.options.permessageDeflate,
127-
inflateBytes: wrapperSocket instanceof WebSocketNodeSocket
128-
? Buffer.from(wrapperSocket.recordedInflateBytes.buffer).toString('base64')
129-
: undefined,
130-
}, socket);
125+
inflateBytes: this.protocol.inflateBytes,
126+
}, this.protocol.getUnderlyingSocket());
131127
}
132128

133129
private async spawn(locale: string, buffer: VSBuffer): Promise<cp.ChildProcess> {

lib/vscode/src/vs/server/node/protocol.ts

+53-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1-
import { field } from '@coder/logger';
1+
import { field, logger, Logger } from '@coder/logger';
22
import * as net from 'net';
33
import { VSBuffer } from 'vs/base/common/buffer';
44
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
55
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
66
import { AuthRequest, ConnectionTypeRequest, HandshakeMessage } from 'vs/platform/remote/common/remoteAgentConnection';
7-
import { logger } from 'vs/server/node/logger';
87

98
export interface SocketOptions {
9+
/** The token is how we identify and connect to existing sessions. */
1010
readonly reconnectionToken: string;
11+
/** Specifies that the client is trying to reconnect. */
1112
readonly reconnection: boolean;
13+
/** If true assume this is not a web socket (always false for code-server). */
1214
readonly skipWebSocketFrames: boolean;
15+
/** Whether to support compression (web socket only). */
1316
readonly permessageDeflate?: boolean;
17+
/**
18+
* Seed zlib with these bytes (web socket only). If parts of inflating was
19+
* done in a different zlib instance we need to pass all those bytes into zlib
20+
* otherwise the inflate might hit an inflated portion referencing a distance
21+
* too far back.
22+
*/
1423
readonly inflateBytes?: VSBuffer;
15-
readonly recordInflateBytes?: boolean;
1624
}
1725

1826
export class Protocol extends PersistentProtocol {
27+
private readonly logger: Logger;
28+
1929
public constructor(socket: net.Socket, public readonly options: SocketOptions) {
2030
super(
2131
options.skipWebSocketFrames
@@ -24,9 +34,12 @@ export class Protocol extends PersistentProtocol {
2434
new NodeSocket(socket),
2535
options.permessageDeflate || false,
2636
options.inflateBytes || null,
27-
options.recordInflateBytes || false,
37+
// Always record inflate bytes if using permessage-deflate.
38+
options.permessageDeflate || false,
2839
),
2940
);
41+
42+
this.logger = logger.named('protocol', field("token", this.options.reconnectionToken));
3043
}
3144

3245
public getUnderlyingSocket(): net.Socket {
@@ -40,24 +53,25 @@ export class Protocol extends PersistentProtocol {
4053
* Perform a handshake to get a connection request.
4154
*/
4255
public handshake(): Promise<ConnectionTypeRequest> {
43-
logger.trace('Protocol handshake', field('token', this.options.reconnectionToken));
56+
this.logger.debug('Initiating handshake...');
4457
return new Promise((resolve, reject) => {
4558
const timeout = setTimeout(() => {
46-
logger.error('Handshake timed out', field('token', this.options.reconnectionToken));
47-
reject(new Error('timed out'));
59+
this.logger.debug('Handshake timed out');
60+
reject(new Error('protocol handshake timed out'));
4861
}, 10000); // Matches the client timeout.
4962

5063
const handler = this.onControlMessage((rawMessage) => {
5164
try {
5265
const raw = rawMessage.toString();
53-
logger.trace('Protocol message', field('token', this.options.reconnectionToken), field('message', raw));
66+
this.logger.trace('Got message', field('message', raw));
5467
const message = JSON.parse(raw);
5568
switch (message.type) {
5669
case 'auth':
5770
return this.authenticate(message);
5871
case 'connectionType':
5972
handler.dispose();
6073
clearTimeout(timeout);
74+
this.logger.debug('Handshake completed');
6175
return resolve(message);
6276
default:
6377
throw new Error('Unrecognized message type');
@@ -90,10 +104,38 @@ export class Protocol extends PersistentProtocol {
90104
}
91105

92106
/**
93-
* Send a handshake message. In the case of the extension host, it just sends
94-
* back a debug port.
107+
* Send a handshake message. In the case of the extension host it should just
108+
* send a debug port.
95109
*/
96-
public sendMessage(message: HandshakeMessage | { debugPort?: number } ): void {
110+
public sendMessage(message: HandshakeMessage | { debugPort?: number | null } ): void {
97111
this.sendControl(VSBuffer.fromString(JSON.stringify(message)));
98112
}
113+
114+
/**
115+
* Disconnect and dispose everything including the underlying socket.
116+
*/
117+
public destroy(reason?: string): void {
118+
try {
119+
if (reason) {
120+
this.sendMessage({ type: 'error', reason });
121+
}
122+
// If still connected try notifying the client.
123+
this.sendDisconnect();
124+
} catch (error) {
125+
// I think the write might fail if already disconnected.
126+
this.logger.warn(error.message || error);
127+
}
128+
this.dispose(); // This disposes timers and socket event handlers.
129+
this.getSocket().dispose(); // This will destroy() the socket.
130+
}
131+
132+
/**
133+
* Get inflateBytes in base64 format from the current socket.
134+
*/
135+
public get inflateBytes(): string | undefined {
136+
const socket = this.getSocket();
137+
return socket instanceof WebSocketNodeSocket
138+
? Buffer.from(socket.recordedInflateBytes.buffer).toString('base64')
139+
: undefined;
140+
}
99141
}

lib/vscode/src/vs/server/node/server.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,11 @@ export class Vscode {
123123
reconnection: query.reconnection === 'true',
124124
skipWebSocketFrames: query.skipWebSocketFrames === 'true',
125125
permessageDeflate,
126-
recordInflateBytes: permessageDeflate,
127126
});
128127
try {
129128
await this.connect(await protocol.handshake(), protocol);
130129
} catch (error) {
131-
protocol.sendMessage({ type: 'error', reason: error.message });
132-
protocol.dispose();
133-
protocol.getSocket().dispose();
130+
protocol.destroy(error.message);
134131
}
135132
return true;
136133
}

0 commit comments

Comments
 (0)