Skip to content

v0.9.9 — Feature complete

Pre-release
Pre-release

Choose a tag to compare

@jamesgober jamesgober released this 13 May 03:23
· 7 commits to main since this release

Release Notes for v0.9.9 - Native eyes

Version 0.9.9 - 2026-05-12

The watch feature gets its real backend. Through 0.9.8 the implementation was a polling thread that re-stat'd the file at 100 ms intervals, which was reasonable on Linux/macOS but unreliable on Windows where the mtime granularity is too coarse for sub-second changes (three watch tests were #[cfg_attr(windows, ignore)]'d as a consequence). This release replaces all of that with the OS-native event source on every platform: inotify on Linux, FSEvents on macOS, and ReadDirectoryChangesW on Windows. Backed by the notify 6 crate gated on the watch feature, with default-features off and only the macos_fsevent selector enabled to keep the transitive set tight.

Public API is unchanged: MemoryMappedFile::watch(callback) still returns a Result<WatchHandle>, the callback still receives ChangeEvent { offset, len, kind } with the same 3-variant ChangeKind. The breaking aspect is implementation-side timing. Polling delivered changes after a ~100 ms detection window; native backends deliver in <1 ms on Linux, <10 ms on Windows, <50 ms on macOS (FSEvents coalesces by design). Callers who used the previous 100 ms delivery as a de-facto debounce floor will see events sooner. The three previously-ignored Windows watch tests run live on every platform now, plus a new tests/watch_native.rs file with five integration tests covering modify, truncate, extend, rapid-sequence, and removed.

This release also formalizes the 1.0.0 hold: the codebase is structurally ready for 1.0 after 0.9.10 closes (fuzz, examples, performance docs, migration guide), but 1.0.0 is on indefinite hold pending cross-repo presentation work (consistent headers/branding/SECURITY.md across the project family). The previously-planned 1.0.0-rc.1 candidate phase is dropped: hyphenated tags caused tooling issues in prior cycles, soak happens on the last 0.9.x in real-world deployment, and 1.0.0 ships directly when it ships. ROADMAP updated accordingly.

