Skip to content

Commit d77ba52

Browse files
committed
Reconnection works
1 parent ad17239 commit d77ba52

File tree

4 files changed

+70
-100
lines changed

4 files changed

+70
-100
lines changed

src/cli.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const main = async (): Promise<void> => {
195195

196196
if (args["open"]) {
197197
await open(serverAddress).catch(console.error);
198-
console.log(" - Opened URL");
198+
console.log(` - Opened ${serverAddress}`);
199199
}
200200
};
201201

src/connection.ts

+55-76
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,62 @@ import { getPathFromAmdModule } from "vs/base/common/amd";
44
import { VSBuffer } from "vs/base/common/buffer";
55
import { Emitter } from "vs/base/common/event";
66
import { ISocket } from "vs/base/parts/ipc/common/ipc.net";
7-
import { NodeSocket, WebSocketNodeSocket } from "vs/base/parts/ipc/node/ipc.net";
7+
import { NodeSocket } from "vs/base/parts/ipc/node/ipc.net";
88
import { ILogService } from "vs/platform/log/common/log";
99
import { IExtHostReadyMessage, IExtHostSocketMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol";
1010

1111
import { Protocol } from "vs/server/src/protocol";
1212
import { uriTransformerPath } from "vs/server/src/util";
1313

1414
export abstract class Connection {
15-
private readonly _onClose = new Emitter<void>();
15+
protected readonly _onClose = new Emitter<void>();
1616
public readonly onClose = this._onClose.event;
17+
protected disposed: boolean = false;
1718

18-
private timeout: NodeJS.Timeout | undefined;
19-
private readonly wait = 1000 * 60;
20-
21-
private closed: boolean = false;
22-
23-
public constructor(protected protocol: Protocol) {
24-
// onClose seems to mean we want to disconnect, so close immediately.
25-
protocol.onClose(() => this.close());
26-
27-
// If the socket closes, we want to wait before closing so we can
28-
// reconnect in the meantime.
29-
protocol.onSocketClose(() => {
30-
this.timeout = setTimeout(() => {
31-
this.close();
32-
}, this.wait);
33-
});
34-
}
19+
public constructor(protected protocol: Protocol) {}
3520

3621
/**
3722
* Set up the connection on a new socket.
3823
*/
39-
public reconnect(protocol: Protocol, buffer: VSBuffer): void {
40-
if (this.closed) {
41-
throw new Error("Cannot reconnect to closed connection");
42-
}
43-
clearTimeout(this.timeout as any); // Not sure why the type doesn't work.
44-
this.protocol = protocol;
45-
this.connect(protocol.getSocket(), buffer);
46-
}
47-
48-
/**
49-
* Close and clean up connection. This will also kill the socket the
50-
* connection is on. Probably not safe to reconnect once this has happened.
51-
*/
52-
protected close(): void {
53-
if (!this.closed) {
54-
this.closed = true;
55-
this.protocol.sendDisconnect();
56-
this.dispose();
57-
this.protocol.dispose();
58-
this._onClose.fire();
59-
}
60-
}
24+
public abstract reconnect(socket: ISocket, buffer: VSBuffer): void;
6125

6226
/**
6327
* Clean up the connection.
6428
*/
6529
protected abstract dispose(): void;
66-
67-
/**
68-
* Connect to a new socket.
69-
*/
70-
protected abstract connect(socket: ISocket, buffer: VSBuffer): void;
7130
}
7231

7332
/**
7433
* Used for all the IPC channels.
7534
*/
7635
export class ManagementConnection extends Connection {
77-
protected dispose(): void {
78-
// Nothing extra to do here.
36+
private timeout: NodeJS.Timeout | undefined;
37+
private readonly wait = 1000 * 60;
38+
39+
public constructor(protocol: Protocol) {
40+
super(protocol);
41+
protocol.onClose(() => this.dispose());
42+
protocol.onSocketClose(() => {
43+
this.timeout = setTimeout(() => this.dispose(), this.wait);
44+
});
7945
}
8046

81-
protected connect(socket: ISocket, buffer: VSBuffer): void {
47+
public reconnect(socket: ISocket, buffer: VSBuffer): void {
48+
clearTimeout(this.timeout as any); // Not sure why the type doesn't work.
8249
this.protocol.beginAcceptReconnection(socket, buffer);
8350
this.protocol.endAcceptReconnection();
8451
}
52+
53+
protected dispose(): void {
54+
if (!this.disposed) {
55+
clearTimeout(this.timeout as any); // Not sure why the type doesn't work.
56+
this.disposed = true;
57+
this.protocol.sendDisconnect();
58+
this.protocol.dispose();
59+
this.protocol.getSocket().end();
60+
this._onClose.fire();
61+
}
62+
}
8563
}
8664

8765
/**
@@ -90,38 +68,45 @@ export class ManagementConnection extends Connection {
9068
export class ExtensionHostConnection extends Connection {
9169
private process: cp.ChildProcess;
9270

93-
public constructor(protocol: Protocol, private readonly log: ILogService) {
71+
public constructor(
72+
protocol: Protocol, buffer: VSBuffer,
73+
private readonly log: ILogService,
74+
) {
9475
super(protocol);
95-
const socket = this.protocol.getSocket();
96-
const buffer = this.protocol.readEntireBuffer();
97-
this.process = this.spawn(socket, buffer);
76+
protocol.dispose();
77+
this.process = this.spawn(buffer);
9878
}
9979

10080
protected dispose(): void {
101-
this.process.kill();
81+
if (!this.disposed) {
82+
this.disposed = true;
83+
this.process.kill();
84+
this.protocol.getSocket().end();
85+
this._onClose.fire();
86+
}
10287
}
10388

104-
protected connect(socket: ISocket, buffer: VSBuffer): void {
105-
this.sendInitMessage(socket, buffer);
89+
public reconnect(socket: ISocket, buffer: VSBuffer): void {
90+
// This is just to set the new socket.
91+
this.protocol.beginAcceptReconnection(socket, null);
92+
this.protocol.dispose();
93+
this.sendInitMessage(buffer);
10694
}
10795

108-
private sendInitMessage(nodeSocket: ISocket, buffer: VSBuffer): void {
109-
const socket = nodeSocket instanceof NodeSocket
110-
? nodeSocket.socket
111-
: (nodeSocket as WebSocketNodeSocket).socket.socket;
112-
96+
private sendInitMessage(buffer: VSBuffer): void {
97+
const socket = this.protocol.getUnderlyingSocket();
11398
socket.pause();
11499

115100
const initMessage: IExtHostSocketMessage = {
116101
type: "VSCODE_EXTHOST_IPC_SOCKET",
117102
initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
118-
skipWebSocketFrames: nodeSocket instanceof NodeSocket,
103+
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
119104
};
120105

121106
this.process.send(initMessage, socket);
122107
}
123108

124-
private spawn(socket: ISocket, buffer: VSBuffer): cp.ChildProcess {
109+
private spawn(buffer: VSBuffer): cp.ChildProcess {
125110
const proc = cp.fork(
126111
getPathFromAmdModule(require, "bootstrap-fork"),
127112
[
@@ -142,20 +127,15 @@ export class ExtensionHostConnection extends Connection {
142127
},
143128
);
144129

145-
proc.on("error", (error) => {
146-
console.error(error);
147-
this.close();
148-
});
149-
150-
proc.on("exit", (code, signal) => {
151-
console.error("Extension host exited", { code, signal });
152-
this.close();
153-
});
130+
proc.on("error", () => this.dispose());
131+
proc.on("exit", () => this.dispose());
154132

155133
proc.stdout.setEncoding("utf8");
156134
proc.stderr.setEncoding("utf8");
157-
proc.stdout.on("data", (data) => this.log.info("Extension host stdout", data));
158-
proc.stderr.on("data", (data) => this.log.error("Extension host stderr", data));
135+
136+
proc.stdout.on("data", (d) => this.log.info("Extension host stdout", d));
137+
proc.stderr.on("data", (d) => this.log.error("Extension host stderr", d));
138+
159139
proc.on("message", (event) => {
160140
if (event && event.type === "__$console") {
161141
const severity = this.log[event.severity] ? event.severity : "info";
@@ -166,10 +146,9 @@ export class ExtensionHostConnection extends Connection {
166146
const listen = (message: IExtHostReadyMessage) => {
167147
if (message.type === "VSCODE_EXTHOST_IPC_READY") {
168148
proc.removeListener("message", listen);
169-
this.sendInitMessage(socket, buffer);
149+
this.sendInitMessage(buffer);
170150
}
171151
};
172-
173152
proc.on("message", listen);
174153

175154
return proc;

src/protocol.ts

+7-20
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ export interface SocketOptions {
1313
}
1414

1515
export class Protocol extends PersistentProtocol {
16-
private disposed: boolean = false;
17-
1816
public constructor(
1917
secWebsocketKey: string,
2018
socket: net.Socket,
@@ -25,15 +23,14 @@ export class Protocol extends PersistentProtocol {
2523
? new NodeSocket(socket)
2624
: new WebSocketNodeSocket(new NodeSocket(socket)),
2725
);
28-
socket.on("error", () => this.dispose());
29-
socket.on("end", () => this.dispose());
26+
socket.on("error", () => socket.destroy());
27+
socket.on("end", () => socket.destroy());
3028

3129
// This magic value is specified by the websocket spec.
3230
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
3331
const reply = crypto.createHash("sha1")
3432
.update(secWebsocketKey + magic)
3533
.digest("base64");
36-
3734
socket.write([
3835
"HTTP/1.1 101 Switching Protocols",
3936
"Upgrade: websocket",
@@ -42,21 +39,11 @@ export class Protocol extends PersistentProtocol {
4239
].join("\r\n") + "\r\n\r\n");
4340
}
4441

45-
public sendDisconnect(): void {
46-
if (!this.disposed) {
47-
super.sendDisconnect();
48-
}
49-
}
50-
51-
public dispose(error?: Error): void {
52-
if (!this.disposed) {
53-
this.disposed = true;
54-
if (error) {
55-
this.sendMessage({ type: "error", reason: error.message });
56-
}
57-
super.dispose();
58-
this.getSocket().dispose();
59-
}
42+
public getUnderlyingSocket(): net.Socket {
43+
const socket = this.getSocket();
44+
return socket instanceof NodeSocket
45+
? socket.socket
46+
: (socket as WebSocketNodeSocket).socket.socket;
6047
}
6148

6249
/**

src/server.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,9 @@ export class MainServer extends Server {
376376
try {
377377
await this.connect(await protocol.handshake(), protocol);
378378
} catch (error) {
379-
protocol.dispose(error);
379+
protocol.sendMessage({ type: "error", reason: error.message });
380+
protocol.dispose();
381+
protocol.getSocket().dispose();
380382
}
381383
});
382384

@@ -539,7 +541,7 @@ export class MainServer extends Server {
539541
protocol.sendMessage(ok);
540542
const buffer = protocol.readEntireBuffer();
541543
protocol.dispose();
542-
return connections.get(token)!.reconnect(protocol, buffer);
544+
return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
543545
}
544546

545547
if (protocol.options.reconnection || connections.has(token)) {
@@ -559,8 +561,10 @@ export class MainServer extends Server {
559561
onDidClientDisconnect: connection.onClose,
560562
});
561563
} else {
564+
const buffer = protocol.readEntireBuffer();
562565
connection = new ExtensionHostConnection(
563-
protocol, this.services.get(ILogService) as ILogService,
566+
protocol, buffer,
567+
this.services.get(ILogService) as ILogService,
564568
);
565569
}
566570
connections.set(protocol.options.reconnectionToken, connection);

0 commit comments

Comments
 (0)