ps. 对应 theia 版本 1.30.0 我们以 Theia 文件监听为例,在多进程模式下,Theia 会新起文件监听进程
bind<FileSystemWatcherService>(FileSystemWatcherService).toDynamicValue(
ctx => NSFW_SINGLE_THREADED
? createNsfwFileSystemWatcherService(ctx)
: spawnNsfwFileSystemWatcherServiceProcess(ctx)
).inSingletonScope();
IPCConnectionProvider
child process fork 子进程,并通过 IPCChannel 对其进行包装
const childProcess = this.fork(options);
const channel = new IPCChannel(childProcess);
childProcess.fork
通过 child process,先创建了 ipc-bootstrap 进程
const childProcess = cp.fork(path.join(__dirname, 'ipc-bootstrap'), options.args, forkOptions);
在该进程中加载 nsfw-wathcer 模块,并将新建的 IPCChannel 实例传给 nsfw-watcher 模块
dynamicRequire<{ default: IPCEntryPoint }>(entryPoint).default(new IPCChannel());
export default <IPCEntryPoint>(connection => {
const server = new NsfwFileSystemWatcherService(options);
const factory = new JsonRpcProxyFactory<FileSystemWatcherServiceClient>(server);
server.setClient(factory.createProxy());
factory.listen(connection);
});
IPCChannel
判断是主进程还是子进程,定义进程间通信接口
constructor(childProcess?: cp.ChildProcess) {
super();
if (childProcess) {
this.setupChildProcess(childProcess);
} else {
this.setupProcess();
}
this.messagePipe.onMessage(message => {
this.onMessageEmitter.fire(() => new Uint8ArrayReadBuffer(message));
});
}
对外暴露这两个方法
- onMessage: 监听其他进程消息
- getWriteBuffer: 创建 Uint8ArrayWriteBuffer 对象,可调用 commit 方法发送消息
JsonRpcProxyFactory
处理进程间通信
- listen 方法获取 IPCChannel 实例,从而通过 onMessage、getWriteBuffer 方法,实现进程间通信
connection => proxyFactory.listen(connection)
- setClient 或者构造函数设置目标对象
const dispatcher = ctx.container.get<FileSystemWatcherServiceDispatcher>(FileSystemWatcherServiceDispatcher);
const serverProxy = proxyFactory.createProxy();
// We need to call `.setClient` before listening, else the JSON-RPC calls won't go through.
serverProxy.setClient(dispatcher);
get(target: T, p: PropertyKey, receiver: any): any {
if (p === 'setClient') {
return (client: any) => {
this.target = client;
};
}
// ...
}
发送消息
在绑定 FileSystemWatcherService 实例方法时,返回的是 JsonRpcProxyFactory 代理对象
createProxy(): JsonRpcProxy<T> {
const result = new Proxy<T>(this as any, this);
return result as any;
}
const serverProxy = proxyFactory.createProxy();
serverProxy.setClient(dispatcher);
return serverProxy;
当我们在调用 FileSystemWatcherService 的实例方法时,实际上就会走到代理流程中
get(target: T, p: PropertyKey, receiver: any): any {
// ...
const isNotify = this.isNotification(p);
return (...args: any[]) => {
const method = p.toString();
const capturedError = new Error(`Request '${method}' failed`);
return this.connectionPromise.then(connection =>
new Promise<void>((resolve, reject) => {
try {
if (isNotify) {
connection.sendNotification(method, args);
resolve(undefined);
} else {
const resultPromise = connection.sendRequest(method, args) as Promise<any>;
resultPromise
.catch((err: any) => reject(this.deserializeError(capturedError, err)))
.then((result: any) => resolve(result));
}
} catch (err) {
reject(err);
}
})
);
};
}
首先,通过方法名判断是通知还是请求方法
protected isNotification(p: PropertyKey): boolean {
return p.toString().startsWith('notify') || p.toString().startsWith('on');
}
并且自定义不同的请求参数
- 通知:RpcMessageType.Notification
- 请求:RpcMessageType.Request
notification(buf: WriteBuffer, requestId: number, method: string, args: any[]): void {
this.encode<NotificationMessage>(buf, { type: RpcMessageType.Notification, id: requestId, method, args });
}
request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void {
this.encode<RequestMessage>(buf, { type: RpcMessageType.Request, id: requestId, method, args });
}
最后调用 getWriteBuffer 方法,发送消息给其他进程 其中请求方法会将请求放在一个 Promise 对象中,等待消息回复后,改变 Promise 状态
sendRequest<T>(method: string, args: any[]): Promise<T> {
const reply = new Deferred<T>();
this.pendingRequests.set(id, reply);
const output = this.channel.getWriteBuffer();
this.encoder.request(output, id, method, args);
output.commit();
return reply.promise;
}
sendNotification(method: string, args: any[]): void {
const output = this.channel.getWriteBuffer();
this.encoder.notification(output, this.nextMessageId++, method, args);
output.commit();
}
接收消息
listen 方法中,会创建 RpcProtocol 实例
listen(channel: Channel): void {
const connection = this.rpcConnectionFactory(channel, (meth, args) => this.onRequest(meth, ...args));
connection.onNotification(event => this.onNotification(event.method, ...event.args));
this.connectionPromiseResolve(connection);
}
该实例中通过 onMessage 监听其他进程消息,并通过 handleMessage 处理消息
this.toDispose.push(channel.onMessage(readBuffer => this.handleMessage(this.decoder.parse(readBuffer()))));
handleMessage 中,通过消息 type 类型分别处理不同的消息
handleMessage(message: RpcMessage): void {
switch (message.type) {
case RpcMessageType.Cancel: {
this.handleCancel(message.id);
break;
}
case RpcMessageType.Request: {
this.handleRequest(message.id, message.method, message.args);
break;
}
case RpcMessageType.Notification: {
this.handleNotify(message.id, message.method, message.args);
break;
}
case RpcMessageType.Reply: {
this.handleReply(message.id, message.res);
break;
}
case RpcMessageType.ReplyErr: {
this.handleReplyErr(message.id, message.err);
break;
}
}
}
通知:Notification
调用目标对象中的方法,不需要返回其他信息
protected onNotification(method: string, ...args: any[]): void {
if (this.target) {
this.target[method](...args);
}
}
请求:Request
实际上通过 onRequest 调用目标对象上的方法,并返回结果值
protected async onRequest(method: string, ...args: any[]): Promise<any> {
try {
if (this.target) {
return await this.target[method](...args);
} else {
throw new Error(`no target was set to handle ${method}`);
}
} catch (error) {
// ...
}
}
结果值通过 replyOK 方法,返回给其他进程
protected async handleRequest(id: number, method: string, args: any[]): Promise<void> {
const output = this.channel.getWriteBuffer();
try {
const result = await this.requestHandler(method, args);
this.encoder.replyOK(output, id, result);
output.commit();
} catch (err) {
// ...
}
}
replyOK(buf: WriteBuffer, requestId: number, res: any): void {
this.encode<ReplyMessage>(buf, { type: RpcMessageType.Reply, id: requestId, res });
}
返回值:replyOk
表示请求返回值,并 resovle 请求的 promise
protected handleReply(id: number, value: any): void {
const replyHandler = this.pendingRequests.get(id);
if (replyHandler) {
this.pendingRequests.delete(id);
replyHandler.resolve(value);
} else {
throw new Error(`No reply handler for reply with id: ${id}`);
}
}
消息传输
消息传输上依赖了 msgpackr 库处理消息,比常规的 JSON Stringify/parse 更高效
在 theia 1.27.0 中引入了该版本,极大的提升了应用的性能