Skip to content

Commit 7a7f040

Browse files
committed
perf: JSON-RPC: faster eventLoop: request buffer
Before this commit we would not start another `getNextEvent()` before we have received a response for the previous one. This commit makes a pool of `getNextEvent()` requests so that the server always has a request that it can readily respond to. Measurements I have tested this on Delta Chat desktop. This seems to provide a measurable speedup in handling event bursts. I did `console.time()` when we start a `BackendRemote.rpc.maybeNetwork()` request (which we do every time the main window is focused), and a `console.timeEnd()` when we receive the first "IDLE entering wait-on-remote state" entry, inside of [`ipcBackend.on('json-rpc-message'`](https://github.com/deltachat/deltachat-desktop/blob/3846aef67c1644d8ec8b57b229329855a378ee4e/packages/target-electron/runtime-electron/runtime.ts#L52). For these measurements I also disabled request-response logging with `config['log-debug'] = false`. Each such `maybeNetwork()` resulted in ~1000 `getNextEvent()` responses. With the original event loop (without a pool) the average time based on 150 measurements was 1152.28 ms. With the new event loop with a pool of 20 based on 150 measurements it was 774.58 ms. That is 67.22% of the original average duration. Related: - deltachat/deltachat-desktop#5282
1 parent 50a7366 commit 7a7f040

1 file changed

Lines changed: 34 additions & 3 deletions

File tree

deltachat-jsonrpc/typescript/src/client.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,51 @@ export class BaseDeltaChat<
4040
* and emitting the respective events on this class.
4141
*/
4242
startEventLoop: boolean,
43+
options?: {
44+
/**
45+
* @see {@linkcode BaseDeltaChat.eventLoop}.
46+
*
47+
* Has no effect if {@linkcode startEventLoop} === false.
48+
*/
49+
eventLoopRequestPoolSize?: number;
50+
},
4351
) {
4452
super();
4553
this.rpc = new RawClient(this.transport);
4654
if (startEventLoop) {
47-
this.eventTask = this.eventLoop();
55+
this.eventTask = this.eventLoop({
56+
eventLoopRequestPoolSize: options?.eventLoopRequestPoolSize,
57+
});
4858
}
4959
}
5060

5161
/**
5262
* @see the constructor's `startEventLoop`
5363
*/
54-
async eventLoop(): Promise<void> {
64+
async eventLoop(options?: {
65+
/**
66+
* How many {@linkcode RawClient.getNextEvent} to constantly keep open.
67+
* Having a value > 1 improves performance
68+
* when dealing with bursts of events.
69+
*
70+
* Must be >= 1.
71+
*
72+
* @default 20
73+
*/
74+
eventLoopRequestPoolSize?: number;
75+
}): Promise<void> {
76+
const promises: ReturnType<typeof this.rpc.getNextEvent>[] = [];
77+
for (let i = 0; i < (options?.eventLoopRequestPoolSize ?? 20); i++) {
78+
promises.push(this.rpc.getNextEvent());
79+
}
80+
const bufferLength = promises.length;
81+
let currInd = 0;
82+
5583
while (true) {
56-
const event = await this.rpc.getNextEvent();
84+
const event = await promises[currInd];
85+
promises[currInd] = this.rpc.getNextEvent();
86+
currInd = (currInd + 1) % bufferLength;
87+
5788
//@ts-ignore
5889
this.emit(event.event.kind, event.contextId, event.event);
5990
this.emit("ALL", event.contextId, event.event);

0 commit comments

Comments
 (0)