Skip to content

fix(idlewatcher): don't adopt a dependency's config on reload - #263

Merged
yusing merged 2 commits into
yusing:mainfrom
taljaards:fix/idlewatcher-dependency-config-clobber
Sep 10, 2026
Merged

yusing merged 2 commits into
yusing:mainfrom
taljaards:fix/idlewatcher-dependency-config-clobber

Conversation

@taljaards

@taljaards taljaards commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

A route that is both its own idle route and another route's dependency can silently lose its own idlewatcher settings.

The bug

NewWatcher reuses an existing watcher by key and copies the incoming base config over the stored one:

if exists {
    if cfg.IdleTimeout > 0 {
        w.cfg.IdlewatcherConfigBase = cfg.IdlewatcherConfigBase
    }

Dependencies are given IdleTimeout = neverTick so they never auto-sleep, and neverTick is time.Duration(1<<63 - 1) — positive, so it passes that check.

When such a route is resolved as a dependency, NewWatcher is called a second time for the same container key with the dependency-synthesized config, and the route's real idle_timeout, wake_timeout, stop_method and stop_signal are replaced by the dependency's inherited base. Which config wins depends on the order NewWatcher happens to be called in.

The fix

Preserve a route-owned base config when a dependency config arrives, while still adopting updated settings for dependency-only watchers:

if cfg.IdleTimeout > 0 && (cfg.IdleTimeout != neverTick || w.cfg.IdleTimeout == neverTick) {
    w.cfg.IdlewatcherConfigBase = cfg.IdlewatcherConfigBase
}

A genuine positive route config is still adopted, including when a dependency-only watcher becomes a route-owned watcher. Existing handling of incoming zero and negative idle timeouts is unchanged.

Tests

Regression coverage checks all base settings: idle timeout, wake timeout, stop timeout, stop method, and stop signal.

  • Dependency configs preserve an existing route's own settings.
  • Genuine route reloads adopt updated settings.
  • Dependency-only watchers adopt updated dependency settings and can be promoted to route-owned watchers.
  • Incoming zero and negative timeouts retain their existing adoption behavior.
  • Reloading a parent refreshes its existing dependency watcher through actual dependency resolution, for inherited settings and explicit zero/negative dependency configs.

Verification

Validated on Linux/arm64 with Go 1.27.1 using the PR's pinned submodules:

  • Focused reload tests passed.
  • The original PR guard caused all three parent-resolution refresh cases to fail with stale settings; restoring the fix made them pass.
  • shadowtree test ./internal/idlewatcher/... -count=1 passed.
  • shadowtree test-race ./internal/idlewatcher/... -count=1 passed.
  • Scoped formatting and git diff --check passed.

No dependency changes or platform shims were needed. Tests use the existing fake-provider harness, not live Docker containers.

🤖 Generated with Claude Code

Note

Stop idlewatcher reload from adopting dependency config over route-owned timeout

Changes the reload condition in watcher.go so a dependency-synthesized neverTick config no longer overwrites an existing watcher that already has a route-owned idle timeout. Genuine reloads with a positive timeout or non-neverTick settings still adopt the new config, and dependency-only watchers still receive refreshed settings.

Adds table-driven tests in watcher_dependencies_test.go covering route-owned reloads, dependency-only reloads, and dependency-setting refresh through a parent reload.

Macroscope summarized 7807dfa.

Summary by CodeRabbit

  • Bug Fixes

    • Preserved existing idle route settings when dependency-generated configuration is applied during a watcher reload.
    • Ensured valid configuration updates continue to apply updated idle and wake timeout values.
  • Tests

    • Added coverage for dependency-generated reloads and valid configuration updates.

NewWatcher reuses an existing watcher by key and copies the incoming
IdlewatcherConfigBase over the stored one when the incoming idle timeout
is positive. neverTick, which marks a dependency that must never
auto-sleep, is math.MaxInt64 and so passes that check.

A route that is both its own idle route and another route's dependency
therefore loses its own idle_timeout, wake_timeout, stop_method and
stop_signal to the dependency's inherited base, depending on the order
NewWatcher is called in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 2eb5f756-90b9-4262-9009-778d7455f8b7

📥 Commits

Reviewing files that changed from the base of the PR and between 752e6cb and 7807dfa.

📒 Files selected for processing (2)
  • internal/idlewatcher/watcher.go
  • internal/idlewatcher/watcher_dependencies_test.go

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

NewWatcher now preserves an existing route watcher’s base configuration when a dependency-synthesized config uses the neverTick sentinel. Tests also verify that genuine configuration updates are still applied.

Changes

Idle watcher reload behavior

Layer / File(s) Summary
Reload guard and validation
internal/idlewatcher/watcher.go, internal/idlewatcher/watcher_dependencies_test.go
The reload path skips base configuration replacement for dependency-synthesized neverTick settings. Tests verify preservation of route settings and adoption of genuine timeout updates.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Merge Risk: ⚪ Minimal · up to 7807d

Idle watcher reloads now preserve route settings when dependency-generated neverTick configurations are encountered, while real route configuration updates continue to apply. No current merge-blocking risk is identified.

Suggested reviewers: yusing

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing a dependency's configuration from replacing the route's own configuration during reload.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the watcher’s beat
The neverTick setting stays neat
Route values remain in place
New timeouts update with grace
Tests guard the reload space

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@yusing
yusing merged commit 508a4f1 into yusing:main Sep 10, 2026
6 checks passed
@yusing

yusing commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Thanks~

@taljaards
taljaards deleted the fix/idlewatcher-dependency-config-clobber branch September 10, 2026 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants