Skip to content

Commit

Permalink
Show message if insufficient file watcher handles
Browse files Browse the repository at this point in the history
Signed-off-by: Nigel Westbury <[email protected]>
  • Loading branch information
westbury committed Sep 8, 2020
1 parent a787284 commit 7ea3cdf
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- This change triggers the context menu for a given shell tab-bar without the need to activate it
- While registering a command, `Event` should be passed down, if not passed, then the commands will not work correctly as they no longer rely on the activation of tab-bar
- [core] Moved `findTitle()` and `findTabBar()` from `common-frontend-contribution.ts` to `application-shell.ts` [#6965](https://github.com/eclipse-theia/theia/pull/6965)

- [filesystem] show Linux users a warning when Inotify handles have been exhausted, with link to instructions on how to fix [#8458](https://github.com/eclipse-theia/theia/pull/8458)

## v1.5.0 - 27/08/2020

Expand Down
8 changes: 8 additions & 0 deletions packages/filesystem/src/browser/file-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { UTF8, UTF8_with_bom } from '@theia/core/lib/common/encodings';
import { EncodingService, ResourceEncoding, DecodeStreamResult } from '@theia/core/lib/common/encoding-service';
import { Mutable } from '@theia/core/lib/common/types';
import { readFileIntoStream } from '../common/io';
import { FileSystemWatcherErrorHandler } from './filesystem-watcher-error-handler';

export interface FileOperationParticipant {

Expand Down Expand Up @@ -247,6 +248,9 @@ export class FileService {
@inject(ContributionProvider) @named(FileServiceContribution)
protected readonly contributions: ContributionProvider<FileServiceContribution>;

@inject(FileSystemWatcherErrorHandler)
protected readonly watcherErrorHandler: FileSystemWatcherErrorHandler;

@postConstruct()
protected init(): void {
for (const contribution of this.contributions.getContributions()) {
Expand Down Expand Up @@ -308,6 +312,7 @@ export class FileService {

const providerDisposables = new DisposableCollection();
providerDisposables.push(provider.onDidChangeFile(changes => this.onDidFilesChangeEmitter.fire(new FileChangesEvent(changes))));
providerDisposables.push(provider.onFileWatchError(() => this.handleFileWatchError()));
providerDisposables.push(provider.onDidChangeCapabilities(() => this.onDidChangeFileSystemProviderCapabilitiesEmitter.fire({ provider, scheme })));

return Disposable.create(() => {
Expand Down Expand Up @@ -1675,4 +1680,7 @@ export class FileService {

// #endregion

protected handleFileWatchError(): void {
this.watcherErrorHandler.handleError();
}
}
3 changes: 3 additions & 0 deletions packages/filesystem/src/browser/filesystem-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
import { RemoteFileServiceContribution } from './remote-file-service-contribution';
import { FileSystemWatcherErrorHandler } from './filesystem-watcher-error-handler';
import { UTF8 } from '@theia/core/lib/common/encodings';

export default new ContainerModule(bind => {
Expand All @@ -50,6 +51,8 @@ export default new ContainerModule(bind => {
bind(FileServiceContribution).toService(RemoteFileServiceContribution);

bind(FileSystemWatcher).toSelf().inSingletonScope();
bind(FileSystemWatcherErrorHandler).toSelf().inSingletonScope();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
bind(FileSystem).toDynamicValue(({ container }) => {
const fileService = container.get(FileService);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/********************************************************************************
* Copyright (C) 2020 Arm and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject } from 'inversify';
import { environment } from '@theia/application-package/lib/environment';
import { MessageService } from '@theia/core';
import { WindowService } from '@theia/core/lib/browser/window/window-service';

@injectable()
export class FileSystemWatcherErrorHandler {

@inject(MessageService) protected readonly messageService: MessageService;
@inject(WindowService) protected readonly windowService: WindowService;

protected watchHandlesExhausted: boolean = false;

protected get instructionsLink(): string {
return 'https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc';
}

public async handleError(): Promise<void> {
if (!this.watchHandlesExhausted) {
this.watchHandlesExhausted = true;
if (this.isElectron()) {
const instructionsAction = 'Instructions';
const action = await this.messageService.warn(
'Unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue.',
{ timeout: 60000 },
instructionsAction
);
if (action === instructionsAction) {
this.windowService.openNewWindow(this.instructionsLink, { external: true });
}
} else {
await this.messageService.warn(
'Unable to watch for file changes in this large workspace. The information you see may not include recent file changes.',
{ timeout: 60000 }
);
}
}
}

protected isElectron(): boolean {
return environment.electron.is();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ export class DelegatingFileSystemProvider implements Required<FileSystemProvider
private readonly onDidChangeFileEmitter = new Emitter<readonly FileChange[]>();
readonly onDidChangeFile = this.onDidChangeFileEmitter.event;

private readonly onFileWatchErrorEmitter = new Emitter<void>();
readonly onFileWatchError = this.onFileWatchErrorEmitter.event;

constructor(
protected readonly delegate: FileSystemProvider,
protected readonly options: DelegatingFileSystemProvider.Options,
protected readonly toDispose = new DisposableCollection()
) {
this.toDispose.push(this.onDidChangeFileEmitter);
this.toDispose.push(delegate.onDidChangeFile(changes => this.handleFileChanges(changes)));
this.toDispose.push(this.onFileWatchErrorEmitter);
this.toDispose.push(delegate.onFileWatchError(changes => this.onFileWatchErrorEmitter.fire()));
}

dispose(): void {
Expand Down
1 change: 1 addition & 0 deletions packages/filesystem/src/common/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,7 @@ export interface FileSystemProvider {
readonly onDidChangeCapabilities: Event<void>;

readonly onDidChangeFile: Event<readonly FileChange[]>;
readonly onFileWatchError: Event<void>;
watch(resource: URI, opts: WatchOptions): IDisposable;

stat(resource: URI): Promise<Stat>;
Expand Down
5 changes: 5 additions & 0 deletions packages/filesystem/src/common/filesystem-watcher-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export interface FileSystemWatcherClient {
* Notify when files under watched uris are changed.
*/
onDidFilesChanged(event: DidFilesChangedParams): void;

/**
* Notify when unable to watch files because of Linux handle limit.
*/
onError(): void;
}

export interface WatchOptions {
Expand Down
12 changes: 12 additions & 0 deletions packages/filesystem/src/common/remote-file-system-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface RemoteFileStreamError extends Error {

export interface RemoteFileSystemClient {
notifyDidChangeFile(event: { changes: RemoteFileChange[] }): void;
notifyFileWatchError(): void;
notifyDidChangeCapabilities(capabilities: FileSystemProviderCapabilities): void;
onFileStreamData(handle: number, data: number[]): void;
onFileStreamEnd(handle: number, error: RemoteFileStreamError | undefined): void;
Expand Down Expand Up @@ -108,6 +109,9 @@ export class RemoteFileSystemProvider implements Required<FileSystemProvider>, D
private readonly onDidChangeFileEmitter = new Emitter<readonly FileChange[]>();
readonly onDidChangeFile = this.onDidChangeFileEmitter.event;

private readonly onFileWatchErrorEmitter = new Emitter<void>();
readonly onFileWatchError = this.onFileWatchErrorEmitter.event;

private readonly onDidChangeCapabilitiesEmitter = new Emitter<void>();
readonly onDidChangeCapabilities = this.onDidChangeCapabilitiesEmitter.event;

Expand Down Expand Up @@ -151,6 +155,9 @@ export class RemoteFileSystemProvider implements Required<FileSystemProvider>, D
notifyDidChangeFile: ({ changes }) => {
this.onDidChangeFileEmitter.fire(changes.map(event => ({ resource: new URI(event.resource), type: event.type })));
},
notifyFileWatchError: () => {
this.onFileWatchErrorEmitter.fire();
},
notifyDidChangeCapabilities: capabilities => this.setCapabilities(capabilities),
onFileStreamData: (handle, data) => this.onFileStreamDataEmitter.fire([handle, Uint8Array.from(data)]),
onFileStreamEnd: (handle, error) => this.onFileStreamEndEmitter.fire([handle, error])
Expand Down Expand Up @@ -338,6 +345,11 @@ export class FileSystemProviderServer implements RemoteFileSystemServer {
});
}
}));
this.toDispose.push(this.provider.onFileWatchError(() => {
if (this.client) {
this.client.notifyFileWatchError();
}
}));
}

async getCapabilities(): Promise<FileSystemProviderCapabilities> {
Expand Down
6 changes: 5 additions & 1 deletion packages/filesystem/src/node/disk-file-system-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export class DiskFileSystemProvider implements Disposable,
private readonly onDidChangeFileEmitter = new Emitter<readonly FileChange[]>();
readonly onDidChangeFile = this.onDidChangeFileEmitter.event;

private readonly onFileWatchErrorEmitter = new Emitter<void>();
readonly onFileWatchError = this.onFileWatchErrorEmitter.event;

protected readonly toDispose = new DisposableCollection(
this.onDidChangeFileEmitter
);
Expand All @@ -112,7 +115,8 @@ export class DiskFileSystemProvider implements Disposable,
onDidFilesChanged: params => this.onDidChangeFileEmitter.fire(params.changes.map(({ uri, type }) => ({
resource: new URI(uri),
type
})))
}))),
onError: () => this.onFileWatchErrorEmitter.fire()
});
}

Expand Down
5 changes: 5 additions & 0 deletions packages/filesystem/src/node/filesystem-watcher-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class FileSystemWatcherServerClient implements FileSystemWatcherServer {
if (this.client) {
this.client.onDidFilesChanged(e);
}
},
onError: () => {
if (this.client) {
this.client.onError();
}
}
});
this.toDispose.push(this.remote);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ describe('nsfw-filesystem-watcher', function (): void {
const watcherClient = {
onDidFilesChanged(event: DidFilesChangedParams): void {
event.changes.forEach(c => actualUris.add(c.uri.toString()));
},
onError(): void {
}
};
watcherServer.setClient(watcherClient);
Expand Down Expand Up @@ -92,6 +94,8 @@ describe('nsfw-filesystem-watcher', function (): void {
const watcherClient = {
onDidFilesChanged(event: DidFilesChangedParams): void {
event.changes.forEach(c => actualUris.add(c.uri.toString()));
},
onError(): void {
}
};
watcherServer.setClient(watcherClient);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ export class NsfwFileSystemWatcherServer implements FileSystemWatcherServer {
errorCallback: error => {
// see https://github.com/atom/github/issues/342
console.warn(`Failed to watch "${basePath}":`, error);
if (error === 'Inotify limit reached') {
if (this.client) {
this.client.onError();
}
}
this.unwatchFileChanges(watcherId);
},
...this.options.nsfwOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class RemoteFileSystemProvider implements FileSystemProviderWithFileReadWriteCap
private readonly _registration: IDisposable;

readonly onDidChangeFile: Event<readonly FileChange[]> = this._onDidChange.event;
readonly onFileWatchError: Event<void> = new Emitter<void>().event; // dummy, never fired

readonly capabilities: FileSystemProviderCapabilities;
readonly onDidChangeCapabilities: Event<void> = Event.None;
Expand Down

0 comments on commit 7ea3cdf

Please sign in to comment.