Skip to content

Commit f4b17b6

Browse files
committed
Synchronously bind to proxy events
This eliminates any chance whatsoever of missing events due to binding too late.
1 parent 20232c9 commit f4b17b6

File tree

16 files changed

+85
-88
lines changed

16 files changed

+85
-88
lines changed

packages/protocol/src/browser/modules/child_process.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import * as cp from "child_process";
22
import * as net from "net";
33
import * as stream from "stream";
44
import { callbackify } from "util";
5-
import { ClientProxy, Module } from "../../common/proxy";
5+
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
66
import { ChildProcessModuleProxy, ChildProcessProxy } from "../../node/modules/child_process";
77
import { ClientWritableProxy, ClientReadableProxy, Readable, Writable } from "./stream";
88

99
// tslint:disable completed-docs
1010

11-
export interface ClientChildProcessProxy extends ChildProcessProxy {
12-
proxyId: number | Module;
13-
}
11+
export interface ClientChildProcessProxy extends ChildProcessProxy, ClientServerProxy {}
1412

1513
export interface ClientChildProcessProxies {
1614
childProcess: ClientChildProcessProxy;
@@ -110,8 +108,7 @@ export class ChildProcess extends ClientProxy<ClientChildProcessProxy> implement
110108
}
111109
}
112110

