Skip to content

Clean up ResourceCache#6704

Merged
jrhee17 merged 3 commits intoline:mainfrom
jrhee17:poc/istio-step1
Apr 9, 2026
Merged

Clean up ResourceCache#6704
jrhee17 merged 3 commits intoline:mainfrom
jrhee17:poc/istio-step1

Conversation

@jrhee17
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 commented Apr 6, 2026

This PR is a subset of #6700

Motivation

The xDS resource stream layer used a lazy switchMap operator when mapping an upstream resource
to a downstream snapshot stream (e.g., a ListenerXdsResourceListenerSnapshot). On every
upstream change, the lazy implementation would unsubscribe from the old inner stream before
creating and subscribing to the new one. This caused the entire downstream ResourceNode chain
— including SDS secret subscriptions — to be torn down and rebuilt on every xDS update.

This is inconsistent with Envoy's behavior, which
maintains existing resource watchers across xDS updates and only replaces them when a genuinely
different resource is received.

A secondary consequence was that ResourceNodeMeterBinder — which records revision, error, and
missing-resource metrics per resource node — was allocated and deregistered per
ResourceNodeAdapter instance. With the old lazy teardown, gauges and counters reset to zero on
every xDS update, producing noisy metric churn.

Modifications

  • Replace SwitchMapStream (lazy) with SwitchMapEagerStream (eager): the new operator
    subscribes to the new inner stream before closing the old one, so downstream resource
    subscriptions survive across upstream changes. An epoch counter discards events from superseded
    inner streams. SwitchMapStream is deleted.

    • SnapshotStream: switchMap()switchMapEager(), backed by SwitchMapEagerStream
    • Call sites updated: ClusterStream, TransportSocketStream, ListenerStream, RouteStream
  • Replace ResourceNodeMeterBinder with ResourceNodeMeterBinderFactory: the factory maintains
    a reference-counted map of binder instances keyed by (XdsType, resourceName). acquire()
    returns the shared binder (incrementing the ref count); close() only deregisters meters when
    the last reference drops to zero, preventing metric resets during the brief overlap when two
    ResourceNodeAdapter instances exist for the same resource during an eager switch.

    • ResourceNodeMeterBinder deleted (moved as inner class of ResourceNodeMeterBinderFactory)
    • ResourceNodeMeterBinderFactory added; exposed via SubscriptionContext.meterBinderFactory()
    • DefaultSubscriptionContext: constructs and holds the factory
    • ResourceNodeAdapter: calls context.meterBinderFactory().acquire() on construction,
      meterBinder.close() on unsubscription
  • SubscriberStorage#ResourceCache is cleaned up

Result

  • xDS resource updates no longer cause unnecessary teardown and recreation of downstream resource
    subscriptions, consistent with Envoy's resource-cache behavior.
  • Resource node metrics (resource.node.revision, resource.node.error,
    resource.node.missing) remain continuous across xDS updates and only deregister when all
    watchers for a given resource name have closed.
  • Preparation for Delta stream is done

@jrhee17 jrhee17 added this to the 1.39.0 milestone Apr 6, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9791812a-28f7-4826-803e-5a69b3296e94

📥 Commits

Reviewing files that changed from the base of the PR and between 8a1a213 and e1e2fe9.

📒 Files selected for processing (1)
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java

📝 Walkthrough

Walkthrough

Refactored XDS reactive streams to use eager switching semantics, introduced a ResourceNodeMeterBinderFactory for metric binder lifecycle and reference counting, and removed centralized resource caching by eliminating cache updates and storage usages.

Changes

Cohort / File(s) Summary
Reactive Operator Migration
xds/src/main/java/com/linecorp/armeria/xds/SnapshotStream.java, xds/src/main/java/com/linecorp/armeria/xds/SwitchMapEagerStream.java, xds/src/main/java/com/linecorp/armeria/xds/ClusterStream.java, xds/src/main/java/com/linecorp/armeria/xds/ListenerStream.java, xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java, xds/src/main/java/com/linecorp/armeria/xds/TransportSocketStream.java
Replaced deferred switchMap(...) with eager switchMapEager(...) usages; introduced epoch-guarding in SwitchMapEagerStream to filter stale inner emissions and defer closing previous inner subscriptions until after new subscription establishment.
Meter Binder Factory
xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeMeterBinderFactory.java, xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeMeterBinder.java (removed), xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java, xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java, xds/src/main/java/com/linecorp/armeria/xds/DefaultSubscriptionContext.java
Removed the standalone ResourceNodeMeterBinder file and added ResourceNodeMeterBinderFactory providing per-resource binders with reference counting and lifecycle/cleanup; adapters now acquire binders from the factory; SubscriptionContext gains meterBinderFactory() and DefaultSubscriptionContext caches the factory.
Cache Management Decoupling
xds/src/main/java/com/linecorp/armeria/xds/DefaultResponseHandler.java, xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java, xds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.java
Stopped updating centralized subscriber cache on successful responses; removed ResourceCache and updateCache(...) from SubscriberStorage; XdsStreamSubscriber no longer depends on external cache and notifies watchers from its in-memory data.
Tests / Naming
xds/src/test/java/com/linecorp/armeria/xds/StreamSwitchMapEagerTest.java
Renamed test class and test methods to reflect eager switching; replaced switchMap(...) calls with switchMapEager(...) across tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

breaking change

Suggested reviewers

  • trustin
  • ikhoon
  • minwoox

Poem

🐰 I hopped through streams both swift and eager,
I tended meters with a careful ledger;
The cache was freed, watchers now awake,
Epochs blink — no stale flake.
Hooray for XDS — hop, hop, hooray! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Clean up ResourceCache' accurately reflects the PR's primary objective of removing the ResourceCache from SubscriberStorage and restructuring resource caching mechanisms across the xDS layer.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context about the ResourceCache cleanup, the transition from lazy to eager switching semantics, and the refactoring of meter binder architecture.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java (1)

