Skip to content

Commit a929d9c

Browse files
committed
Batch stat and readdir calls
1 parent 1594fc4 commit a929d9c

File tree

3 files changed

+101
-4
lines changed

3 files changed

+101
-4
lines changed

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

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
import * as fs from "fs";
22
import { callbackify } from "util";
3-
import { ClientProxy } from "../../common/proxy";
3+
import { ClientProxy, Batch } from "../../common/proxy";
44
import { IEncodingOptions, IEncodingOptionsCallback } from "../../common/util";
55
import { FsModuleProxy, Stats as IStats, WatcherProxy, WriteStreamProxy } from "../../node/modules/fs";
66
import { Writable } from "./stream";
77

88
// tslint:disable no-any
99

10+
class StatBatch extends Batch<IStats, { path: fs.PathLike }> {
11+
public constructor(private readonly proxy: FsModuleProxy) {
12+
super();
13+
}
14+
15+
protected remoteCall(batch: { path: fs.PathLike }[]): Promise<(IStats | Error)[]> {
16+
return this.proxy.statBatch(batch);
17+
}
18+
}
19+
20+
class ReaddirBatch extends Batch<Buffer[] | fs.Dirent[] | string[], { path: fs.PathLike, options: IEncodingOptions }> {
21+
public constructor(private readonly proxy: FsModuleProxy) {
22+
super();
23+
}
24+
25+
protected remoteCall(queue: { path: fs.PathLike, options: IEncodingOptions }[]): Promise<(Buffer[] | fs.Dirent[] | string[] | Error)[]> {
26+
return this.proxy.readdirBatch(queue);
27+
}
28+
}
29+
1030
class Watcher extends ClientProxy<WatcherProxy> implements fs.FSWatcher {
1131
public close(): void {
1232
this.proxy.close();
@@ -28,7 +48,13 @@ class WriteStream extends Writable<WriteStreamProxy> implements fs.WriteStream {
2848
}
2949

3050
export class FsModule {
31-
public constructor(private readonly proxy: FsModuleProxy) {}
51+
private readonly statBatch: StatBatch;
52+
private readonly readdirBatch: ReaddirBatch;
53+
54+
public constructor(private readonly proxy: FsModuleProxy) {
55+
this.statBatch = new StatBatch(this.proxy);
56+
this.readdirBatch = new ReaddirBatch(this.proxy);
57+
}
3258

3359
public access = (path: fs.PathLike, mode: number | undefined | ((err: NodeJS.ErrnoException) => void), callback?: (err: NodeJS.ErrnoException) => void): void => {
3460
if (typeof mode === "function") {
@@ -175,7 +201,7 @@ export class FsModule {
175201
callback = options;
176202
options = undefined;
177203
}
178-
callbackify(this.proxy.readdir)(path, options, callback!);
204+
callbackify(this.readdirBatch.add)({ path, options }, callback!);
179205
}
180206

181207
public readlink = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, linkString: string | Buffer) => void): void => {
@@ -203,7 +229,7 @@ export class FsModule {
203229
}
204230

205231
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
206-
callbackify(this.proxy.stat)(path, (error, stats) => {
232+
callbackify(this.statBatch.add)({ path }, (error, stats) => {
207233
callback(error, stats && new Stats(stats));
208234
});
209235
}

packages/protocol/src/common/proxy.ts

+63
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,66 @@ export enum Module {
8181
NodePty = "node-pty",
8282
Trash = "trash",
8383
}
84+
85+
interface BatchItem<T, A> {
86+
args: A;
87+
resolve: (t: T) => void;
88+
reject: (e: Error) => void;
89+
}
90+
91+
/**
92+
* Batch remote calls.
93+
*/
94+
export abstract class Batch<T, A> {
95+
/**
96+
* Flush after reaching this count.
97+
*/
98+
private maxCount = 100;
99+
100+
/**
101+
* Flush after not receiving more requests for this amount of time.
102+
*/
103+
private maxTime = 100;
104+
105+
private flushTimeout: number | NodeJS.Timer | undefined;
106+
107+
private batch = <BatchItem<T, A>[]>[];
108+
109+
public add = (args: A): Promise<T> => {
110+
return new Promise((resolve, reject) => {
111+
this.batch.push({
112+
args,
113+
resolve,
114+
reject,
115+
});
116+
if (this.batch.length >= this.maxCount) {
117+
this.flush();
118+
} else {
119+
clearTimeout(this.flushTimeout as any);
120+
this.flushTimeout = setTimeout(this.flush, this.maxTime);
121+
}
122+
});
123+
}
124+
125+
protected abstract remoteCall(batch: A[]): Promise<(T | Error)[]>;
126+
127+
private flush = (): void => {
128+
clearTimeout(this.flushTimeout as any);
129+
130+
const batch = this.batch;
131+
this.batch = [];
132+
133+
this.remoteCall(batch.map((q) => q.args)).then((results) => {
134+
batch.forEach((item, i) => {
135+
const result = results[i];
136+
if (!result) {
137+
item.reject(new Error("no response"));
138+
} else if (result instanceof Error) {
139+
item.reject(result);
140+
} else {
141+
item.resolve(result);
142+
}
143+
});
144+
}).catch((error) => batch.forEach((item) => item.reject(error)));
145+
}
146+
}

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

+8
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ export class FsModuleProxy {
182182
return promisify(fs.readdir)(path, options);
183183
}
184184

185+
public readdirBatch(args: { path: fs.PathLike, options: IEncodingOptions }[]): Promise<(Buffer[] | fs.Dirent[] | string[] | Error)[]> {
186+
return Promise.all(args.map((args) => this.readdir(args.path, args.options).catch((e) => e)));
187+
}
188+
185189
public readlink(path: fs.PathLike, options: IEncodingOptions): Promise<string | Buffer> {
186190
return promisify(fs.readlink)(path, options);
187191
}
@@ -202,6 +206,10 @@ export class FsModuleProxy {
202206
return this.makeStatsSerializable(await promisify(fs.stat)(path));
203207
}
204208

209+
public async statBatch(args: { path: fs.PathLike }[]): Promise<(Stats | Error)[]> {
210+
return Promise.all(args.map((args) => this.stat(args.path).catch((e) => e)));
211+
}
212+
205213
public symlink(target: fs.PathLike, path: fs.PathLike, type?: fs.symlink.Type | null): Promise<void> {
206214
return promisify(fs.symlink)(target, path, type);
207215
}

0 commit comments

Comments
 (0)