Skip to content

Commit d505df9

Browse files
committed
PersistentProtocol
1 parent 356874f commit d505df9

9 files changed

Lines changed: 966 additions & 218 deletions

File tree

src/vs/base/parts/ipc/node/ipc.net.ts

Lines changed: 641 additions & 125 deletions
Large diffs are not rendered by default.

src/vs/base/parts/ipc/test/node/ipc.net.test.ts

Lines changed: 173 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,68 +6,143 @@
66
import * as assert from 'assert';
77
import { Socket } from 'net';
88
import { EventEmitter } from 'events';
9-
import { Protocol } from 'vs/base/parts/ipc/node/ipc.net';
9+
import { Protocol, PersistentProtocol } from 'vs/base/parts/ipc/node/ipc.net';
1010

11-
class MockDuplex extends EventEmitter {
11+
class MessageStream {
1212

13-
private _cache: Buffer[] = [];
13+
private _currentComplete: ((data: Buffer) => void) | null;
14+
private _messages: Buffer[];
1415

15-
readonly destroyed = false;
16+
constructor(x: Protocol | PersistentProtocol) {
17+
this._currentComplete = null;
18+
this._messages = [];
19+
x.onMessage(data => {
20+
this._messages.push(data);
21+
this._trigger();
22+
});
23+
}
1624

17-
private _deliver(): void {
18-
if (this._cache.length) {
19-
const data = Buffer.concat(this._cache);
20-
this._cache.length = 0;
21-
this.emit('data', data);
25+
private _trigger(): void {
26+
if (!this._currentComplete) {
27+
return;
2228
}
29+
if (this._messages.length === 0) {
30+
return;
31+
}
32+
const complete = this._currentComplete;
33+
const msg = this._messages.shift()!;
34+
35+
this._currentComplete = null;
36+
complete(msg);
37+
}
38+
39+
public waitForOne(): Promise<Buffer> {
40+
return new Promise<Buffer>((complete) => {
41+
this._currentComplete = complete;
42+
this._trigger();
43+
});
44+
}
45+
}
46+
47+
class EtherStream extends EventEmitter {
48+
constructor(
49+
private readonly _ether: Ether,
50+
private readonly _name: 'a' | 'b'
51+
) {
52+
super();
2353
}
2454

2555
write(data: Buffer, cb?: Function): boolean {
26-
this._cache.push(data);
27-
setImmediate(() => this._deliver());
56+
this._ether.write(this._name, data);
2857
return true;
2958
}
3059
}
3160

61+
class Ether {
62+
63+
private readonly _a: EtherStream;
64+
private readonly _b: EtherStream;
65+
66+
private _ab: Buffer[];
67+
private _ba: Buffer[];
68+
69+
public get a(): Socket {
70+
return <any>this._a;
71+
}
72+
73+
public get b(): Socket {
74+
return <any>this._b;
75+
}
76+
77+
constructor() {
78+
this._a = new EtherStream(this, 'a');
79+
this._b = new EtherStream(this, 'b');
80+
this._ab = [];
81+
this._ba = [];
82+
}
83+
84+
public write(from: 'a' | 'b', data: Buffer): void {
85+
if (from === 'a') {
86+
this._ab.push(data);
87+
} else {
88+
this._ba.push(data);
89+
}
90+
91+
setImmediate(() => this._deliver());
92+
}
93+
94+
private _deliver(): void {
95+
96+
if (this._ab.length > 0) {
97+
const data = Buffer.concat(this._ab);
98+
this._ab.length = 0;
99+
this._b.emit('data', data);
100+
setImmediate(() => this._deliver());
101+
return;
102+
}
103+
104+
if (this._ba.length > 0) {
105+
const data = Buffer.concat(this._ba);
106+
this._ba.length = 0;
107+
this._a.emit('data', data);
108+
setImmediate(() => this._deliver());
109+
return;
110+
}
111+
112+
}
113+
}
32114

33115
suite('IPC, Socket Protocol', () => {
34116

35-
let stream: Socket;
117+
let ether: Ether;
36118

37119
setup(() => {
38-
stream = <any>new MockDuplex();
120+
ether = new Ether();
39121
});
40122

41123
test('read/write', async () => {
42124

43-
const a = new Protocol(stream);
44-
const b = new Protocol(stream);
125+
const a = new Protocol(ether.a);
126+
const b = new Protocol(ether.b);
127+
const bMessages = new MessageStream(b);
45128

46-
await new Promise(resolve => {
47-
const sub = b.onMessage(data => {
48-
sub.dispose();
49-
assert.equal(data.toString(), 'foobarfarboo');
50-
resolve(undefined);
51-
});
52-
a.send(Buffer.from('foobarfarboo'));
53-
});
54-
return new Promise(resolve => {
55-
const sub_1 = b.onMessage(data => {
56-
sub_1.dispose();
57-
assert.equal(data.readInt8(0), 123);
58-
resolve(undefined);
59-
});
60-
const buffer = Buffer.allocUnsafe(1);
61-
buffer.writeInt8(123, 0);
62-
a.send(buffer);
63-
});
129+
a.send(Buffer.from('foobarfarboo'));
130+
const msg1 = await bMessages.waitForOne();
131+
assert.equal(msg1.toString(), 'foobarfarboo');
132+
133+
const buffer = Buffer.allocUnsafe(1);
134+
buffer.writeInt8(123, 0);
135+
a.send(buffer);
136+
const msg2 = await bMessages.waitForOne();
137+
assert.equal(msg2.readInt8(0), 123);
64138
});
65139

66140

67-
test('read/write, object data', () => {
141+
test('read/write, object data', async () => {
68142

69-
const a = new Protocol(stream);
70-
const b = new Protocol(stream);
143+
const a = new Protocol(ether.a);
144+
const b = new Protocol(ether.b);
145+
const bMessages = new MessageStream(b);
71146

72147
const data = {
73148
pi: Math.PI,
@@ -77,12 +152,68 @@ suite('IPC, Socket Protocol', () => {
77152
};
78153

79154
a.send(Buffer.from(JSON.stringify(data)));
155+
const msg = await bMessages.waitForOne();
156+
assert.deepEqual(JSON.parse(msg.toString()), data);
157+
});
80158

81-
return new Promise(resolve => {
82-
b.onMessage(msg => {
83-
assert.deepEqual(JSON.parse(msg.toString()), data);
84-
resolve(undefined);
85-
});
86-
});
159+
});
160+
161+
suite('PersistentProtocol reconnection', () => {
162+
let ether: Ether;
163+
164+
setup(() => {
165+
ether = new Ether();
166+
});
167+
168+
test('acks get piggybacked with messages', async () => {
169+
const a = new PersistentProtocol(ether.a);
170+
const aMessages = new MessageStream(a);
171+
const b = new PersistentProtocol(ether.b);
172+
const bMessages = new MessageStream(b);
173+
174+
a.send(Buffer.from('a1'));
175+
assert.equal(a.unacknowledgedCount, 1);
176+
assert.equal(b.unacknowledgedCount, 0);
177+
178+
a.send(Buffer.from('a2'));
179+
assert.equal(a.unacknowledgedCount, 2);
180+
assert.equal(b.unacknowledgedCount, 0);
181+
182+
a.send(Buffer.from('a3'));
183+
assert.equal(a.unacknowledgedCount, 3);
184+
assert.equal(b.unacknowledgedCount, 0);
185+
186+
const a1 = await bMessages.waitForOne();
187+
assert.equal(a1.toString(), 'a1');
188+
assert.equal(a.unacknowledgedCount, 3);
189+
assert.equal(b.unacknowledgedCount, 0);
190+
191+
const a2 = await bMessages.waitForOne();
192+
assert.equal(a2.toString(), 'a2');
193+
assert.equal(a.unacknowledgedCount, 3);
194+
assert.equal(b.unacknowledgedCount, 0);
195+
196+
const a3 = await bMessages.waitForOne();
197+
assert.equal(a3.toString(), 'a3');
198+
assert.equal(a.unacknowledgedCount, 3);
199+
assert.equal(b.unacknowledgedCount, 0);
200+
201+
b.send(Buffer.from('b1'));
202+
assert.equal(a.unacknowledgedCount, 3);
203+
assert.equal(b.unacknowledgedCount, 1);
204+
205+
const b1 = await aMessages.waitForOne();
206+
assert.equal(b1.toString(), 'b1');
207+
assert.equal(a.unacknowledgedCount, 0);
208+
assert.equal(b.unacknowledgedCount, 1);
209+
210+
a.send(Buffer.from('a4'));
211+
assert.equal(a.unacknowledgedCount, 1);
212+
assert.equal(b.unacknowledgedCount, 1);
213+
214+
const b2 = await bMessages.waitForOne();
215+
assert.equal(b2.toString(), 'a4');
216+
assert.equal(a.unacknowledgedCount, 1);
217+
assert.equal(b.unacknowledgedCount, 0);
87218
});
88219
});

src/vs/code/electron-main/app.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
5555
import * as errors from 'vs/base/common/errors';
5656
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
5757
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
58-
import { connectRemoteAgentManagement } from 'vs/platform/remote/node/remoteAgentConnection';
58+
import { connectRemoteAgentManagement, ManagementPersistentConnection } from 'vs/platform/remote/node/remoteAgentConnection';
5959
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
6060
import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService';
6161
import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
@@ -650,26 +650,27 @@ export class CodeApplication extends Disposable {
650650

651651
class ActiveConnection {
652652
private readonly _authority: string;
653-
private readonly _client: Promise<Client<RemoteAgentConnectionContext>>;
653+
private readonly _connection: Promise<ManagementPersistentConnection>;
654654
private readonly _disposeRunner: RunOnceScheduler;
655655

656656
constructor(authority: string, host: string, port: number) {
657657
this._authority = authority;
658-
this._client = connectRemoteAgentManagement(authority, host, port, `main`, isBuilt);
658+
this._connection = connectRemoteAgentManagement(authority, host, port, `main`, isBuilt);
659659
this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000);
660660
}
661661

662662
dispose(): void {
663663
this._disposeRunner.dispose();
664664
connectionPool.delete(this._authority);
665-
this._client.then((connection) => {
665+
this._connection.then((connection) => {
666666
connection.dispose();
667667
});
668668
}
669669

670-
getClient(): Promise<Client<RemoteAgentConnectionContext>> {
670+
async getClient(): Promise<Client<RemoteAgentConnectionContext>> {
671671
this._disposeRunner.schedule();
672-
return this._client;
672+
const connection = await this._connection;
673+
return connection.client;
673674
}
674675
}
675676

src/vs/platform/remote/node/remoteAgentConnection.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,58 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Client, BufferedProtocol } from 'vs/base/parts/ipc/node/ipc.net';
6+
import { Client, PersistentProtocol } from 'vs/base/parts/ipc/node/ipc.net';
77
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
8-
9-
export function connectRemoteAgentManagement(remoteAuthority: string, host: string, port: number, clientId: string, isBuilt: boolean): Promise<Client<RemoteAgentConnectionContext>> {
10-
throw new Error(`Not implemented`);
11-
}
12-
13-
export interface IExtensionHostConnectionResult {
14-
protocol: BufferedProtocol;
15-
debugPort?: number;
16-
}
8+
import { Disposable } from 'vs/base/common/lifecycle';
179

1810
export interface IRemoteExtensionHostStartParams {
1911
language: string;
2012
debugId?: string;
21-
break: boolean;
22-
port: number | null;
13+
break?: boolean;
14+
port?: number | null;
2315
updatePort?: boolean;
2416
}
2517

26-
export function connectRemoteAgentExtensionHost(host: string, port: number, startArguments: IRemoteExtensionHostStartParams, isBuilt: boolean): Promise<IExtensionHostConnectionResult> {
18+
export async function connectRemoteAgentManagement(remoteAuthority: string, host: string, port: number, clientId: string, isBuilt: boolean): Promise<ManagementPersistentConnection> {
2719
throw new Error(`Not implemented`);
2820
}
21+
22+
export async function connectRemoteAgentExtensionHost(host: string, port: number, startArguments: IRemoteExtensionHostStartParams, isBuilt: boolean): Promise<ExtensionHostPersistentConnection> {
23+
throw new Error(`Not implemented`);
24+
}
25+
26+
abstract class PersistentConnection extends Disposable {
27+
28+
public readonly reconnectionToken: string;
29+
public readonly protocol: PersistentProtocol;
30+
31+
constructor(reconnectionToken: string, protocol: PersistentProtocol) {
32+
super();
33+
this.reconnectionToken = reconnectionToken;
34+
this.protocol = protocol;
35+
}
36+
}
37+
38+
export class ManagementPersistentConnection extends PersistentConnection {
39+
40+
public readonly client: Client<RemoteAgentConnectionContext>;
41+
42+
constructor(remoteAuthority: string, host: string, port: number, clientId: string, isBuilt: boolean, reconnectionToken: string, protocol: PersistentProtocol) {
43+
super(reconnectionToken, protocol);
44+
45+
this.client = this._register(new Client<RemoteAgentConnectionContext>(protocol, {
46+
remoteAuthority: remoteAuthority,
47+
clientId: clientId
48+
}));
49+
}
50+
}
51+
52+
export class ExtensionHostPersistentConnection extends PersistentConnection {
53+
54+
public readonly debugPort: number | undefined;
55+
56+
constructor(host: string, port: number, startArguments: IRemoteExtensionHostStartParams, isBuilt: boolean, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) {
57+
super(reconnectionToken, protocol);
58+
this.debugPort = debugPort;
59+
}
60+
}

src/vs/workbench/services/extensions/electron-browser/extensionHost.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { URI } from 'vs/base/common/uri';
2020
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
2121
import { findFreePort, randomPort } from 'vs/base/node/ports';
2222
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
23-
import { Protocol, generateRandomPipeName, BufferedProtocol } from 'vs/base/parts/ipc/node/ipc.net';
23+
import { PersistentProtocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
2424
import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService';
2525
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
2626
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
@@ -361,7 +361,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
361361
// using a buffered message protocol here because between now
362362
// and the first time a `then` executes some messages might be lost
363363
// unless we immediately register a listener for `onMessage`.
364-
resolve(new BufferedProtocol(new Protocol(this._extensionHostConnection)));
364+
resolve(new PersistentProtocol(this._extensionHostConnection));
365365
});
366366

367367
}).then((protocol) => {

0 commit comments

Comments
 (0)