Skip to content

Commit 5925ab1

Browse files
author
Kartik Raj
committed
Add disposable registry
1 parent 3ef70e3 commit 5925ab1

File tree

7 files changed

+80
-59
lines changed

7 files changed

+80
-59
lines changed

src/client/common/platform/fileSystemWatcher.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
import * as chokidar from 'chokidar';
77
import * as path from 'path';
8-
import { Disposable, RelativePattern, workspace } from 'vscode';
8+
import { RelativePattern, workspace } from 'vscode';
99
import { traceError, traceVerbose, traceWarning } from '../logger';
10+
import { DisposableRegistry } from '../syncDisposableRegistry';
11+
import { IDisposable } from '../types';
1012
import { normCasePath } from './fs-paths';
1113

1214
/**
@@ -23,7 +25,7 @@ export function watchLocationForPattern(
2325
baseDir: string,
2426
pattern: string,
2527
callback: (type: FileChangeType, absPath: string) => void
26-
): Disposable {
28+
): IDisposable {
2729
// Use VSCode API iff base directory to exists within the current workspace folders
2830
const found = workspace.workspaceFolders?.find((e) => normCasePath(baseDir).startsWith(normCasePath(e.uri.fsPath)));
2931
if (found) {
@@ -37,26 +39,22 @@ function watchLocationUsingVSCodeAPI(
3739
baseDir: string,
3840
pattern: string,
3941
callback: (type: FileChangeType, absPath: string) => void
40-
) {
42+
): IDisposable {
4143
const globPattern = new RelativePattern(baseDir, pattern);
42-
const disposables: Disposable[] = [];
44+
const disposables = new DisposableRegistry();
4345
traceVerbose(`Start watching: ${baseDir} with pattern ${pattern} using VSCode API`);
4446
const watcher = workspace.createFileSystemWatcher(globPattern);
4547
disposables.push(watcher.onDidCreate((e) => callback(FileChangeType.Created, e.fsPath)));
4648
disposables.push(watcher.onDidChange((e) => callback(FileChangeType.Changed, e.fsPath)));
4749
disposables.push(watcher.onDidDelete((e) => callback(FileChangeType.Deleted, e.fsPath)));
48-
return {
49-
dispose: async () => {
50-
disposables.forEach((d) => d.dispose());
51-
}
52-
};
50+
return disposables;
5351
}
5452

5553
function watchLocationUsingChokidar(
5654
baseDir: string,
5755
pattern: string,
5856
callback: (type: FileChangeType, absPath: string) => void
59-
) {
57+
): IDisposable {
6058
const watcherOpts: chokidar.WatchOptions = {
6159
cwd: baseDir,
6260
ignoreInitial: true,
@@ -103,10 +101,15 @@ function watchLocationUsingChokidar(
103101
callback(eventType, absPath);
104102
});
105103

106-
const dispose = async () => {
104+
const stopWatcher = async () => {
107105
if (watcher) {
108-
await watcher.close();
106+
const obj = watcher;
109107
watcher = null;
108+
try {
109+
await obj.close();
110+
} catch (err) {
111+
traceError(`Failed to close FS watcher (${err})`);
112+
}
110113
}
111114
};
112115

@@ -119,12 +122,12 @@ function watchLocationUsingChokidar(
119122
// See https://github.com/Microsoft/vscode/issues/7950
120123
if (error.code === 'ENOSPC') {
121124
traceError(`Inotify limit reached (ENOSPC) for ${baseDir} with pattern ${pattern}`);
122-
await dispose();
125+
await stopWatcher();
123126
} else {
124127
traceWarning(error.toString());
125128
}
126129
}
127130
});
128131

129-
return { dispose };
132+
return { dispose: () => stopWatcher().ignoreErrors() };
130133
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { traceWarning } from './logger';
7+
import { IDisposable } from './types';
8+
9+
export class DisposableRegistry implements IDisposable {
10+
private _list: IDisposable[] = [];
11+
12+
public dispose(): void {
13+
this._list.forEach((l, index) => {
14+
try {
15+
l.dispose();
16+
} catch (err) {
17+
traceWarning(`dispose() #${index} failed (${err})`);
18+
}
19+
});
20+
this._list = [];
21+
}
22+
23+
public push(disposable?: IDisposable): void {
24+
if (disposable) {
25+
this._list.push(disposable);
26+
}
27+
}
28+
29+
public get list(): IDisposable[] {
30+
return this._list;
31+
}
32+
}

src/client/pythonEnvironments/base/locator.ts

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
// eslint-disable-next-line max-classes-per-file
55
import { Event, Uri } from 'vscode';
6-
import { AsyncDisposableRegistry } from '../../common/asyncDisposableRegistry';
7-
import { IAsyncDisposableRegistry, IDisposable } from '../../common/types';
6+
import { DisposableRegistry } from '../../common/syncDisposableRegistry';
7+
import { IDisposable } from '../../common/types';
88
import { iterEmpty } from '../../common/utils/async';
99
import { PythonEnvInfo, PythonEnvKind } from './info';
1010
import {
@@ -170,28 +170,32 @@ interface IEmitter<E extends BasicPythonEnvsChangedEvent> {
170170
}
171171

172172
/**
173-
* The generic base for Python envs locators.
173+
* The base for most Python envs locators.
174174
*
175175
* By default `resolveEnv()` returns undefined. Subclasses may override
176176
* the method to provide an implementation.
177177
*
178-
* Subclasses will call `this.emitter.fire()` to emit events.
178+
* Subclasses will call `this.emitter.fire()` * to emit events.
179+
*
180+
* In most cases this is the class you will want to subclass.
181+
* Only in low-level cases should you consider subclassing `LocatorBase`
182+
* using `BasicPythonEnvsChangedEvent.
179183
*
180184
* Also, in most cases the default event type (`PythonEnvsChangedEvent`)
181185
* should be used. Only in low-level cases should you consider using
182186
* `BasicPythonEnvsChangedEvent`.
183187
*/
184-
export abstract class LocatorBase<E extends BasicPythonEnvsChangedEvent = PythonEnvsChangedEvent>
188+
export abstract class Locator<E extends BasicPythonEnvsChangedEvent = PythonEnvsChangedEvent>
185189
implements IDisposable, ILocator<E> {
186190
public readonly onChanged: Event<E>;
187191

188-
protected readonly emitter: IEmitter<E>;
192+
protected readonly emitter: IPythonEnvsWatcher<E> & IEmitter<E>;
189193

190-
protected readonly disposables: IAsyncDisposableRegistry = new AsyncDisposableRegistry();
194+
protected readonly disposables = new DisposableRegistry();
191195

192-
constructor(watcher: IPythonEnvsWatcher<E> & IEmitter<E>) {
193-
this.emitter = watcher;
194-
this.onChanged = watcher.onChanged;
196+
constructor() {
197+
this.emitter = new PythonEnvsWatcher();
198+
this.onChanged = this.emitter.onChanged;
195199
}
196200

197201
public abstract iterEnvs(query?: QueryForEvent<E>): IPythonEnvsIterator;
@@ -201,24 +205,6 @@ implements IDisposable, ILocator<E> {
201205
}
202206

203207
public dispose(): void {
204-
this.disposables.dispose().ignoreErrors();
205-
}
206-
}
207-
208-
/**
209-
* The base for most Python envs locators.
210-
*
211-
* By default `resolveEnv()` returns undefined. Subclasses may override
212-
* the method to provide an implementation.
213-
*
214-
* Subclasses will call `this.emitter.fire()` * to emit events.
215-
*
216-
* In most cases this is the class you will want to subclass.
217-
* Only in low-level cases should you consider subclassing `LocatorBase`
218-
* using `BasicPythonEnvsChangedEvent.
219-
*/
220-
export abstract class Locator extends LocatorBase {
221-
constructor() {
222-
super(new PythonEnvsWatcher());
208+
this.disposables.dispose();
223209
}
224210
}

src/client/pythonEnvironments/common/pythonBinariesWatcher.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,31 @@
55

66
import * as minimatch from 'minimatch';
77
import * as path from 'path';
8-
import { Disposable } from 'vscode';
98
import { FileChangeType, watchLocationForPattern } from '../../common/platform/fileSystemWatcher';
9+
import { DisposableRegistry } from '../../common/syncDisposableRegistry';
10+
import { IDisposable } from '../../common/types';
1011
import { getOSType, OSType } from '../../common/utils/platform';
1112

1213
const [executable, binName] = getOSType() === OSType.Windows ? ['python.exe', 'Scripts'] : ['python', 'bin'];
1314

1415
/**
1516
* @param baseDir The base directory from which watch paths are to be derived.
1617
* @param callback The listener function will be called when the event happens.
17-
* @param executableSuffixGlob Glob which represents suffix of the full executable file path to watch.
18+
* @param executableBaseGlob Glob which represents basename of the executable to watch.
1819
*/
1920
export function watchLocationForPythonBinaries(
2021
baseDir: string,
2122
callback: (type: FileChangeType, absPath: string) => void,
22-
executableSuffixGlob: string = executable,
23-
): Disposable {
24-
const patterns = [executableSuffixGlob, `*/${executableSuffixGlob}`, `*/${binName}/${executableSuffixGlob}`];
25-
const disposables: Disposable[] = [];
23+
executableBaseGlob: string = executable,
24+
): IDisposable {
25+
if (executableBaseGlob.includes(path.sep)) {
26+
throw new Error('Glob basename contains invalid characters');
27+
}
28+
const patterns = [executableBaseGlob, `*/${executableBaseGlob}`, `*/${binName}/${executableBaseGlob}`];
29+
const disposables = new DisposableRegistry();
2630
for (const pattern of patterns) {
2731
disposables.push(watchLocationForPattern(baseDir, pattern, (type: FileChangeType, e: string) => {
28-
const isMatch = minimatch(e, path.join('**', executableSuffixGlob), { nocase: getOSType() === OSType.Windows });
32+
const isMatch = minimatch(e, path.join('**', executableBaseGlob), { nocase: getOSType() === OSType.Windows });
2933
if (!isMatch) {
3034
// When deleting the file for some reason path to all directories leading up to python are reported
3135
// Skip those events
@@ -34,9 +38,5 @@ export function watchLocationForPythonBinaries(
3438
callback(type, e);
3539
}));
3640
}
37-
return {
38-
dispose: async () => {
39-
disposables.forEach((d) => d.dispose());
40-
},
41-
};
41+
return disposables;
4242
}

src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvronmentLocator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ import { traceVerbose } from '../../../../common/logger';
77
import { FileChangeType } from '../../../../common/platform/fileSystemWatcher';
88
import { chain, iterable, sleep } from '../../../../common/utils/async';
99
import {
10-
getEnvironmentVariable, getOSType, getUserHomeDir, OSType,
10+
getEnvironmentVariable, getOSType, getUserHomeDir, OSType
1111
} from '../../../../common/utils/platform';
1212
import { PythonEnvInfo, PythonEnvKind, UNKNOWN_PYTHON_VERSION } from '../../../base/info';
1313
import { buildEnvInfo } from '../../../base/info/env';
1414
import { IPythonEnvsIterator } from '../../../base/locator';
15-
import { FSWatchingLocator } from '../../../base/locators/lowLevel/locator';
15+
import { FSWatchingLocator } from '../../../base/locators/lowLevel/fsWatchingLocator';
1616
import { findInterpretersInDir } from '../../../common/commonUtils';
1717
import { getFileInfo, pathExists } from '../../../common/externalDependencies';
1818
import { watchLocationForPythonBinaries } from '../../../common/pythonBinariesWatcher';
1919
import { isPipenvEnvironment } from './pipEnvHelper';
2020
import {
2121
isVenvEnvironment,
2222
isVirtualenvEnvironment,
23-
isVirtualenvwrapperEnvironment,
23+
isVirtualenvwrapperEnvironment
2424
} from './virtualEnvironmentIdentifier';
2525

2626
const DEFAULT_SEARCH_DEPTH = 2;

src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { PythonEnvInfo, PythonEnvKind } from '../../../base/info';
1111
import { buildEnvInfo } from '../../../base/info/env';
1212
import { getPythonVersionFromPath } from '../../../base/info/pythonVersion';
1313
import { IPythonEnvsIterator } from '../../../base/locator';
14-
import { FSWatchingLocator } from '../../../base/locators/lowLevel/locator';
14+
import { FSWatchingLocator } from '../../../base/locators/lowLevel/fsWatchingLocator';
1515
import { getFileInfo } from '../../../common/externalDependencies';
1616
import { watchLocationForPythonBinaries } from '../../../common/pythonBinariesWatcher';
1717

0 commit comments

Comments
 (0)