-
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
Changes from 9 commits
67230b2
e95c668
23ad66c
a5fadac
a32c71c
3689733
d603845
93175e4
75a2864
0ed6a80
6e4fd76
b0fd442
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 { spawnSync } 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,48 @@ 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. | ||||||||||||||||||||||
| * Result is cached to avoid repeated executions. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| private isWatchmanAvailable(): boolean { | ||||||||||||||||||||||
| if (this.watchmanAvailable !== null) { | ||||||||||||||||||||||
| return this.watchmanAvailable; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| // Use spawnSync with shell: false (default) for security - prevents command injection | ||||||||||||||||||||||
| const result = spawnSync('watchman', ['version'], { stdio: 'ignore', timeout: 5000 }); | ||||||||||||||||||||||
| // Check for spawn errors (e.g., command not found) or non-zero exit status | ||||||||||||||||||||||
| this.watchmanAvailable = !result.error && result.status === 0; | ||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||
|
||||||||||||||||||||||
| } 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.
remove only @davidfirst