fix: app crash on clicking close button (#7637)

* fix: app crash on clicking close button \n Added collection, workspace, and api spec watcher cleanup on app close method

* fix: close file watchers before app exit to prevent crash on macOS

Close all chokidar file watchers (collection, workspace, apiSpec) before
the Node environment is torn down. The native FSEvents watchers run on
their own threads and their cleanup races with FreeEnvironment, causing
an abort when fse_instance_destroy tries to lock a destroyed mutex.

Watchers are closed in both mainWindow.on('close') and app.on('before-quit')
to cover the native close button path and the app.exit() path.

* fix: move watcher cleanup from close handler to before-quit only

The close event is cancelable — if the user cancels the unsaved changes
dialog, watchers would remain closed for the rest of the session.
Move closeAllWatchers() to before-quit which only fires on actual quit.

---------

Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com>
This commit is contained in:
Chirag Chandrashekhar
2026-04-01 10:54:50 +05:30
committed by GitHub
parent 4a78f637d3
commit 8338f91487
4 changed files with 44 additions and 0 deletions

View File

@@ -141,6 +141,16 @@ class ApiSpecWatcher {
delete this.watcherWorkspaces[watchPath];
}
}
closeAllWatchers() {
for (const [watchPath, watcher] of Object.entries(this.watchers)) {
try {
watcher?.close();
} catch (err) {}
}
this.watchers = {};
this.watcherWorkspaces = {};
}
}
module.exports = ApiSpecWatcher;

View File

@@ -958,6 +958,15 @@ class CollectionWatcher {
.filter(([path, watcher]) => !!watcher)
.map(([path, _watcher]) => path);
}
closeAllWatchers() {
for (const [watchPath, watcher] of Object.entries(this.watchers)) {
try {
watcher?.close();
} catch (err) {}
}
this.watchers = {};
}
}
const collectionWatcher = new CollectionWatcher();

View File

@@ -224,6 +224,24 @@ class WorkspaceWatcher {
hasWatcher(workspacePath) {
return Boolean(this.watchers[workspacePath]);
}
closeAllWatchers() {
for (const [watchPath, watcher] of Object.entries(this.watchers)) {
try {
watcher?.close();
} catch (err) {}
}
this.watchers = {};
for (const [watchPath, watcher] of Object.entries(this.environmentWatchers)) {
try {
watcher?.close();
} catch (err) {}
}
this.environmentWatchers = {};
dotEnvWatcher.closeAll();
}
}
module.exports = WorkspaceWatcher;

View File

@@ -122,6 +122,12 @@ const focusMainWindow = () => {
}
};
const closeAllWatchers = () => {
collectionWatcher.closeAllWatchers();
workspaceWatcher.closeAllWatchers();
apiSpecWatcher.closeAllWatchers();
};
// Parse protocol URL from command line arguments (if any)
appProtocolUrl = getAppProtocolUrlFromArgv(process.argv);
@@ -459,6 +465,7 @@ app.on('ready', async () => {
// Quit the app once all windows are closed
app.on('before-quit', () => {
closeAllWatchers();
// Release single instance lock to allow other instances to take over
if (useSingleInstance && gotTheLock) {
app.releaseSingleInstanceLock();