Skip to content

fix(watch): filter out Access events to prevent infinite rebuild loop on Linux#8557

Merged
hyf0 merged 4 commits intorolldown:mainfrom
hyf0-agent:fix/watch-infinite-rebuild-linux
Mar 6, 2026
Merged

fix(watch): filter out Access events to prevent infinite rebuild loop on Linux#8557
hyf0 merged 4 commits intorolldown:mainfrom
hyf0-agent:fix/watch-infinite-rebuild-linux

Conversation

@hyf0-agent
Copy link
Copy Markdown
Contributor

Summary

Fix infinite rebuild loop in watch mode on Linux (regression in rc.7).

Closes #8555

Root Cause

rolldown-notify with TargetMode::TrackPath registers inotify watches including IN_OPEN and IN_ATTRIB flags. When the build process reads watched source files during bundling, inotify emits Access events (EventKind::Access(Open), Access(Read), Access(Close)).

The previous catch-all _ => WatcherChangeKind::Update in TaskFsEventHandler::map_event_kind mapped these Access events to file-change notifications, causing:

build reads src/main.js → inotify fires IN_OPEN
→ notify emits Access(Open) → map_event_kind returns Update
→ watcher triggers rebuild → build reads src/main.js again → ∞

Evidence from strace:

inotify_add_watch(21, ".../src", IN_MODIFY|IN_ATTRIB|IN_CLOSE_WRITE|IN_OPEN|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE)

Evidence from inotifywait — the only events on source files during each rebuild cycle are reads:

OPEN src/main.js → ACCESS → CLOSE_NOWRITE
OPEN src/hello.js → ACCESS → CLOSE_NOWRITE

Fix

Only map Create, Remove, and Modify events to rebuild triggers. All other event kinds (Access, Other, Any) return None and are filtered out before reaching the coordinator.

Test Plan

Reproduced on Linux (Ubuntu, kernel 6.14.0) with a minimal project:

  • Before fix: editing any source file triggers infinite rebuild loop (~2-3ms per cycle)
  • After fix: single rebuild on file change, watcher stabilizes correctly

… on Linux

On Linux, `rolldown-notify` with `TargetMode::TrackPath` registers inotify
watches that include `IN_OPEN` and `IN_ATTRIB` flags. When the build process
reads watched source files, these flags cause inotify to emit Access events.

The previous catch-all `_ => WatcherChangeKind::Update` mapped these Access
events to file-change notifications, causing:

  build reads src → inotify fires IN_OPEN → watcher treats as Update
  → triggers rebuild → build reads src again → infinite loop

Fix: only map Create, Remove, and Modify events to rebuild triggers.
All other event kinds (Access, Other, Any) are now ignored.

Closes rolldown#8555
@hyf0 hyf0 requested review from sapphi-red and shulaoda March 5, 2026 16:56
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 6, 2026

Open in StackBlitz

@rolldown/browser

npm i https://pkg.pr.new/@rolldown/browser@8557

@rolldown/debug

npm i https://pkg.pr.new/@rolldown/debug@8557

@rolldown/pluginutils

npm i https://pkg.pr.new/@rolldown/pluginutils@8557

rolldown

npm i https://pkg.pr.new/rolldown@8557

@rolldown/binding-android-arm64

npm i https://pkg.pr.new/@rolldown/binding-android-arm64@8557

@rolldown/binding-darwin-arm64

npm i https://pkg.pr.new/@rolldown/binding-darwin-arm64@8557

@rolldown/binding-darwin-x64

npm i https://pkg.pr.new/@rolldown/binding-darwin-x64@8557

@rolldown/binding-freebsd-x64

npm i https://pkg.pr.new/@rolldown/binding-freebsd-x64@8557

@rolldown/binding-linux-arm-gnueabihf

npm i https://pkg.pr.new/@rolldown/binding-linux-arm-gnueabihf@8557

@rolldown/binding-linux-arm64-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-arm64-gnu@8557

@rolldown/binding-linux-arm64-musl

npm i https://pkg.pr.new/@rolldown/binding-linux-arm64-musl@8557

@rolldown/binding-linux-ppc64-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-ppc64-gnu@8557

@rolldown/binding-linux-s390x-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-s390x-gnu@8557

@rolldown/binding-linux-x64-gnu

npm i https://pkg.pr.new/@rolldown/binding-linux-x64-gnu@8557

@rolldown/binding-linux-x64-musl

npm i https://pkg.pr.new/@rolldown/binding-linux-x64-musl@8557

@rolldown/binding-openharmony-arm64

npm i https://pkg.pr.new/@rolldown/binding-openharmony-arm64@8557

@rolldown/binding-wasm32-wasi

npm i https://pkg.pr.new/@rolldown/binding-wasm32-wasi@8557

@rolldown/binding-win32-arm64-msvc