Highlights

  • Native backends on every platform. inotify on Linux delivers events as the kernel sees them (typical <1 ms from file change to callback). FSEvents on macOS batches at ~50 ms granularity by design. ReadDirectoryChangesW on Windows delivers in <10 ms. The platform-behavior table is in the rustdoc on MemoryMappedFile::watch and in docs/API.md.
  • notify 6 as the abstraction layer. notify::recommended_watcher picks the best backend at compile time; we wrap it with our WatchHandle type that owns the underlying watcher plus a dispatcher thread that drains the event channel and translates each notify::EventKind into our coarser ChangeKind. The translation is in map_notify_kind and is intentionally conservative: anything that's not clearly metadata or removal surfaces as Modified so callers refresh rather than miss a real change.
  • Three previously-ignored Windows watch tests run live. watch::tests::test_watch_file_changes, watch::tests::test_multiple_watchers, and tests/feature_integration.rs::test_all_features_integration all lose their #[cfg_attr(windows, ignore = "...")] markers and pass on every platform.
  • Five new integration tests in tests/watch_native.rs: watch_modify_detected, watch_truncate_detected, watch_extend_detected, watch_rapid_sequence_coalesces_or_reports_each, watch_removed_event_terminates_dispatcher. Each uses std::fs API writes from a separate file handle to simulate the real-world "another process modified the file" scenario.
  • WatchHandle::Drop tears down cleanly. Drops the underlying notify::RecommendedWatcher first (which closes the OS subscription synchronously and the internal channel), then detaches the dispatcher thread via a join wrapper so the dropping thread is never blocked past the OS's own teardown time.
  • WatchHandle::is_active() is now part of the surface (was previously #[allow(dead_code)]); useful for tests and diagnostics.

Bug-class changes

  • The Windows polling-watch flakiness is gone. The root cause was Windows mtime granularity: std::fs::metadata().modified() returned timestamps quantized to the FS-level resolution, so two writes within the same tick looked identical and the watch loop missed the second. ReadDirectoryChangesW operates on raw filesystem change notifications and does not depend on mtime, so the granularity issue disappears entirely.
  • Per-watcher thread leak is gone. The old polling implementation spawned a thread per watch call that ran a loop { sleep; poll } until the file was deleted; WatchHandle::Drop signaled it via an AtomicBool (audit H5 fix in 0.9.5). The new implementation's thread exits as soon as the channel closes, which happens synchronously when the RecommendedWatcher drops. No AtomicBool, no polling interval to wait for.

Important note: mmap-write detection

mmap-side writes (mmap.update_region(...) followed by mmap.flush()) are not a reliable trigger for any platform's native FS watcher. The writes go through the page cache and only reach the watcher at OS-decided writeback time, which is platform-dependent and never well-bounded. This is the same story as on every other native filesystem watcher in existence (notify-rs's docs note this; inotify's man page documents the limitation).

For reliable cross-platform detection, modify the file through the std::fs API from another process or another file handle. This matches the actual real-world use case for watch: detect changes made by something other than the current mapping holder. The rustdoc on MemoryMappedFile::watch calls this out, the README has a one-line note in the watch example, and the integration tests are written this way (write via std::fs::OpenOptions + write_all + sync_all).

Breaking changes

No public API breaks. The full surface of MemoryMappedFile::watch, WatchHandle, ChangeEvent, and ChangeKind is unchanged.

The implementation-side change in event timing (faster delivery) is the only behavioral difference visible to callers. If you were relying on the old ~100 ms polling delay as an implicit debounce floor, add explicit debouncing on top of the callback (wait N ms after the last event before reacting). Most callers will simply observe their changed flag flip faster.

Tests

  • 127 tests pass under --all-features (up from 121 in 0.9.8), 1 ignored (the unrelated hugepages-fallback test), 0 failed. The 3 Windows-ignored watch tests are now live, and 5 new integration tests landed in tests/watch_native.rs.
  • CI matrix combos still clean locally for --no-default-features and --no-default-features --features "cow locking advise".
  • cargo fmt --check clean.
  • cargo clippy --all-targets --all-features -D warnings clean.
  • Banned-words scan zero hits.
  • MSRV unchanged at Rust 1.75. cargo +1.75 build --all-features clean. notify 6.1.x advertises MSRV 1.60; the transitive set adds crossbeam-channel, mio (Linux), filetime, and windows-sys-derived shims (Windows).

Documentation

  • docs/API.md watch section fully rewritten. Platform-behavior table (Linux <1 ms / macOS <50 ms / Windows <10 ms typical latencies), coalescing notes, error contract, the mmap-write caveat. Install snippets bumped to 0.9.9. Version history entry added.
  • README.md feature-table entry rewritten ("Native file-change notifications: inotify (Linux), FSEvents (macOS), ReadDirectoryChangesW (Windows)"). Watch example block updated. mmap-write caveat note added.
  • REPS.md watch surface marked Since 0.9.9: backed by notify.
  • .dev/ROADMAP.md restructured: 1.0.0 placed on indefinite hold with the rationale documented (cross-repo presentation cleanup), 1.0.0-rc.1 removed entirely (hyphenated tags caused prior tooling issues), versioning strategy through hold documented as "continue with 0.9.x; jump to 0.10.0 if necessary, but prefer 0.9 to 1.0 trajectory."

Roadmap status

Version Goal Status
0.9.5 Correctness bugfix release shipped
0.9.6 Unsafe audit + property tests shipped
0.9.7 Performance milestone (zero-copy iteration, touch_pages tight loop) shipped
0.9.8 Ergonomic API expansion shipped
0.9.9 Native watch backends this release
0.9.10 Pre-1.0 stabilization (fuzz, examples, performance docs, migration guide) next
1.0.0 Stable on hold pending cross-repo cleanup

Notes

  • No source-code change to the watch public API. Callers compile unchanged. The dispatcher thread is named mmap-io-watch:<path> so it's easy to identify in top / Task Manager / debugger views.
  • notify was chosen over hand-rolled per-platform code because it has been stress-tested by thousands of downstream crates over multiple years across all three platforms; the per-platform quirks (inotify mask handling, FSEvents event types, ReadDirectoryChangesW overlapped-I/O semantics) are deep enough to make hand-rolling a multi-week investment with no functional payoff. The dep cost (one direct dep, ~5 transitive items with default-features off) is bounded and stable.
  • F1 (anonymous shared-memory mapping) remains open for the same structural reason it was held in 0.9.8: it requires Inner.file: Option<File> plus sentinel-path handling threaded through resize / prefetch / async-flush. Tractable but its own focused milestone.

Full Changelog: v0.9.8...v0.9.9