Skip to content

Fix FSEvents routing for differently cased watch paths - #64191

Open
Jake Bailey (jakebailey) wants to merge 5 commits into
microsoft:mainfrom
jakebailey:fix-64089
Open

Fix FSEvents routing for differently cased watch paths#64191
Jake Bailey (jakebailey) wants to merge 5 commits into
microsoft:mainfrom
jakebailey:fix-64089

Conversation

@jakebailey

Copy link
Copy Markdown
Member

Fixes #64089

Use the watched volume's case sensitivity when routing FSEvents and
filtering file watches. Preserve caller-visible root casing, including
shared callbacks, overflow matching, and root deletion handling.
Skip shared path prefixes eight bytes at a time and handle ASCII case
folding without component splitting. Retain the Unicode fallback for
case-equivalent paths with different UTF-8 lengths.

Reuse the parent directory's comparer for WatchFile instead of querying
filesystem case sensitivity twice. Add routing benchmarks and expand
boundary and Unicode alignment coverage.

Compare against the initial casing fix on Apple M1. Allocations are
unchanged: zero for exact matches and rejections, and one for rebasing
a differently cased event path.

goos: darwin
goarch: arm64
pkg: github.com/microsoft/TypeScript/tsc/internal/fswatch
cpu: Apple M1
                                           │    before     │                after                │
                                           │    sec/op     │    sec/op     vs base               │
FSEventsDisplayPath/exact-match-8             9.031n ± ∞ ¹   8.958n ± ∞ ¹        ~ (p=0.548 n=5)
FSEventsDisplayPath/case-mismatch-8          130.50n ± ∞ ¹   80.89n ± ∞ ¹  -38.02% (p=0.008 n=5)
FSEventsDisplayPath/sibling-miss-8           103.20n ± ∞ ¹   14.26n ± ∞ ¹  -86.18% (p=0.008 n=5)
FSEventsDisplayPath/unrelated-miss-8         26.120n ± ∞ ¹   6.676n ± ∞ ¹  -74.44% (p=0.008 n=5)
FSEventsDisplayPath/unicode-match-8          115.30n ± ∞ ¹   74.29n ± ∞ ¹  -35.57% (p=0.008 n=5)
FSEventsDisplayPath/unicode-length-match-8    98.83n ± ∞ ¹   52.34n ± ∞ ¹  -47.04% (p=0.008 n=5)
FSEventsRoutingFanout/100-8                  10.799µ ± ∞ ¹   1.634µ ± ∞ ¹  -84.87% (p=0.008 n=5)
FSEventsRoutingFanout/1000-8                 109.21µ ± ∞ ¹   15.10µ ± ∞ ¹  -86.18% (p=0.008 n=5)
geomean                                       284.3n         94.97n        -66.60%
¹ need >= 6 samples for confidence interval at level 0.95
Use CoreFoundation case folding and NFC normalization for non-ASCII
comparisons on case-insensitive Darwin watches. Recognize expanding
aliases such as sharp s and SS without merging distinct dotted-I,
dotless-I, circled-letter, or fullwidth spellings.

Preserve the ASCII fast path, prepare watch-root comparison forms, and
share lazy event folding across routing and callback filtering. Rebase
using original path boundaries rather than folded byte lengths.

Cover file and directory watches, shared callbacks, symlinks, overflow,
and root termination with filesystem regressions and routing benchmarks.
Keep fswatch isolated and leave Windows and case-sensitive behavior
unchanged.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Darwin-specific filesystem semantics, Unicode folding, and assembly-backed CoreFoundation integration warrant final human validation.

Pull request overview

Fixes FSEvents event routing when requested path casing differs from disk casing.

Changes:

  • Adds volume-aware, Unicode-capable path comparison.
  • Applies case-aware routing to directory, file, overflow, and deletion events.
  • Adds macOS integration tests, unit tests, benchmarks, and documentation.
File summaries
File Description
tsc/internal/fswatch/watcher.go Integrates comparers into watch sharing and callback routing.
tsc/internal/fswatch/watcher_test.go Updates file callback tests.
tsc/internal/fswatch/README.md Documents FSEvents casing behavior.
tsc/internal/fswatch/pathcompare.go Implements path comparison, folding, and rebasing.
tsc/internal/fswatch/pathcompare_test.go Tests comparison and file filtering.
tsc/internal/fswatch/fsevents_darwin.go Adds case-aware FSEvents routing.
tsc/internal/fswatch/fsevents_darwin_fold_test.go Tests Unicode folding and aliases.
tsc/internal/fswatch/fsevents_darwin_ffi.s Adds the CoreFoundation folding trampoline.
tsc/internal/fswatch/fsevents_darwin_ffi.go Implements native CoreFoundation path folding.
tsc/internal/fswatch/fsevents_darwin_case_test.go Tests casing differences on macOS filesystems.
tsc/internal/fswatch/fsevents_darwin_bench_test.go Benchmarks routing and folding fan-out.
tsc/internal/fswatch/CHANGES.md Records the behavior change.
tsc/internal/fswatch/canonicalize_other.go Provides non-Darwin comparer behavior.
tsc/internal/fswatch/canonicalize_darwin.go Detects per-volume case sensitivity.
Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@jakebailey

Copy link
Copy Markdown
Member Author

This was a weekend Astra experiment; it does seem to fix everything.

The scary thing I noticed however is that tspath's path casing (inherited from Strada) is actually... not correct for macOS? It makes me wonder if it's even correct for Windows either.

For this, however, I kept fswatch self-contained. Might steal some perf tricks that were introduced here for tspath's ContainsPath checks.

@jakebailey

Copy link
Copy Markdown
Member Author

I believe kqueue also has this problem on macOS. If the other kqueue OSs have issues like this, I don't quite know what we'd do, but I think macOS is the only BSD that also has canonically insensitive filenames.

Enable the existing Darwin path comparer for kqueue subscriptions so
WatchFile recognizes alternate casing, expanding Unicode aliases, and
normalization-equivalent names reported by directory enumeration.

Add shared Darwin coverage for creation, modification, deletion, and
distinct filenames. Preserve kqueue directory-event spelling and leave
case-sensitive comparison and other platforms unchanged.
Describe volume-aware matching for FSEvents and kqueue, native Unicode
folding, backend-specific event spelling, and cached comparison forms.
Clarify the scope of the APFS observations and unchanged platform behavior.
@jakebailey

Copy link
Copy Markdown
Member Author

So, this fixes it for fswatch itself, but then we run into the problem that its callers also are interested in checking the paths, and they can get confused too, so this may not actually fix anything. I'm looking into anything that might help ☹️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FSEvents watcher drops events when requested casing differs from disk casing

3 participants