27-36: ⚠️ Potential issue | 🟠 Major

Reacquire the pooled meter binder on every start.

Line 36 increments the shared binder ref count once in the constructor, but Line 84 decrements it every time this RefCountedStream stops. Because the stream can start again after its watcher count returns to zero, a stop → start → stop cycle on the same ResourceNodeAdapter will release a reference it never reacquired and can remove the shared meters while another adapter for the same (type, name) is still active. The same mismatch also leaks a reference if context.subscribe(this) throws before the cleanup lambda is installed. Please move acquire() into onStart() (before context.subscribe(this)), close it in the catch path, and release it after context.unsubscribe(this).

Also applies to: 73-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java` around
lines 27 - 36, The constructor currently acquires the shared
ResourceNodeMeterBinder once (resourceNodeMeterBinder) but never reacquires it
on subsequent restarts, causing ref-count mismatch; move the acquire call into
onStart() (i.e., call context.meterBinderFactory().acquire(type, name) at the
start of onStart() before calling context.subscribe(this)), ensure you close it
in the onStart() catch path if subscribe throws, and release it after
context.unsubscribe(this) when stopping the RefCountedStream (and in any
stop/cleanup paths) so each start acquires and every corresponding stop releases
the binder; update the handling around
context.subscribe(this)/context.unsubscribe(this) and the stop lambda to use
this per-start acquisition and proper cleanup of the ResourceNodeMeterBinder.
🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java (1)

54-55: Complete the ResourceCache cleanup to avoid stale in-file abstractions.

After removing cache wiring from subscriber construction, SubscriberStorage.ResourceCache is now a leftover abstraction in this file. Removing it (and its now-only supporting imports) would make the cleanup consistent and reduce confusion.

♻️ Proposed cleanup
-import com.google.common.collect.ImmutableMap;
-
-import com.linecorp.armeria.common.annotation.Nullable;
@@
-    static class ResourceCache {
-
-        private final Map<XdsType, Map<String, Object>> type2resources = new HashMap<>();
-
-        void updateResources(XdsType type, Map<String, Object> resources) {
-            type2resources.put(type, resources);
-        }
-
-        `@Nullable`
-        Object find(XdsType type, String resourceName) {
-            return type2resources.getOrDefault(type, ImmutableMap.of())
-                                 .get(resourceName);
-        }
-    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java` around
lines 54 - 55, Remove the now-unused ResourceCache abstraction from
SubscriberStorage: delete the inner class SubscriberStorage.ResourceCache and
any imports that only supported it, and update code references so subscriber
construction uses the existing subscriberMap logic (e.g., where
XdsStreamSubscriber is created and subscriberMap.get(type).put(resourceName,
subscriber)); ensure no remaining references to ResourceCache remain and
run/adjust imports accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java`:
- Around line 27-36: The constructor currently acquires the shared
ResourceNodeMeterBinder once (resourceNodeMeterBinder) but never reacquires it
on subsequent restarts, causing ref-count mismatch; move the acquire call into
onStart() (i.e., call context.meterBinderFactory().acquire(type, name) at the
start of onStart() before calling context.subscribe(this)), ensure you close it
in the onStart() catch path if subscribe throws, and release it after
context.unsubscribe(this) when stopping the RefCountedStream (and in any
stop/cleanup paths) so each start acquires and every corresponding stop releases
the binder; update the handling around
context.subscribe(this)/context.unsubscribe(this) and the stop lambda to use
this per-start acquisition and proper cleanup of the ResourceNodeMeterBinder.

---

Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java`:
- Around line 54-55: Remove the now-unused ResourceCache abstraction from
SubscriberStorage: delete the inner class SubscriberStorage.ResourceCache and
any imports that only supported it, and update code references so subscriber
construction uses the existing subscriberMap logic (e.g., where
XdsStreamSubscriber is created and subscriberMap.get(type).put(resourceName,
subscriber)); ensure no remaining references to ResourceCache remain and
run/adjust imports accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2e51b27d-5688-458c-beed-6abe02c63c52

📥 Commits

Reviewing files that changed from the base of the PR and between e86cf86 and 8a1a213.

📒 Files selected for processing (15)
  • xds/src/main/java/com/linecorp/armeria/xds/ClusterStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/DefaultResponseHandler.java
  • xds/src/main/java/com/linecorp/armeria/xds/DefaultSubscriptionContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/ListenerStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeMeterBinder.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeMeterBinderFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SnapshotStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.java
  • xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
  • xds/src/main/java/com/linecorp/armeria/xds/SwitchMapEagerStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/TransportSocketStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.java
  • xds/src/test/java/com/linecorp/armeria/xds/StreamSwitchMapEagerTest.java
💤 Files with no reviewable changes (2)
  • xds/src/main/java/com/linecorp/armeria/xds/DefaultResponseHandler.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeMeterBinder.java

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (8150425) to head (1c5adc6).
⚠️ Report is 411 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main   #6704       +/-   ##
============================================
- Coverage     74.46%       0   -74.47%     
============================================
  Files          1963       0     -1963     
  Lines         82437       0    -82437     
  Branches      10764       0    -10764     
============================================
- Hits          61385       0    -61385     
+ Misses        15918       0    -15918     
+ Partials       5134       0     -5134     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jrhee17 jrhee17 marked this pull request as ready for review April 8, 2026 01:27
Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

👍👍

Copy link
Copy Markdown
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

👍 👍

@jrhee17 jrhee17 merged commit f21a684 into line:main Apr 9, 2026
17 checks passed
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.

3 participants