-
Notifications
You must be signed in to change notification settings - Fork 945
fix(watcher): use Watchman when available and reduce FSEvents usage #10115
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
base: master
Are you sure you want to change the base?
Changes from 4 commits
67230b2
e95c668
23ad66c
a5fadac
a32c71c
3689733
d603845
93175e4
75a2864
0ed6a80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,8 +26,9 @@ import type { CheckTypes } from './check-types'; | |||||||||||||||||||||
| import type { WatcherMain } from './watcher.main.runtime'; | ||||||||||||||||||||||
| import { WatchQueue } from './watch-queue'; | ||||||||||||||||||||||
| import type { Logger } from '@teambit/logger'; | ||||||||||||||||||||||
| import type { Event } from '@parcel/watcher'; | ||||||||||||||||||||||
| import type { Event, Options as ParcelWatcherOptions } from '@parcel/watcher'; | ||||||||||||||||||||||
| import ParcelWatcher from '@parcel/watcher'; | ||||||||||||||||||||||
| import { execSync } from 'child_process'; | ||||||||||||||||||||||
| import { sendEventsToClients } from '@teambit/harmony.modules.send-server-sent-events'; | ||||||||||||||||||||||
| import { WatcherDaemon, WatcherClient, getOrCreateWatcherConnection, type WatcherError } from './watcher-daemon'; | ||||||||||||||||||||||
| import { formatFSEventsErrorMessage } from './fsevents-error'; | ||||||||||||||||||||||
|
|
@@ -98,6 +99,8 @@ export class Watcher { | |||||||||||||||||||||
| private parcelSubscription: { unsubscribe: () => Promise<void> } | null = null; | ||||||||||||||||||||||
| // Signal handlers for cleanup (to avoid accumulation) | ||||||||||||||||||||||
| private signalCleanupHandler: (() => void) | null = null; | ||||||||||||||||||||||
| // Cached Watchman availability (checked once per process lifetime) | ||||||||||||||||||||||
| private watchmanAvailable: boolean | null = null; | ||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||
| private workspace: Workspace, | ||||||||||||||||||||||
| private pubsub: PubsubMain, | ||||||||||||||||||||||
|
|
@@ -130,6 +133,46 @@ export class Watcher { | |||||||||||||||||||||
| ]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Get Parcel watcher options, preferring Watchman on macOS when available. | ||||||||||||||||||||||
| * On macOS, FSEvents is the default but has a system-wide limit of ~500 streams. | ||||||||||||||||||||||
| * Watchman is a single-daemon solution that avoids this limit. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| private getParcelWatcherOptions(): ParcelWatcherOptions { | ||||||||||||||||||||||
| const options: ParcelWatcherOptions = { | ||||||||||||||||||||||
| ignore: this.getParcelIgnorePatterns(), | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // On macOS, prefer Watchman if available to avoid FSEvents stream limit | ||||||||||||||||||||||
| if (process.platform === 'darwin') { | ||||||||||||||||||||||
| if (this.isWatchmanAvailable()) { | ||||||||||||||||||||||
| options.backend = 'watchman'; | ||||||||||||||||||||||
| this.logger.debug('Using Watchman backend for file watching'); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| this.logger.debug('Using FSEvents backend for file watching (Watchman not available)'); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return options; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Check if Watchman is installed and running. | ||||||||||||||||||||||
| * Result is cached to avoid repeated shell executions. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| private isWatchmanAvailable(): boolean { | ||||||||||||||||||||||
| if (this.watchmanAvailable !== null) { | ||||||||||||||||||||||
| return this.watchmanAvailable; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| execSync('watchman version', { stdio: 'ignore', timeout: 5000 }); | ||||||||||||||||||||||
|
||||||||||||||||||||||
| execSync('watchman version', { stdio: 'ignore', timeout: 5000 }); | |
| execSync('watchman version', { stdio: 'ignore', timeout: 5000, shell: false }); |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider logging when Watchman detection fails or times out to help users debug watcher configuration issues. This would make it easier to diagnose problems like Watchman being installed but not responding properly.
try {
const result = spawnSync('watchman', ['version'], { stdio: 'ignore', timeout: 5000 });
this.watchmanAvailable = !result.error && result.status === 0;
if (!this.watchmanAvailable && result.error) {
this.logger.debug(`Watchman check failed: ${result.error.message}`);
}
} catch (err: any) {
this.logger.debug(`Watchman check threw exception: ${err.message}`);
this.watchmanAvailable = false;
}| } catch { | |
| if (!this.watchmanAvailable) { | |
| if (result.error) { | |
| this.logger.debug(`Watchman detection failed: ${result.error.message}`); | |
| } else { | |
| this.logger.debug(`Watchman detection failed: exited with status ${result.status}`); | |
| } | |
| } | |
| } catch (err: any) { | |
| this.logger.debug(`Watchman detection threw exception: ${err?.message ?? err}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states "Check if Watchman is installed and running" but the implementation only checks if the
watchman versioncommand executes successfully. This doesn't verify that the Watchman daemon is actually running and ready to watch files.Consider either:
watchman get-socknamewhich requires the daemon to be active)