Skip to content

Commit acc8715

Browse files
authored
feat: use async in watch file changes (#4026)
1 parent 7a46787 commit acc8715

6 files changed

Lines changed: 88 additions & 90 deletions

File tree

packages/extension/src/browser/vscode/api/main.thread.file-system-event.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ILogger,
1010
ProgressLocation,
1111
URI,
12+
Uri,
1213
formatLocalize,
1314
localize,
1415
raceCancellation,
@@ -77,15 +78,15 @@ export class MainThreadFileSystemEvent extends Disposable {
7778
for (const change of changes) {
7879
switch (change.type) {
7980
case FileChangeType.ADDED:
80-
events.created.push(new URI(change.uri).codeUri);
81+
events.created.push(Uri.parse(change.uri));
8182
hasResult = true;
8283
break;
8384
case FileChangeType.UPDATED:
84-
events.changed.push(new URI(change.uri).codeUri);
85+
events.changed.push(Uri.parse(change.uri));
8586
hasResult = true;
8687
break;
8788
case FileChangeType.DELETED:
88-
events.deleted.push(new URI(change.uri).codeUri);
89+
events.deleted.push(Uri.parse(change.uri));
8990
hasResult = true;
9091
break;
9192
}

packages/file-service/src/node/recursive/file-service-watcher.ts

Lines changed: 66 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222

2323
import { FileChangeType, FileSystemWatcherClient, IFileSystemWatcherServer, INsfw, WatchOptions } from '../../common';
2424
import { FileChangeCollection } from '../file-change-collection';
25+
import { shouldIgnorePath } from '../shared';
2526

2627
export interface WatcherOptions {
2728
excludesPattern: ParsedPattern[];
@@ -45,7 +46,7 @@ export interface NsfwFileSystemWatcherOption {
4546
}
4647

4748
@Injectable({ multiple: true })
48-
export class FileSystemWatcherServer implements IFileSystemWatcherServer {
49+
export class FileSystemWatcherServer extends Disposable implements IFileSystemWatcherServer {
4950
private static readonly PARCEL_WATCHER_BACKEND = isWindows ? 'windows' : isLinux ? 'inotify' : 'fs-events';
5051

5152
private WATCHER_HANDLERS = new Map<
@@ -57,8 +58,6 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
5758

5859
protected client: FileSystemWatcherClient | undefined;
5960

60-
protected readonly toDispose = new DisposableCollection(Disposable.create(() => this.setClient(undefined)));
61-
6261
protected changes = new FileChangeCollection();
6362

6463
@Autowired(ILogServiceManager)
@@ -67,12 +66,14 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
6766
private logger: ILogService;
6867

6968
constructor(@Optional() private readonly excludes: string[] = []) {
69+
super();
7070
this.logger = this.loggerManager.getLogger(SupportLogNamespace.Node);
71-
}
7271

73-
dispose(): void {
74-
this.toDispose.dispose();
75-
this.WATCHER_HANDLERS.clear();
72+
this.addDispose(
73+
Disposable.create(() => {
74+
this.WATCHER_HANDLERS.clear();
75+
}),
76+
);
7677
}
7778

7879
/**
@@ -150,7 +151,7 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
150151
});
151152
toDisposeWatcher.push(Disposable.create(() => this.WATCHER_HANDLERS.delete(watcherId as number)));
152153
toDisposeWatcher.push(await this.start(watcherId, watchPath, options));
153-
this.toDispose.push(toDisposeWatcher);
154+
this.addDispose(toDisposeWatcher);
154155
return watcherId;
155156
}
156157

@@ -180,24 +181,10 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
180181
* @param events
181182
*/
182183
protected trimChangeEvent(events: ParcelWatcher.Event[]): ParcelWatcher.Event[] {
183-
events = events.filter((event: ParcelWatcher.Event) => {
184-
if (event.path) {
185-
if (this.isTempFile(event.path)) {
186-
// write-file-atomic 源文件xxx.xx 对应的临时文件为 xxx.xx.22243434
187-
// 这类文件的更新应当完全隐藏掉
188-
return false;
189-
}
190-
}
191-
return true;
192-
});
193-
184+
events = events.filter((event: ParcelWatcher.Event) => !shouldIgnorePath(event.path));
194185
return events;
195186
}
196187

197-
private isTempFile(path: string) {
198-
return /\.\d{7}\d+$/.test(path);
199-
}
200-
201188
private getDefaultWatchExclude() {
202189
return ['**/.git/objects/**', '**/.git/subtree-cache/**', '**/node_modules/**/*', '**/.hg/store/**'];
203190
}
@@ -251,20 +238,14 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
251238
return undefined; // watch 失败则返回 undefined
252239
};
253240

254-
/**
255-
* 由于 parcel/watcher 在 Linux 下存在内存越界访问问题触发了 sigsegv 导致 crash,所以在 Linux 下仍旧使用 nsfw
256-
* 社区相关 issue: https://github.com/parcel-bundler/watcher/issues/49
257-
* 后续这里的 watcher 模块需要重构掉,先暂时这样处理
258-
*
259-
* 代码来自 issue: https://github.com/opensumi/core/pull/1437/files?diff=split&w=0#diff-9de963117a88a70d7c58974bf2b092c61a196d6eef719846d78ca5c9d100b796 的旧代码处理
260-
*/
261241
if (this.isEnableNSFW()) {
262242
const nsfw = await this.withNSFWModule();
263243
const watcher: INsfw.NSFW = await nsfw(
264244
realPath,
265245
(events: INsfw.ChangeEvent[]) => this.handleNSFWEvents(events, watcherId),
266246
{
267-
errorCallback: (error: any) => {
247+
errorCallback: (err) => {
248+
this.logger.error('NSFW watcher encountered an error and will stop watching.', err);
268249
// see https://github.com/atom/github/issues/342
269250
this.unwatchFileChanges(watcherId);
270251
},
@@ -314,15 +295,15 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
314295
}
315296

316297
setClient(client: FileSystemWatcherClient | undefined) {
317-
if (client && this.toDispose.disposed) {
298+
if (client && this.disposed) {
318299
return;
319300
}
320301
this.client = client;
321302
}
322303

323304
/**
324-
* @deprecated
325-
* 主要是用来跳过 jest 测试
305+
* 由于 parcel/watcher 在 Linux 下存在内存越界访问问题触发了 sigsegv 导致 crash,所以在 Linux 下仍旧使用 nsfw
306+
* 社区相关 issue: https://github.com/parcel-bundler/watcher/issues/49
326307
*/
327308
private isEnableNSFW(): boolean {
328309
return isLinux;
@@ -347,9 +328,9 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
347328
return true;
348329
}
349330

350-
return !this.isTempFile(event.file!);
331+
return !shouldIgnorePath(event.file);
351332
});
352-
// 合并下事件,由于 resolvePath 耗时较久,这里只用当前事件路径及文件名去重,后续处理事件再获取真实路径
333+
353334
const mergedEvents = uniqBy(filterEvents, (event) => {
354335
if (event.action === INsfw.actions.RENAMED) {
355336
const deletedPath = paths.join(event.directory, event.oldFile!);
@@ -360,47 +341,58 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
360341
return event.action + paths.join(event.directory, event.file!);
361342
});
362343

363-
for (const event of mergedEvents) {
364-
if (event.action === INsfw.actions.RENAMED) {
365-
const deletedPath = this.resolvePath(event.directory, event.oldFile!);
366-
if (isIgnored(watcherId, deletedPath)) {
367-
continue;
368-
}
344+
await Promise.all(
345+
mergedEvents.map(async (event) => {
346+
switch (event.action) {
347+
case INsfw.actions.RENAMED:
348+
{
349+
const deletedPath = await this.resolvePath(event.directory, event.oldFile!);
350+
if (isIgnored(watcherId, deletedPath)) {
351+
return;
352+
}
369353

370-
this.pushDeleted(deletedPath);
354+
this.pushDeleted(deletedPath);
371355

372-
if (event.newDirectory) {
373-
const path = this.resolvePath(event.newDirectory, event.newFile!);
374-
if (isIgnored(watcherId, path)) {
375-
continue;
376-
}
356+
if (event.newDirectory) {
357+
const path = await this.resolvePath(event.newDirectory, event.newFile!);
358+
if (isIgnored(watcherId, path)) {
359+
return;
360+
}
377361

378-
this.pushAdded(path);
379-
} else {
380-
const path = this.resolvePath(event.directory, event.newFile!);
381-
if (isIgnored(watcherId, path)) {
382-
continue;
383-
}
362+
this.pushAdded(path);
363+
} else {
364+
const path = await this.resolvePath(event.directory, event.newFile!);
365+
if (isIgnored(watcherId, path)) {
366+
return;
367+
}
384368

385-
this.pushAdded(path);
386-
}
387-
} else {
388-
const path = this.resolvePath(event.directory, event.file!);
389-
if (isIgnored(watcherId, path)) {
390-
continue;
391-
}
369+
this.pushAdded(path);
370+
}
371+
}
372+
break;
373+
default:
374+
{
375+
const path = await this.resolvePath(event.directory, event.file!);
376+
if (isIgnored(watcherId, path)) {
377+
return;
378+
}
392379

393-
if (event.action === INsfw.actions.CREATED) {
394-
this.pushAdded(path);
395-
}
396-
if (event.action === INsfw.actions.DELETED) {
397-
this.pushDeleted(path);
398-
}
399-
if (event.action === INsfw.actions.MODIFIED) {
400-
this.pushUpdated(path);
380+
switch (event.action) {
381+
case INsfw.actions.CREATED:
382+
this.pushAdded(path);
383+
break;
384+
case INsfw.actions.DELETED:
385+
this.pushDeleted(path);
386+
break;
387+
case INsfw.actions.MODIFIED:
388+
this.pushUpdated(path);
389+
break;
390+
}
391+
}
392+
break;
401393
}
402-
}
403-
}
394+
}),
395+
);
404396
}
405397

406398
private async withNSFWModule(): Promise<typeof import('nsfw')> {
@@ -426,16 +418,16 @@ export class FileSystemWatcherServer implements IFileSystemWatcherServer {
426418
this.fireDidFilesChanged();
427419
}
428420

429-
protected resolvePath(directory: string, file: string): string {
421+
protected async resolvePath(directory: string, file: string): Promise<string> {
430422
const path = paths.join(directory, file);
431423
// 如果是 linux 则获取一下真实 path,以防返回的是软连路径被过滤
432424
if (isLinux) {
433425
try {
434-
return fs.realpathSync.native(path);
426+
return await fs.realpath.native(path);
435427
} catch (_e) {
436428
try {
437429
// file does not exist try to resolve directory
438-
return paths.join(fs.realpathSync.native(directory), file);
430+
return paths.join(await fs.realpath.native(directory), file);
439431
} catch (_e) {
440432
// directory does not exist fall back to symlink
441433
return path;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function shouldIgnorePath(path?: string) {
2+
if (!path) {
3+
return true;
4+
}
5+
6+
if (/\.\d{7}\d+$/.test(path)) {
7+
// write-file-atomic 源文件xxx.xx 对应的临时文件为 xxx.xx.22243434
8+
// 这类文件的更新应当完全隐藏掉
9+
return true;
10+
}
11+
12+
return false;
13+
}

packages/file-service/src/node/un-recursive/file-service-watcher.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616

1717
import { FileChangeType, FileSystemWatcherClient, IFileSystemWatcherServer } from '../../common/index';
1818
import { FileChangeCollection } from '../file-change-collection';
19+
import { shouldIgnorePath } from '../shared';
1920
const { join, basename, normalize } = path;
2021
@Injectable({ multiple: true })
2122
export class UnRecursiveFileSystemWatcher implements IFileSystemWatcherServer {
@@ -96,7 +97,7 @@ export class UnRecursiveFileSystemWatcher implements IFileSystemWatcherServer {
9697
});
9798

9899
watcher.on('change', (type: string, filename: string | Buffer) => {
99-
if (this.isTemporaryFile(filename as string)) {
100+
if (shouldIgnorePath(filename as string)) {
100101
return;
101102
}
102103

@@ -248,15 +249,4 @@ export class UnRecursiveFileSystemWatcher implements IFileSystemWatcherServer {
248249
}
249250
this.client = client;
250251
}
251-
252-
protected isTemporaryFile(path: string): boolean {
253-
if (path) {
254-
if (/\.\d{7}\d+$/.test(path)) {
255-
// write-file-atomic 源文件xxx.xx 对应的临时文件为 xxx.xx.22243434
256-
// 这类文件的更新应当完全隐藏掉
257-
return true;
258-
}
259-
}
260-
return false;
261-
}
262252
}

packages/utils/src/async.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export type MaybePromise<T> = T | Promise<T> | PromiseLike<T>;
88
export const FRAME_ONE = 16;
99
export const FRAME_TWO = FRAME_ONE * 2;
1010
export const FRAME_THREE = FRAME_ONE * 3;
11+
export const FRAME_FOUR = FRAME_ONE * 4;
12+
export const FRAME_FIVE = FRAME_ONE * 5;
1113

1214
export interface CancelablePromise<T> extends Promise<T> {
1315
cancel(): void;

tools/playwright/src/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class OpenSumiApp extends Disposable {
6565
protected async load(workspace: OpenSumiWorkspace): Promise<void> {
6666
this.disposables.push(workspace);
6767
const now = Date.now();
68-
await this.loadOrReload(this.page, `/#${workspace.workspace.codeUri.fsPath}`);
68+
await this.loadOrReload(this.page, `/?workspaceDir=${workspace.workspace.codeUri.fsPath}`);
6969
await this.page.waitForSelector(this.appData.loadingSelector, { state: 'detached' });
7070
const time = Date.now() - now;
7171
// eslint-disable-next-line no-console

0 commit comments

Comments
 (0)