npm i https://pkg.pr.new/@rolldown/binding-win32-arm64-msvc@8557

@rolldown/binding-win32-x64-msvc

npm i https://pkg.pr.new/@rolldown/binding-win32-x64-msvc@8557

commit: c73f601

Copy link
Copy Markdown
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

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

The code looks good to me.
I guess we should align with

EventKind::Create(_create_kind) => {
for path in batched_event.detail.paths {
changed_files.insert(path, WatcherChangeKind::Create);
}
}
#[cfg(target_os = "macos")]
EventKind::Modify(notify::event::ModifyKind::Metadata(_))
if !self.ctx.options.use_polling =>
{
// When using kqueue on mac, ignore metadata changes as it happens frequently and doesn't affect the build in most cases
// Note that when using polling, we shouldn't ignore metadata changes as the polling watcher prefer to emit them over
// content change events
}
EventKind::Modify(notify::event::ModifyKind::Name(notify::event::RenameMode::From))
| EventKind::Remove(_) => {
for path in batched_event.detail.paths {
changed_files.insert(path, WatcherChangeKind::Delete);
}
}
EventKind::Modify(_modify_kind) => {
for path in batched_event.detail.paths {
changed_files.insert(path, WatcherChangeKind::Update);
}
}
later

Treat Modify(Name(RenameMode::From)) as Delete to match the event
handling in BundleCoordinator::handle_watch_event, where a rename-from
is treated as a file deletion.

Ref: rolldown#8557 (review)
@hyf0 hyf0 merged commit b613c7d into rolldown:main Mar 6, 2026
31 checks passed
@github-actions github-actions bot mentioned this pull request Mar 9, 2026
shulaoda added a commit that referenced this pull request Mar 9, 2026
## [1.0.0-rc.8] - 2026-03-09

### 🚀 Features

- watch: enable full functional fs watcher in wasm (#8575) by @hyf0
- watch: expose debounce related options (#8572) by @hyf0

### 🐛 Bug Fixes

- detect new URL(…, import.meta.url) with no-sub template literal (#8565) by @char
- devtools: trace dynamic imports in devtools (#8581) by @cal-gooo
- watch: rebuild when a previously missing file is created (#8562) by @hyf0-agent
- watch: filter out Access events to prevent infinite rebuild loop on Linux (#8557) by @hyf0-agent

### 🚜 Refactor

- watch: remove auto watch for fail imports (#8585) by @hyf0
- fs_watcher: unify the way of constructing watcher (#8571) by @hyf0
- cli: migrate CLI to CAC (#8551) by @h-a-n-a
- switch asset module support from hard-code to builtin plugin (#8546) by @hyf0

### 📚 Documentation

- fix subject-verb agreement in why-bundlers.md (#8591) by @brandonzylstra
- maintenance: align release and canary workflow guide (#8538) by @minsoo-web
- add `format` option to directives example config (#8590) by @shulaoda
- fix: change twitter to x logo in team (#8552) by @mdong1909
- correct composable filter support explanation (#8550) by @sapphi-red

### ⚡ Performance

- testing: share tokio runtime across fixture tests (#8567) by @Boshen

### 🧪 Testing

- hmr: fix infinite loop in dev server test retry logic (#8576) by @hyf0-agent
- cli: add more cli-e2e test cases (#8548) by @h-a-n-a

### ⚙️ Miscellaneous Tasks

- docs: update in-depth/directives for `output.strict` option (#8535) by @minsoo-web
- add PNPM_HOME Dev Drive mapping to Windows CI workflows (#8589) by @Boshen
- deps: update github-actions (#8588) by @renovate[bot]
- move Windows cargo target dir to Dev Drive (#8586) by @Boshen
- optimize cache keys to fix race conditions and reduce usage (#8578) by @Boshen
- remove WASI build & test pipeline (#8580) by @Boshen
- remove unnecessary submodule checkouts (#8577) by @Boshen
- use Dev Drive for Windows CI jobs (#8574) by @Boshen
- skip redundant native binding build for browser and remove standalone job (#8573) by @Boshen
- parallelize Node tests on ubuntu, single Node 24 on macOS/windows (#8570) by @Boshen
- docs: bump @voidzero-dev/vitepress-theme to 4.8.0 (#8558) by @crusty-voidzero
- dedupe type-check from dev server workflow (#8554) by @Boshen

### ❤️ New Contributors

* @brandonzylstra made their first contribution in [#8591](#8591)
* @char made their first contribution in [#8565](#8565)
* @cal-gooo made their first contribution in [#8581](#8581)
* @hyf0-agent made their first contribution in [#8562](#8562)
* @h-a-n-a made their first contribution in [#8551](#8551)

Co-authored-by: shulaoda <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Infinite trigger watch on Linux (Ubuntu 25.04)

3 participants