Theia 进程间通信——源码解读

467 阅读2分钟

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 方法发送消息

IPCChannel.png

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 中引入了该版本,极大的提升了应用的性能

流程图

未命名文件(12).png