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

Fix fs.watch leak with a timestamp to prevent repetitive buildFromConfig #7238

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Changes from 2 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
42 changes: 32 additions & 10 deletions pkg/rancher-desktop/main/tray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ export class Tray {
private settings: Settings;
private currentNetworkStatus: networkStatus = networkStatus.CHECKING;
private static instance: Tray;
private abortController: AbortController | undefined;
private networkState: boolean | undefined;
private networkInterval: NodeJS.Timeout;
private runBuildFromConfigTimer: NodeJS.Timeout | null = null;
private kubeConfigWatchers: fs.FSWatcher[] = [];
private fsWatcherInterval: NodeJS.Timeout;

protected contextMenuItems: Electron.MenuItemConstructorOptions[] = [
{
Expand Down Expand Up @@ -130,18 +132,19 @@ export class Tray {
* triggered, close the watcher and restart after a duration (one second).
*/
private async watchForChanges() {
const abortController = new AbortController();
for (const watcher of this.kubeConfigWatchers) {
watcher.close();
}
this.kubeConfigWatchers = [];
mikeseese marked this conversation as resolved.
Show resolved Hide resolved

this.abortController = abortController;
const paths = await kubeconfig.getKubeConfigPaths();
const options: fs.WatchOptions = {
persistent: false,
recursive: !this.isLinux(), // Recursive not implemented in Linux
encoding: 'utf-8',
signal: abortController.signal,
};

paths.map(filepath => fs.watch(filepath, options, async(eventType) => {
this.kubeConfigWatchers = paths.map(filepath => fs.watch(filepath, options, async(eventType) => {
if (eventType === 'rename') {
try {
await fs.promises.access(filepath);
Expand All @@ -151,10 +154,14 @@ export class Tray {
}
}

this.abortController?.abort();
this.buildFromConfig();

setTimeout(this.watchForChanges.bind(this), 1_000);
if (this.runBuildFromConfigTimer === null) {
// This prevents calling buildFromConfig multiple times in quick succession
// while making sure that the last file change within the period is processed.
this.runBuildFromConfigTimer = setTimeout(() => {
this.runBuildFromConfigTimer = null;
this.buildFromConfig();
}, 1_000);
}
mikeseese marked this conversation as resolved.
Show resolved Hide resolved
}));
}

Expand Down Expand Up @@ -183,6 +190,14 @@ export class Tray {
this.buildFromConfig();
this.watchForChanges();

/**
mikeseese marked this conversation as resolved.
Show resolved Hide resolved
* We reset the watchers on an interval in the event that `fs.watch` silently
* fails to keep watching. This original issue is documented at
* https://github.com/rancher-sandbox/rancher-desktop/pull/2038 and further discussed at
* https://github.com/rancher-sandbox/rancher-desktop/pull/7238#discussion_r1690128729
*/
this.fsWatcherInterval = setInterval(() => this.watchForChanges(), 5 * 60_000);

mainEvents.on('backend-locked-update', this.backendStateEvent);
mainEvents.emit('backend-locked-check');
mainEvents.on('k8s-check-state', this.k8sStateChangedEvent);
Expand Down Expand Up @@ -242,11 +257,18 @@ export class Tray {
*/
public hide() {
this.trayMenu.destroy();
this.abortController?.abort();
mainEvents.off('k8s-check-state', this.k8sStateChangedEvent);
mainEvents.off('settings-update', this.settingsUpdateEvent);
ipcMainProxy.removeListener('update-network-status', this.updateNetworkStatusEvent);
clearInterval(this.fsWatcherInterval);
clearInterval(this.networkInterval);
if (this.runBuildFromConfigTimer) {
clearTimeout(this.runBuildFromConfigTimer);
mikeseese marked this conversation as resolved.
Show resolved Hide resolved
}
for (const watcher of this.kubeConfigWatchers) {
watcher.close();
}
this.kubeConfigWatchers = [];
mikeseese marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
Loading