Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show message if insufficient file watcher handles #8458

Merged
merged 1 commit into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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