Skip to content

Commit 282881d

Browse files
committed
Move connection logic into connection class
- Moved everything I could into the class itself. - Improve the logging situation a bit. - Switch some trace logs to debug. - Get debug port from message arguments.
1 parent 7137c2d commit 282881d

File tree

4 files changed

+109
-78
lines changed

4 files changed

+109
-78
lines changed

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

+69-34
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,49 @@ import * as cp from 'child_process';
33
import { VSBuffer } from 'vs/base/common/buffer';
44
import { Emitter } from 'vs/base/common/event';
55
import { FileAccess } from 'vs/base/common/network';
6-
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
76
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
7+
import { IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection';
88
import { getNlsConfiguration } from 'vs/server/node/nls';
99
import { Protocol } from 'vs/server/node/protocol';
1010
import { IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
1111

1212
export abstract class Connection {
1313
private readonly _onClose = new Emitter<void>();
14+
/**
15+
* Fire when the connection is closed (not just disconnected). This should
16+
* only happen when the connection is offline and old or has an error.
17+
*/
1418
public readonly onClose = this._onClose.event;
1519
private disposed = false;
1620
private _offline: number | undefined;
1721

18-
public constructor(protected protocol: Protocol, public readonly token: string) {}
22+
protected readonly logger: Logger;
23+
24+
public constructor(
25+
protected readonly protocol: Protocol,
26+
public readonly name: string,
27+
) {
28+
this.logger = logger.named(
29+
this.name,
30+
field("token", this.protocol.options.reconnectionToken),
31+
);
32+
33+
this.logger.debug('Connecting...');
34+
this.onClose(() => this.logger.debug('Closed'));
35+
}
1936

2037
public get offline(): number | undefined {
2138
return this._offline;
2239
}
2340

24-
public reconnect(socket: ISocket, buffer: VSBuffer): void {
41+
public reconnect(protocol: Protocol): void {
42+
this.logger.debug('Reconnecting...');
2543
this._offline = undefined;
26-
this.doReconnect(socket, buffer);
44+
this.doReconnect(protocol);
2745
}
2846

29-
public dispose(): void {
47+
public dispose(reason?: string): void {
48+
this.logger.debug('Disposing...', field('reason', reason));
3049
if (!this.disposed) {
3150
this.disposed = true;
3251
this.doDispose();
@@ -35,6 +54,7 @@ export abstract class Connection {
3554
}
3655

3756
protected setOffline(): void {
57+
this.logger.debug('Disconnected');
3858
if (!this._offline) {
3959
this._offline = Date.now();
4060
}
@@ -43,27 +63,34 @@ export abstract class Connection {
4363
/**
4464
* Set up the connection on a new socket.
4565
*/
46-
protected abstract doReconnect(socket: ISocket, buffer: VSBuffer): void;
66+
protected abstract doReconnect(protcol: Protocol): void;
67+
68+
/**
69+
* Dispose/destroy everything permanently.
70+
*/
4771
protected abstract doDispose(): void;
4872
}
4973

5074
/**
5175
* Used for all the IPC channels.
5276
*/
5377
export class ManagementConnection extends Connection {
54-
public constructor(protected protocol: Protocol, token: string) {
55-
super(protocol, token);
78+
public constructor(protocol: Protocol) {
79+
super(protocol, 'management');
5680
protocol.onDidDispose(() => this.dispose()); // Explicit close.
5781
protocol.onSocketClose(() => this.setOffline()); // Might reconnect.
82+
protocol.sendMessage({ type: 'ok' });
5883
}
5984

6085
protected doDispose(): void {
6186
this.protocol.destroy();
6287
}
6388

64-
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
65-
this.protocol.beginAcceptReconnection(socket, buffer);
89+
protected doReconnect(protocol: Protocol): void {
90+
protocol.sendMessage({ type: 'ok' });
91+
this.protocol.beginAcceptReconnection(protocol.getSocket(), protocol.readEntireBuffer());
6692
this.protocol.endAcceptReconnection();
93+
protocol.dispose();
6794
}
6895
}
6996

@@ -82,17 +109,21 @@ type ExtHostMessage = DisconnectedMessage | ConsoleMessage | IExtHostReadyMessag
82109

83110
export class ExtensionHostConnection extends Connection {
84111
private process?: cp.ChildProcess;
85-
private readonly logger: Logger;
86112

87113
public constructor(
88-
locale: string, protocol: Protocol, buffer: VSBuffer, token: string,
114+
protocol: Protocol,
115+
private readonly params: IRemoteExtensionHostStartParams,
89116
private readonly environment: INativeEnvironmentService,
90117
) {
91-
super(protocol, token);
92-
this.logger = logger.named('exthost', field('token', token));
93-
this.protocol.dispose();
94-
this.spawn(locale, buffer).then((p) => this.process = p);
95-
this.protocol.getUnderlyingSocket().pause();
118+
super(protocol, 'exthost');
119+
120+
protocol.sendMessage({ debugPort: this.params.port });
121+
const buffer = protocol.readEntireBuffer();
122+
const inflateBytes = protocol.inflateBytes;
123+
protocol.dispose();
124+
protocol.getUnderlyingSocket().pause();
125+
126+
this.spawn(buffer, inflateBytes).then((p) => this.process = p);
96127
}
97128

98129
protected doDispose(): void {
@@ -102,34 +133,38 @@ export class ExtensionHostConnection extends Connection {
102133
}
103134
}
104135

105-
protected doReconnect(socket: ISocket, buffer: VSBuffer): void {
106-
this.protocol.setSocket(socket);
107-
this.protocol.dispose();
108-
this.sendInitMessage(buffer);
136+
protected doReconnect(protocol: Protocol): void {
137+
protocol.sendMessage({ debugPort: this.params.port });
138+
const buffer = protocol.readEntireBuffer();
139+
const inflateBytes = protocol.inflateBytes;
140+
protocol.dispose();
141+
protocol.getUnderlyingSocket().pause();
142+
this.protocol.setSocket(protocol.getSocket());
143+
144+
this.sendInitMessage(buffer, inflateBytes);
109145
}
110146

111-
private sendInitMessage(buffer: VSBuffer): void {
147+
private sendInitMessage(buffer: VSBuffer, inflateBytes: Uint8Array | undefined): void {
112148
if (!this.process) {
113149
throw new Error("Tried to initialize VS Code before spawning");
114150
}
115151

116-
this.protocol.getUnderlyingSocket().pause();
117-
118152
this.logger.debug('Sending socket');
119153

154+
// TODO: Do something with the debug port.
120155
this.process.send({
121156
type: 'VSCODE_EXTHOST_IPC_SOCKET',
122157
initialDataChunk: Buffer.from(buffer.buffer).toString('base64'),
123158
skipWebSocketFrames: this.protocol.options.skipWebSocketFrames,
124159
permessageDeflate: this.protocol.options.permessageDeflate,
125-
inflateBytes: this.protocol.inflateBytes,
160+
inflateBytes: inflateBytes ? Buffer.from(inflateBytes).toString('base64') : undefined,
126161
}, this.protocol.getUnderlyingSocket());
127162
}
128163

129-
private async spawn(locale: string, buffer: VSBuffer): Promise<cp.ChildProcess> {
130-
this.logger.trace('Getting NLS configuration...');
131-
const config = await getNlsConfiguration(locale, this.environment.userDataPath);
132-
this.logger.trace('Spawning extension host...');
164+
private async spawn(buffer: VSBuffer, inflateBytes: Uint8Array | undefined): Promise<cp.ChildProcess> {
165+
this.logger.debug('Getting NLS configuration...');
166+
const config = await getNlsConfiguration(this.params.language, this.environment.userDataPath);
167+
this.logger.debug('Spawning extension host...');
133168
const proc = cp.fork(
134169
FileAccess.asFileUri('bootstrap-fork', require).fsPath,
135170
// While not technically necessary, makes it easier to tell which process
@@ -158,7 +193,7 @@ export class ExtensionHostConnection extends Connection {
158193
this.dispose();
159194
});
160195
proc.on('exit', (code) => {
161-
this.logger.trace('Exited', field('code', code));
196+
this.logger.debug('Exited', field('code', code));
162197
this.dispose();
163198
});
164199
if (proc.stdout && proc.stderr) {
@@ -177,20 +212,20 @@ export class ExtensionHostConnection extends Connection {
177212
}
178213
break;
179214
case 'VSCODE_EXTHOST_DISCONNECTED':
180-
this.logger.trace('Going offline');
215+
this.logger.debug('Got disconnected message');
181216
this.setOffline();
182217
break;
183218
case 'VSCODE_EXTHOST_IPC_READY':
184-
this.logger.trace('Got ready message');
185-
this.sendInitMessage(buffer);
219+
this.logger.debug('Handshake completed');
220+
this.sendInitMessage(buffer, inflateBytes);
186221
break;
187222
default:
188223
this.logger.error('Unexpected message', field('event', event));
189224
break;
190225
}
191226
});
192227

193-
this.logger.trace('Waiting for handshake...');
228+
this.logger.debug('Waiting for handshake...');
194229
return proc;
195230
}
196231
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ export class Protocol extends PersistentProtocol {
130130
}
131131

132132
/**
133-
* Get inflateBytes in base64 format from the current socket.
133+
* Get inflateBytes from the current socket.
134134
*/
135-
public get inflateBytes(): string | undefined {
135+
public get inflateBytes(): Uint8Array | undefined {
136136
const socket = this.getSocket();
137137
return socket instanceof WebSocketNodeSocket
138-
? Buffer.from(socket.recordedInflateBytes.buffer).toString('base64')
138+
? socket.recordedInflateBytes.buffer
139139
: undefined;
140140
}
141141
}

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

+36-40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { field } from '@coder/logger';
21
import * as fs from 'fs';
32
import * as net from 'net';
43
import { release } from 'os';
@@ -140,65 +139,69 @@ export class Vscode {
140139
switch (message.desiredConnectionType) {
141140
case ConnectionType.ExtensionHost:
142141
case ConnectionType.Management:
142+
// Initialize connection map for this type of connection.
143143
if (!this.connections.has(message.desiredConnectionType)) {
144144
this.connections.set(message.desiredConnectionType, new Map());
145145
}
146146
const connections = this.connections.get(message.desiredConnectionType)!;
147147

148-
const ok = async () => {
149-
return message.desiredConnectionType === ConnectionType.ExtensionHost
150-
? { debugPort: await this.getDebugPort() }
151-
: { type: 'ok' };
152-
};
153-
154148
const token = protocol.options.reconnectionToken;
155-
if (protocol.options.reconnection && connections.has(token)) {
156-
protocol.sendMessage(await ok());
157-
const buffer = protocol.readEntireBuffer();
158-
protocol.dispose();
159-
return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
160-
} else if (protocol.options.reconnection || connections.has(token)) {
161-
throw new Error(protocol.options.reconnection
162-
? 'Unrecognized reconnection token'
163-
: 'Duplicate reconnection token'
164-
);
149+
let connection = connections.get(token);
150+
if (protocol.options.reconnection && connection) {
151+
return connection.reconnect(protocol);
165152
}
166153

167-
logger.debug('New connection', field('token', token));
168-
protocol.sendMessage(await ok());
154+
// This probably means the process restarted so the session was lost
155+
// while the browser remained open.
156+
if (protocol.options.reconnection) {
157+
throw new Error(`Unable to reconnect; session no longer exists (${token})`);
158+
}
159+
160+
// This will probably never happen outside a chance collision.
161+
if (connection) {
162+
throw new Error('Unable to connect; token is already in use');
163+
}
169164

170-
let connection: Connection;
165+
// Now that the initial exchange has completed we can create the actual
166+
// connection on top of the protocol then send it to whatever uses it.
171167
if (message.desiredConnectionType === ConnectionType.Management) {
172-
connection = new ManagementConnection(protocol, token);
168+
// The management connection is used by firing onDidClientConnect
169+
// which makes the IPC server become aware of the connection.
170+
connection = new ManagementConnection(protocol);
173171
this._onDidClientConnect.fire({
174-
protocol, onDidClientDisconnect: connection.onClose,
172+
protocol,
173+
onDidClientDisconnect: connection.onClose,
175174
});
176175
} else {
177-
const buffer = protocol.readEntireBuffer();
176+
// The extension host connection is used by spawning an extension host
177+
// and passing the socket into it.
178178
connection = new ExtensionHostConnection(
179-
message.args ? message.args.language : 'en',
180-
protocol, buffer, token,
179+
protocol,
180+
{
181+
language: 'en',
182+
...message.args,
183+
},
181184
this.services.get(IEnvironmentService) as INativeEnvironmentService,
182185
);
183186
}
184187
connections.set(token, connection);
185-
connection.onClose(() => {
186-
logger.debug('Connection closed', field('token', token));
187-
connections.delete(token);
188-
});
188+
connection.onClose(() => connections.delete(token));
189+
189190
this.disposeOldOfflineConnections(connections);
191+
logger.debug(`${connections.size} active ${connection.name} connection(s)`);
190192
break;
191-
case ConnectionType.Tunnel: return protocol.tunnel();
192-
default: throw new Error('Unrecognized connection type');
193+
case ConnectionType.Tunnel:
194+
return protocol.tunnel();
195+
default:
196+
throw new Error(`Unrecognized connection type ${message.desiredConnectionType}`);
193197
}
194198
}
195199

196200
private disposeOldOfflineConnections(connections: Map<string, Connection>): void {
197201
const offline = Array.from(connections.values())
198202
.filter((connection) => typeof connection.offline !== 'undefined');
199203
for (let i = 0, max = offline.length - this.maxExtraOfflineConnections; i < max; ++i) {
200-
logger.debug('Disposing offline connection', field('token', offline[i].token));
201-
offline[i].dispose();
204+
offline[i].dispose("old");
202205
}
203206
}
204207

@@ -292,11 +295,4 @@ export class Vscode {
292295
});
293296
});
294297
}
295-
296-
/**
297-
* TODO: implement.
298-
*/
299-
private async getDebugPort(): Promise<number | undefined> {
300-
return undefined;
301-
}
302298
}

src/node/routes/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const register = async (
4141
if (error) {
4242
return reject(error)
4343
}
44-
logger.trace(plural(count, `${count} active connection`))
44+
logger.debug(plural(count, `${count} active connection`))
4545
resolve(count > 0)
4646
})
4747
})

0 commit comments

Comments
 (0)