Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/util/node/worker.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Worker, WorkerOptions } from 'worker_threads';
7
8
export type RpcRequest = { id: number; fn: string; args: any[] };
9
10
export type RpcResponse = { id: number; res?: any; err?: Error };
11
12
/**
13
* Holds promises for RPC requests and resolves them when the call completes.
14
*/
15
export class RcpResponseHandler {
16
private nextId = 1;
17
18
private readonly handlers = new Map<number, { resolve: (res: any) => void; reject: (err: Error) => void }>();
19
20
public createHandler<T>(): { id: number; result: Promise<T> } {
21
const id = this.nextId++;
22
let resolve: (res: any) => void;
23
let reject: (err: Error) => void;
24
const result = new Promise<any>((res, rej) => {
25
resolve = res;
26
reject = rej;
27
});
28
this.handlers.set(id, { resolve: resolve!, reject: reject! });
29
return { id, result };
30
}
31
32
public handleResponse(response: RpcResponse) {
33
const handler = this.handlers.get(response.id);
34
if (!handler) {
35
return;
36
}
37
38
this.handlers.delete(response.id);
39
if (response.err) {
40
handler.reject(response.err);
41
} else {
42
handler.resolve(response.res);
43
}
44
}
45
46
/**
47
* Handle an unexpected error by logging it and rejecting all handlers.
48
*/
49
public handleError(err: Error) {
50
for (const handler of this.handlers.values()) {
51
handler.reject(err);
52
}
53
this.handlers.clear();
54
}
55
56
public clear() {
57
this.handlers.clear();
58
}
59
}
60
61
export type RpcProxy<ProxyType> = {
62
[K in keyof ProxyType]: ProxyType[K] extends ((...args: infer Args) => infer R) ? (...args: Args) => Promise<Awaited<R>> : never;
63
};
64
65
export function createRpcProxy<ProxyType>(remoteCall: (name: string, args: any[]) => Promise<any>): RpcProxy<ProxyType> {
66
const handler = {
67
get: (target: any, name: PropertyKey) => {
68
if (typeof name === 'string' && !target[name]) {
69
target[name] = (...myArgs: any[]) => {
70
return remoteCall(name, myArgs);
71
};
72
}
73
return target[name];
74
}
75
};
76
return new Proxy(Object.create(null), handler);
77
}
78
79
export class WorkerWithRpcProxy<WorkerProxyType, HostProxyType = {}> {
80
private readonly worker: Worker;
81
private readonly responseHandler = new RcpResponseHandler();
82
83
public readonly proxy: RpcProxy<WorkerProxyType>;
84
85
constructor(workerPath: string, workerOptions?: WorkerOptions, host?: HostProxyType) {
86
this.worker = new Worker(workerPath, workerOptions);
87
this.worker.on('message', async (msg: RpcRequest | RpcResponse) => {
88
if ('fn' in msg) {
89
try {
90
const response = await (host as any)?.[msg.fn].apply(host, msg.args);
91
this.worker.postMessage({ id: msg.id, res: response } satisfies RpcResponse);
92
} catch (err) {
93
this.worker.postMessage({ id: msg.id, err } satisfies RpcResponse);
94
}
95
} else {
96
this.responseHandler.handleResponse(msg);
97
}
98
});
99
this.worker.on('error', (err) => this.handleError(err));
100
101
this.worker.on('exit', code => {
102
if (code !== 0) {
103
this.handleError(new Error(`Worker thread exited with code ${code}.`));
104
}
105
});
106
107
this.proxy = createRpcProxy((fn: string, args: any[]): Promise<any> => {
108
if (!this.worker) {
109
throw new Error(`Worker was terminated!`);
110
}
111
112
const { id, result } = this.responseHandler.createHandler<any>();
113
this.worker.postMessage({ id, fn, args } satisfies RpcRequest);
114
return result;
115
});
116
}
117
118
terminate() {
119
this.worker.removeAllListeners();
120
this.worker.terminate();
121
this.responseHandler.clear();
122
}
123
124
/**
125
* Handle an unexpected error by logging it and rejecting all handlers.
126
*/
127
private handleError(err: Error) {
128
this.responseHandler.handleError(err);
129
}
130
}
131
132