113-
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy {
114-
proxyId: number | Module;
111+
interface ClientChildProcessModuleProxy extends ChildProcessModuleProxy, ClientServerProxy {
115112
exec(command: string, options?: { encoding?: string | null } & cp.ExecOptions | null, callback?: ((error: cp.ExecException | null, stdin: string | Buffer, stdout: string | Buffer) => void)): Promise<ClientChildProcessProxies>;
116113
fork(modulePath: string, args?: string[], options?: cp.ForkOptions): Promise<ClientChildProcessProxies>;
117114
spawn(command: string, args?: string[], options?: cp.SpawnOptions): Promise<ClientChildProcessProxies>;

packages/protocol/src/browser/modules/fs.ts

+5-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as fs from "fs";
22
import { callbackify } from "util";
3-
import { Batch, ClientProxy, Module } from "../../common/proxy";
3+
import { Batch, ClientProxy, ClientServerProxy } from "../../common/proxy";
44
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
55
import { FsModuleProxy, ReadStreamProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
66
import { Readable, Writable } from "./stream";
@@ -38,9 +38,7 @@ class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.P
3838
}
3939
}
4040

41-
interface ClientWatcherProxy extends WatcherProxy {
42-
proxyId: number | Module;
43-
}
41+
interface ClientWatcherProxy extends WatcherProxy, ClientServerProxy {}
4442

4543
class Watcher extends ClientProxy<ClientWatcherProxy> implements fs.FSWatcher {
4644
public close(): void {
@@ -52,9 +50,7 @@ class Watcher extends ClientProxy<ClientWatcherProxy> implements fs.FSWatcher {
5250
}
5351
}
5452

55-
interface ClientReadStreamProxy extends ReadStreamProxy {
56-
proxyId: number | Module;
57-
}
53+
interface ClientReadStreamProxy extends ReadStreamProxy, ClientServerProxy {}
5854

5955
class ReadStream extends Readable<ClientReadStreamProxy> implements fs.ReadStream {
6056
public get bytesRead(): number {
@@ -70,9 +66,7 @@ class ReadStream extends Readable<ClientReadStreamProxy> implements fs.ReadStrea
7066
}
7167
}
7268

73-
interface ClientWriteStreamProxy extends WriteStreamProxy {
74-
proxyId: number | Module;
75-
}
69+
interface ClientWriteStreamProxy extends WriteStreamProxy, ClientServerProxy {}
7670

7771
class WriteStream extends Writable<ClientWriteStreamProxy> implements fs.WriteStream {
7872
public get bytesWritten(): number {
@@ -88,8 +82,7 @@ class WriteStream extends Writable<ClientWriteStreamProxy> implements fs.WriteSt
8882
}
8983
}
9084

91-
interface ClientFsModuleProxy extends FsModuleProxy {
92-
proxyId: number | Module;
85+
interface ClientFsModuleProxy extends FsModuleProxy, ClientServerProxy {
9386
createReadStream(path: fs.PathLike, options?: any): Promise<ClientReadStreamProxy>;
9487
createWriteStream(path: fs.PathLike, options?: any): Promise<ClientWriteStreamProxy>;
9588
watch(filename: fs.PathLike, options?: IEncodingOptions): Promise<ClientWatcherProxy>;

packages/protocol/src/browser/modules/net.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import * as net from "net";
22
import { callbackify } from "util";
3-
import { ClientProxy, Module } from "../../common/proxy";
3+
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
44
import { NetModuleProxy, NetServerProxy, NetSocketProxy } from "../../node/modules/net";
55
import { Duplex } from "./stream";
66

77
// tslint:disable completed-docs
88

9-
interface ClientNetSocketProxy extends NetSocketProxy {
10-
proxyId: number | Module;
11-
}
9+
interface ClientNetSocketProxy extends NetSocketProxy, ClientServerProxy {}
1210

1311
export class Socket extends Duplex<ClientNetSocketProxy> implements net.Socket {
1412
private _connecting: boolean = false;
@@ -130,8 +128,7 @@ export class Socket extends Duplex<ClientNetSocketProxy> implements net.Socket {
130128
}
131129
}
132130

133-
interface ClientNetServerProxy extends NetServerProxy {
134-
proxyId: number | Module;
131+
interface ClientNetServerProxy extends NetServerProxy, ClientServerProxy {
135132
onConnection(cb: (proxy: ClientNetSocketProxy) => void): Promise<void>;
136133
}
137134

@@ -217,8 +214,7 @@ export class Server extends ClientProxy<ClientNetServerProxy> implements net.Ser
217214

218215
type NodeNet = typeof net;
219216

220-
interface ClientNetModuleProxy extends NetModuleProxy {
221-
proxyId: number | Module;
217+
interface ClientNetModuleProxy extends NetModuleProxy, ClientServerProxy {
222218
createSocket(options?: net.SocketConstructorOpts): Promise<ClientNetSocketProxy>;
223219
createConnection(target: string | number | net.NetConnectOpts, host?: string): Promise<ClientNetSocketProxy>;
224220
createServer(options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean }): Promise<ClientNetServerProxy>;

packages/protocol/src/browser/modules/node-pty.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import * as pty from "node-pty";
2-
import { ClientProxy, Module } from "../../common/proxy";
2+
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
33
import { NodePtyModuleProxy, NodePtyProcessProxy } from "../../node/modules/node-pty";
44

55
// tslint:disable completed-docs
66

7-
interface ClientNodePtyProcessProxy extends NodePtyProcessProxy {
8-
proxyId: number | Module;
9-
}
7+
interface ClientNodePtyProcessProxy extends NodePtyProcessProxy, ClientServerProxy {}
108

119
export class NodePtyProcess extends ClientProxy<ClientNodePtyProcessProxy> implements pty.IPty {
1210
private _pid = -1;
@@ -59,8 +57,7 @@ export class NodePtyProcess extends ClientProxy<ClientNodePtyProcessProxy> imple
5957

6058
type NodePty = typeof pty;
6159

62-
interface ClientNodePtyModuleProxy extends NodePtyModuleProxy {
63-
proxyId: number | Module;
60+
interface ClientNodePtyModuleProxy extends NodePtyModuleProxy, ClientServerProxy {
6461
spawn(file: string, args: string[] | string, options: pty.IPtyForkOptions): Promise<ClientNodePtyProcessProxy>;
6562
}
6663

packages/protocol/src/browser/modules/spdlog.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import * as spdlog from "spdlog";
2-
import { ClientProxy, Module } from "../../common/proxy";
2+
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
33
import { RotatingLoggerProxy, SpdlogModuleProxy } from "../../node/modules/spdlog";
44

55
// tslint:disable completed-docs
66

7-
interface ClientRotatingLoggerProxy extends RotatingLoggerProxy {
8-
proxyId: number | Module;
9-
}
7+
interface ClientRotatingLoggerProxy extends RotatingLoggerProxy, ClientServerProxy {}
108

119
class RotatingLogger extends ClientProxy<ClientRotatingLoggerProxy> implements spdlog.RotatingLogger {
1210
public constructor(
@@ -35,8 +33,7 @@ class RotatingLogger extends ClientProxy<ClientRotatingLoggerProxy> implements s
3533
}
3634
}
3735

38-
interface ClientSpdlogModuleProxy extends SpdlogModuleProxy {
39-
proxyId: number | Module;
36+
interface ClientSpdlogModuleProxy extends SpdlogModuleProxy, ClientServerProxy {
4037
createLogger(name: string, filePath: string, fileSize: number, fileCount: number): Promise<ClientRotatingLoggerProxy>;
4138
}
4239

packages/protocol/src/browser/modules/stream.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import * as stream from "stream";
22
import { callbackify } from "util";
3-
import { ClientProxy, Module } from "../../common/proxy";
3+
import { ClientProxy, ClientServerProxy } from "../../common/proxy";
44
import { isPromise } from "../../common/util";
55
import { DuplexProxy, ReadableProxy, WritableProxy } from "../../node/modules/stream";
66

77
// tslint:disable completed-docs
88

9-
export interface ClientWritableProxy extends WritableProxy {
10-
proxyId: number | Module;
11-
}
9+
export interface ClientWritableProxy extends WritableProxy, ClientServerProxy {}
1210

1311
export class Writable<T extends ClientWritableProxy = ClientWritableProxy> extends ClientProxy<T> implements stream.Writable {
1412
public get writable(): boolean {
@@ -93,9 +91,7 @@ export class Writable<T extends ClientWritableProxy = ClientWritableProxy> exten
9391
}
9492
}
9593

96-
export interface ClientReadableProxy extends ReadableProxy {
97-
proxyId: number | Module;
98-
}
94+
export interface ClientReadableProxy extends ReadableProxy, ClientServerProxy {}
9995

10096
export class Readable<T extends ClientReadableProxy = ClientReadableProxy> extends ClientProxy<T> implements stream.Readable {
10197
public get readable(): boolean {
@@ -153,6 +149,9 @@ export class Readable<T extends ClientReadableProxy = ClientReadableProxy> exten
153149
public pipe<P extends NodeJS.WritableStream>(destination: P, options?: { end?: boolean }): P {
154150
// tslint:disable-next-line no-any this will be a Writable instance.
155151
const writableProxy = (destination as any as Writable).proxyPromise;
152+
if (!writableProxy) {
153+
throw new Error("can only pipe stream proxies");
154+
}
156155
this.catch(
157156
isPromise(writableProxy)
158157
? writableProxy.then((p) => this.proxy.pipe(p, options))
@@ -181,9 +180,7 @@ export class Readable<T extends ClientReadableProxy = ClientReadableProxy> exten
181180
}
182181
}
183182

184-
export interface ClientDuplexProxy extends DuplexProxy {
185-
proxyId: number | Module;
186-
}
183+
export interface ClientDuplexProxy extends DuplexProxy, ClientServerProxy {}
187184

188185
export class Duplex<T extends ClientDuplexProxy = ClientDuplexProxy> extends Writable<T> implements stream.Duplex, stream.Readable {
189186
private readonly _readable: Readable;

packages/protocol/src/browser/modules/trash.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import * as trash from "trash";
2-
import { Module } from "../../common/proxy";
2+
import { ClientServerProxy } from "../../common/proxy";
33
import { TrashModuleProxy } from "../../node/modules/trash";
44

55
// tslint:disable completed-docs
66

7-
interface ClientTrashModuleProxy extends TrashModuleProxy {
8-
proxyId: number | Module;
9-
}
7+
interface ClientTrashModuleProxy extends TrashModuleProxy, ClientServerProxy {}
108

119
export class TrashModule {
1210
public constructor(private readonly proxy: ClientTrashModuleProxy) {}

packages/protocol/src/common/proxy.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,14 @@ export abstract class ClientProxy<T extends ClientServerProxy> extends EventEmit
116116

117117
/**
118118
* Proxy to the actual instance on the server. Every method must only accept
119-
* serializable arguments and must return promises with serializable values. If
120-
* a proxy itself has proxies on creation (like how ChildProcess has stdin),
119+
* serializable arguments and must return promises with serializable values.
120+
*
121+
* If a proxy itself has proxies on creation (like how ChildProcess has stdin),
121122
* then it should return all of those at once, otherwise you will miss events
122123
* from those child proxies and fail to dispose them properly.
124+
*
125+
* Events listeners are added client-side (since all events automatically
126+
* forward to the client), so onDone and onEvent do not need to be asynchronous.
123127
*/
124128
export interface ServerProxy {
125129
/**
@@ -131,17 +135,20 @@ export interface ServerProxy {
131135
* This is used instead of an event to force it to be implemented since there
132136
* would be no guarantee the implementation would remember to emit the event.
133137
*/
134-
onDone(cb: () => void): Promise<void>;
138+
onDone(cb: () => void): void;
135139

136140
/**
137141
* Listen to all possible events. On the client, this is to reduce boilerplate
138142
* that would just be a bunch of error-prone forwarding of each individual
139-
* event from the proxy to its own emitter. It also fixes a timing issue
140-
* because we just always send all events from the server, so we never miss
141-
* any due to listening too late.
143+
* event from the proxy to its own emitter.
144+
*
145+
* It also fixes a timing issue because we just always send all events from
146+
* the server, so we never miss any due to listening too late.
147+
*
148+
* This cannot be async because then we can bind to the events too late.
142149
*/
143150
// tslint:disable-next-line no-any
144-
onEvent(cb: (event: string, ...args: any[]) => void): Promise<void>;
151+
onEvent(cb: (event: string, ...args: any[]) => void): void;
145152
}
146153

147154
/**

packages/protocol/src/node/modules/child_process.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class ChildProcessProxy implements ServerProxy {
4343
return this.process.pid;
4444
}
4545

46-
public async onDone(cb: () => void): Promise<void> {
46+
public onDone(cb: () => void): void {
4747
this.process.on("close", cb);
4848
}
4949

@@ -53,7 +53,7 @@ export class ChildProcessProxy implements ServerProxy {
5353
}
5454

5555
// tslint:disable-next-line no-any
56-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
56+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
5757
this.process.on("close", (code, signal) => cb("close", code, signal));
5858
this.process.on("disconnect", () => cb("disconnect"));
5959
this.process.on("error", (error) => cb("error", error));

packages/protocol/src/node/modules/fs.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export class ReadStreamProxy extends ReadableProxy<fs.ReadStream> {
4848
}
4949

5050
// tslint:disable-next-line no-any
51-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
52-
await super.onEvent(cb);
51+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
52+
super.onEvent(cb);
5353
this.stream.on("open", (fd) => cb("open", fd));
5454
}
5555
}
@@ -65,8 +65,8 @@ export class WriteStreamProxy extends WritableProxy<fs.WriteStream> {
6565
}
6666

6767
// tslint:disable-next-line no-any
68-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
69-
await super.onEvent(cb);
68+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
69+
super.onEvent(cb);
7070
this.stream.on("open", (fd) => cb("open", fd));
7171
}
7272
}
@@ -83,13 +83,13 @@ export class WatcherProxy implements ServerProxy {
8383
this.watcher.removeAllListeners();
8484
}
8585

86-
public async onDone(cb: () => void): Promise<void> {
86+
public onDone(cb: () => void): void {
8787
this.watcher.on("close", cb);
8888
this.watcher.on("error", cb);
8989
}
9090

9191
// tslint:disable-next-line no-any
92-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
92+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
9393
this.watcher.on("change", (event, filename) => cb("change", event, filename));
9494
this.watcher.on("close", () => cb("close"));
9595
this.watcher.on("error", (error) => cb("error", error));

packages/protocol/src/node/modules/net.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export class NetSocketProxy extends DuplexProxy<net.Socket> {
2424
this.stream.unref();
2525
}
2626

27-
public async onDone(cb: () => void): Promise<void> {
27+
public onDone(cb: () => void): void {
2828
this.stream.on("close", cb);
2929
}
3030

3131
// tslint:disable-next-line no-any
32-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
33-
await super.onEvent(cb);
32+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
33+
super.onEvent(cb);
3434
this.stream.on("connect", () => cb("connect"));
3535
this.stream.on("lookup", (error, address, family, host) => cb("lookup", error, address, family, host));
3636
this.stream.on("timeout", () => cb("timeout"));
@@ -65,12 +65,12 @@ export class NetServerProxy implements ServerProxy {
6565
this.server.removeAllListeners();
6666
}
6767

68-
public async onDone(cb: () => void): Promise<void> {
68+
public onDone(cb: () => void): void {
6969
this.server.on("close", cb);
7070
}
7171

7272
// tslint:disable-next-line no-any
73-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
73+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
7474
this.server.on("close", () => cb("close"));
7575
this.server.on("error", (error) => cb("error", error));
7676
this.server.on("listening", () => cb("listening"));

packages/protocol/src/node/modules/node-pty.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class NodePtyProcessProxy implements ServerProxy {
4747
this.process.write(data);
4848
}
4949

50-
public async onDone(cb: () => void): Promise<void> {
50+
public onDone(cb: () => void): void {
5151
this.process.on("exit", cb);
5252
}
5353

@@ -58,7 +58,7 @@ export class NodePtyProcessProxy implements ServerProxy {
5858
}
5959

6060
// tslint:disable-next-line no-any
61-
public async onEvent(cb: (event: string, ...args: any[]) => void): Promise<void> {
61+
public onEvent(cb: (event: string, ...args: any[]) => void): void {
6262
this.emitter.on("process", (process) => cb("process", process));
6363
this.process.on("data", (data) => cb("data", data));
6464
this.process.on("exit", (exitCode, signal) => cb("exit", exitCode, signal));

packages/protocol/src/node/modules/spdlog.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class RotatingLoggerProxy implements ServerProxy {
2121
public async flush (): Promise<void> { this.logger.flush(); }
2222
public async drop (): Promise<void> { this.logger.drop(); }
2323

24-
public async onDone(cb: () => void): Promise<void> {
24+
public onDone(cb: () => void): void {
2525
this.emitter.on("dispose", cb);
2626
}
2727

@@ -32,7 +32,7 @@ export class RotatingLoggerProxy implements ServerProxy {
3232
}
3333

3434
// tslint:disable-next-line no-any
35-
public async onEvent(_cb: (event: string, ...args: any[]) => void): Promise<void> {
35+
public onEvent(_cb: (event: string, ...args: any[]) => void): void {
3636
// No events.
3737
}
3838
}

0 commit comments

Comments
 (0)