Skip to content

Introduce an extension point for specifying custom ConfigSource#6747

Merged
jrhee17 merged 7 commits intoline:mainfrom
jrhee17:feat/path-config-source
May 8, 2026
Merged

Introduce an extension point for specifying custom ConfigSource#6747
jrhee17 merged 7 commits intoline:mainfrom
jrhee17:feat/path-config-source

Conversation

@jrhee17
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 commented May 4, 2026

Motivation:

The xDS ConfigSourceClient was a monolithic class that tightly coupled gRPC streaming, timeout handling, and response parsing into a single unit. This made it impossible to support non-gRPC config sources like Envoy's path_config_source (file-based) or user-defined custom_config_source backends. The class also mixed concerns — gRPC client construction, initial fetch timeout computation, and subscriber management all lived in one place.

Modifications:

  • Deleted ConfigSourceClient and decomposed it into focused components:
    • ConfigSourceSubscription — a public interface representing a subscription that delivers xDS resources from any config source
    • ConfigSourceHandler — internal glue that wires a ConfigSourceSubscription to a StateCoordinator
    • SotwConfigSourceSubscriptionFactory — a public SPI interface for creating subscriptions for non-gRPC config sources
    • SotwSubscriptionCallbacks — the callback interface implementations use to push DiscoveryResponse data back
    • GrpcConfigSourceStreamFactory — extracts the gRPC-specific client construction and stream logic from the old ConfigSourceClient, implements SotwConfigSourceSubscriptionFactory
  • Added PathSotwConfigSourceSubscriptionFactory with PathConfigSourceSubscription that watches a file via DirectoryWatchService/PathWatcher and pushes parsed DiscoveryResponse on every change
    • Added XdsResourceReader — a public utility for parsing xDS protobuf messages from YAML or JSON strings/files, with a TypeRegistry that auto-discovers all io.envoyproxy protobuf types via classpath scanning
    • Updated XdsExtensionRegistry to register built-in PathSotwConfigSourceSubscriptionFactory and GrpcConfigSourceStreamFactory, and load SPI-discovered SotwConfigSourceSubscriptionFactory implementations

Result:

  • Users can now use path_config_source in their xDS bootstrap to load xDS resources from local YAML or JSON files, with automatic file watching for updates.
  • Users can implement SotwConfigSourceSubscriptionFactory to plug in custom non-gRPC config sources (e.g., etcd, Consul, ZooKeeper) via ServiceLoader SPI.
  • XdsResourceReader is available as a public utility for parsing Envoy protobuf types from YAML/JSON.

@jrhee17 jrhee17 added this to the 1.39.0 milestone May 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors xDS config-source handling to a handler+subscription model, adds SOTW/Path/gRPC subscription factories and callbacks, centralizes YAML/JSON→protobuf parsing, extends proto for custom_config_source, and updates tests and integrations.

Changes

xDS Config-Source Handler & Subscription Model

Layer / File(s) Summary
Public API / Interfaces
xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceSubscription.java, xds/src/main/java/com/linecorp/armeria/xds/SotwSubscriptionCallbacks.java, xds/src/main/java/com/linecorp/armeria/xds/SotwConfigSourceSubscriptionFactory.java
Adds ConfigSourceSubscription (updateInterests/close), SotwSubscriptionCallbacks (onDiscoveryResponse), and SotwConfigSourceSubscriptionFactory SPI for non-gRPC SOTW subscriptions.
Handler / Lifecycle
xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceHandler.java, xds/src/main/java/com/linecorp/armeria/xds/ControlPlaneClientManager.java
Introduces ConfigSourceHandler to coordinate StateCoordinator + ConfigSourceSubscription; ControlPlaneClientManager now creates/holds handlers via factory-based createClient and uses Map<ConfigSource, ConfigSourceHandler>.
gRPC Stream Factory & Wiring
xds/src/main/java/com/linecorp/armeria/xds/GrpcConfigSourceStreamFactory.java, xds/src/main/java/com/linecorp/armeria/xds/AdsXdsStream.java
Adds GrpcConfigSourceStreamFactory that builds StateCoordinator + xDS stream graph (ADS/Delta/SOTW variants) and returns a ConfigSourceHandler; AdsXdsStream exposes stateCoordinator().
Path (file) Subscription & Metrics
xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java, xds/src/main/java/com/linecorp/armeria/xds/PathConfigSourceLifecycleObserver.java
Adds path-based SOTW subscription factory that watches files, parses on change, and pushes DiscoveryResponse; adds lifecycle observer with Micrometer counters for file loaded/errors.
Parsing Utility
xds/src/main/java/com/linecorp/armeria/xds/XdsResourceReader.java, xds/build.gradle, it/xds-client/.../XdsResourceReader.java
Adds centralized XdsResourceReader to parse YAML/JSON → protobuf using a reflected TypeRegistry; adds Jackson YAML dependency and updates the it-module test helper to delegate to it.
State & Stream Behavior
xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java, xds/src/main/java/com/linecorp/armeria/xds/SotwActualStream.java, xds/src/main/java/com/linecorp/armeria/xds/DeltaActualStream.java
StateCoordinator implements SotwSubscriptionCallbacks, computes initial fetch timeout from ConfigSource, and handles SOTW updates; SOTW/Delta actual streams delegate coordinator access to owner and call coordinator update APIs for reconciliation.
ConfigSource Mapping & Extension Registry
xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceMapper.java, xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java, xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java
Detects explicit config sources (Api/Ads/Path/Custom), XdsExtensionRegistry.of(...) now accepts DirectoryWatchService + metrics and registers path/SOTW factories (plus SPI loading), and bootstrap wiring updated to use new registry and handler map types.
Proto & Tests / Integration
xds-api/src/main/proto/.../config_source.proto, it/xds-client/src/test/.../PathConfigSourceTest.java, it/xds-controlplane-api/.../ControlPlaneApiRegressionTest.java, xds/src/test/...
Adds google.protobuf.Any custom_config_source = 1000 and Armeria proto options; adds/updates integration and unit tests to construct registry with watch service/metrics, to use ConfigSourceHandler map types, and to validate path config-source behavior for JSON/YAML.
Removals / Cleanups
xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceClient.java
Removes legacy ConfigSourceClient in favor of handler + subscription factories; corresponding constructor and test updates applied.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant CPCM as ControlPlaneClientManager
    participant CSH as ConfigSourceHandler
    participant SF as ConfigSourceFactory
    participant SUB as ConfigSourceSubscription
    participant SC as StateCoordinator
    participant STREAM as XdsStream
    participant WATCH as ResourceWatcher

    App->>CPCM: subscribe(configSource, type, name, watcher)
    CPCM->>CPCM: clientMap.computeIfAbsent(configSource, createClient)
    CPCM->>SF: create(configSource, ...)
    SF->>SC: new StateCoordinator(configSource,...)
    SF->>SUB: create subscription (Grpc or Path)
    SF-->>CPCM: return ConfigSourceHandler(SC, SUB)
    CPCM->>CSH: addSubscriber(type, name, watcher)
    CSH->>SC: register(type, name, watcher)
    CSH->>SUB: updateInterests(type, interestedResources)
    SUB->>STREAM: resourcesUpdated(type)
    STREAM->>STREAM: send/adjust discovery requests

    alt update arrives (file or gRPC)
        SUB->>SC: onDiscoveryResponse(response)
        SC->>SC: onSotwConfigUpdate(type, parsedResources)
        SC->>WATCH: notify resource updates / missing
    end

    App->>CPCM: unsubscribe(...)
    CPCM->>CSH: removeSubscriber(...)
    CSH->>SUB: updateInterests(...)
    CSH->>SC: unregister(...)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • line/armeria#6723 — Related: extension/registry and StateCoordinator/parser interfaces overlap.
  • line/armeria#6654 — Related: moves/expands XdsResourceReader parsing used here.
  • line/armeria#6597 — Related: overlapping changes to parsing and secret/DataSource handling.

Suggested reviewers

  • trustin
  • ikhoon
  • minwoox

Poem

🐇 I hop where handlers tend the stream and file,
I nibble YAML bytes and parse them with a smile,
State hums softly, stitching missing names anew,
Streams and factories dance — a tidy rendezvous,
I thump my happy paw — the xDS meadow’s styled.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing an extension point for custom ConfigSource. It aligns with the PR's core objective of enabling non-gRPC config sources through a new SPI interface and decomposing the monolithic ConfigSourceClient.
Description check ✅ Passed The description is well-related to the changeset, clearly explaining the motivation, modifications, and results. It details the decomposition of ConfigSourceClient, new components, and the ability to support custom config sources.
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.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java (1)

61-73: 💤 Low value

SPI factory name collisions will throw at startup.

If an SPI-discovered SotwConfigSourceSubscriptionFactory has the same name() as the built-in PathSotwConfigSourceSubscriptionFactory or GrpcConfigSourceStreamFactory, the ImmutableMap.Builder will throw IllegalArgumentException on duplicate keys. This may be intentional to prevent accidental overrides, but consider documenting this behavior or adding a more descriptive error message.

🤖 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/XdsExtensionRegistry.java` around
lines 61 - 73, SPI-discovered SotwConfigSourceSubscriptionFactory instances can
have the same name() as built-in factories
(PathSotwConfigSourceSubscriptionFactory or GrpcConfigSourceStreamFactory),
causing ImmutableMap.Builder to throw on duplicate keys; add an explicit
duplicate-name check before calling register (or inside register) that detects
if byName already contains the factory.name() and throw a clear
IllegalStateException (or log+skip) that includes the duplicate name and both
factory class names (e.g., reference PathSotwConfigSourceSubscriptionFactory,
GrpcConfigSourceStreamFactory, SotwConfigSourceSubscriptionFactory, register,
byName, byTypeUrl) so the startup failure message is descriptive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/PathConfigSourceTest.java`:
- Around line 127-145: The watcher lambda in verifyClusterFromPathConfigSource
silently ignores throwable t which causes tests to hang; update the
SnapshotWatcher lambda (watcher) so that if t is non-null it immediately fails
the test by rethrowing or wrapping t (e.g., throw new
AssertionError("SnapshotWatcher error", t)), otherwise continue to set
snapshotRef when snapshot is a ClusterSnapshot; keep existing await/assert logic
that checks snapshotRef and xdsResource name and still call
xdsBootstrap.clusterRoot("path-cluster") as before.

In `@xds/src/main/java/com/linecorp/armeria/xds/SotwSubscriptionCallbacks.java`:
- Around line 37-44: The onDiscoveryResponse(DiscoveryResponse) Javadoc is
missing the threading requirement; document that implementations must be invoked
on the event-loop thread used by the subscription: state that callers must call
onDiscoveryResponse from the EventExecutor passed into
SotwConfigSourceSubscriptionFactory.create (i.e., the same EventExecutor used by
StateCoordinator), and note that StateCoordinator.onDiscoveryResponse enforces
this with checkArgument(eventLoop.inEventLoop(), ...), so calling from another
thread will throw IllegalArgumentException; add this requirement to the method
Javadoc so third-party implementors know to run their callback on that
EventExecutor.

In `@xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java`:
- Around line 61-72: The initialFetchTimeoutMillis method incorrectly treats
protobuf Duration as an Instant; replace the manual Instant.ofEpochSecond
conversion with Durations.toMillis(timeoutDuration) (import
com.google.protobuf.util.Durations) to get a proper millisecond conversion and
validation; update checkArgument to validate the resulting millis (e.g.,
checkArgument(millis >= 0, ...)) and return the millis from
initialFetchTimeoutMillis (refer to method initialFetchTimeoutMillis and symbol
timeoutDuration).

---

Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java`:
- Around line 61-73: SPI-discovered SotwConfigSourceSubscriptionFactory
instances can have the same name() as built-in factories
(PathSotwConfigSourceSubscriptionFactory or GrpcConfigSourceStreamFactory),
causing ImmutableMap.Builder to throw on duplicate keys; add an explicit
duplicate-name check before calling register (or inside register) that detects
if byName already contains the factory.name() and throw a clear
IllegalStateException (or log+skip) that includes the duplicate name and both
factory class names (e.g., reference PathSotwConfigSourceSubscriptionFactory,
GrpcConfigSourceStreamFactory, SotwConfigSourceSubscriptionFactory, register,
byName, byTypeUrl) so the startup failure message is descriptive.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a5f9373-0bee-49e8-af19-77ca66aa1754

📥 Commits

Reviewing files that changed from the base of the PR and between 0f9da8a and 5db037a.

📒 Files selected for processing (26)
  • it/xds-client/src/main/java/com/linecorp/armeria/xds/it/XdsResourceReader.java
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/PathConfigSourceTest.java
  • xds-api/src/main/proto/envoy/config/core/v3/config_source.proto
  • xds/build.gradle
  • xds/src/main/java/com/linecorp/armeria/xds/AdsXdsStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceClient.java
  • xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceHandler.java
  • xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceMapper.java
  • xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceSubscription.java
  • xds/src/main/java/com/linecorp/armeria/xds/ControlPlaneClientManager.java
  • xds/src/main/java/com/linecorp/armeria/xds/DeltaActualStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/GrpcConfigSourceStreamFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/PathConfigSourceLifecycleObserver.java
  • xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/ResourceNodeAdapter.java
  • xds/src/main/java/com/linecorp/armeria/xds/SotwActualStream.java
  • xds/src/main/java/com/linecorp/armeria/xds/SotwConfigSourceSubscriptionFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/SotwSubscriptionCallbacks.java
  • xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsResourceReader.java
  • xds/src/test/java/com/linecorp/armeria/xds/StateCoordinatorTest.java
  • xds/src/test/java/com/linecorp/armeria/xds/XdsClientCleanupTest.java
  • xds/src/test/java/com/linecorp/armeria/xds/XdsExtensionRegistryTest.java
  • xds/src/test/resources/META-INF/services/com.linecorp.armeria.xds.validator.XdsValidatorIndex
💤 Files with no reviewable changes (1)
  • xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceClient.java

Comment thread xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java
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.

🧹 Nitpick comments (1)
it/xds-client/src/test/java/com/linecorp/armeria/xds/it/PathConfigSourceTest.java (1)

127-152: ⚖️ Poor tradeoff

Consider adding a test for dynamic file-update delivery.

The helper verifyClusterFromPathConfigSource only verifies the initial load path. The primary value of path_config_source is reacting to file changes at runtime. A complementary test that overwrites the file after XdsBootstrap is running and asserts the new snapshot is delivered would give meaningful coverage of the watch loop in PathConfigSourceSubscription.parseAndPush.

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

In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/PathConfigSourceTest.java`
around lines 127 - 152, The test helper verifyClusterFromPathConfigSource only
asserts initial load; add a new test that starts XdsBootstrap with
verifyClusterFromPathConfigSource (or reuse its setup) and then updates the file
backing the PathConfigSource to a different cluster name, then await until the
SnapshotWatcher (set as defaultSnapshotWatcher) observes a new ClusterSnapshot
with the new resource name. Specifically, reuse the
AtomicReference<ClusterSnapshot> and SnapshotWatcher logic from
verifyClusterFromPathConfigSource, perform a file overwrite of the path-config
file after xdsBootstrap.clusterRoot("path-cluster") is active, and assert the
watcher receives the updated snapshot (verifying
PathConfigSourceSubscription.parseAndPush reacts to the file change and pushes
the new snapshot).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/PathConfigSourceTest.java`:
- Around line 127-152: The test helper verifyClusterFromPathConfigSource only
asserts initial load; add a new test that starts XdsBootstrap with
verifyClusterFromPathConfigSource (or reuse its setup) and then updates the file
backing the PathConfigSource to a different cluster name, then await until the
SnapshotWatcher (set as defaultSnapshotWatcher) observes a new ClusterSnapshot
with the new resource name. Specifically, reuse the
AtomicReference<ClusterSnapshot> and SnapshotWatcher logic from
verifyClusterFromPathConfigSource, perform a file overwrite of the path-config
file after xdsBootstrap.clusterRoot("path-cluster") is active, and assert the
watcher receives the updated snapshot (verifying
PathConfigSourceSubscription.parseAndPush reacts to the file change and pushes
the new snapshot).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 861fe2d6-d3fa-4888-9e78-bb9fb52cc2d6

📥 Commits

Reviewing files that changed from the base of the PR and between 5db037a and a088c57.

📒 Files selected for processing (4)
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/PathConfigSourceTest.java
  • xds/src/main/java/com/linecorp/armeria/xds/SotwConfigSourceSubscriptionFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/SotwSubscriptionCallbacks.java
  • xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java
✅ Files skipped from review due to trivial changes (1)
  • xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • xds/src/main/java/com/linecorp/armeria/xds/SotwSubscriptionCallbacks.java
  • xds/src/main/java/com/linecorp/armeria/xds/SotwConfigSourceSubscriptionFactory.java

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
it/xds-controlplane-api/src/test/java/com/linecorp/armeria/xds/it/ControlPlaneApiRegressionTest.java (1)

115-119: ⚡ Quick win

Fail fast on watcher callback errors instead of silently ignoring them.

At Line 115 and Line 186, the callback drops t. If the watcher reports an error, these tests may only fail by timeout. Capture t and assert it is null inside await() for clearer failures.

Suggested change
-        final AtomicReference<ClusterSnapshot> snapshotRef = new AtomicReference<>();
+        final AtomicReference<ClusterSnapshot> snapshotRef = new AtomicReference<>();
+        final AtomicReference<Throwable> errorRef = new AtomicReference<>();
         try (XdsBootstrap xdsBootstrap = XdsBootstrap.of(bootstrap)) {
             xdsBootstrap.clusterRoot("my-cluster")
                         .addSnapshotWatcher((snapshot, t) -> {
+                            if (t != null) {
+                                errorRef.set(t);
+                                return;
+                            }
                             if (snapshot != null) {
                                 snapshotRef.set(snapshot);
                             }
                         });

             await().untilAsserted(() -> {
+                assertThat(errorRef.get()).isNull();
                 assertThat(snapshotRef.get()).isNotNull();
                 assertThat(snapshotRef.get().xdsResource().resource().getName())
                         .isEqualTo("my-cluster");
             });
         }

Also applies to: 186-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@it/xds-controlplane-api/src/test/java/com/linecorp/armeria/xds/it/ControlPlaneApiRegressionTest.java`
around lines 115 - 119, The watcher callback passed to addSnapshotWatcher
currently ignores the throwable parameter t, causing test failures to surface
only via timeouts; modify the callback used in ControlPlaneApiRegressionTest
(the lambda passed to addSnapshotWatcher that updates snapshotRef) to capture
the throwable (t) into a shared AtomicReference or similar (e.g., errorRef) and
then, in the await() assertion after the watcher runs, assert that that errorRef
is null (or rethrow/assert the throwable) so any watcher error fails fast and
surfaces immediately; apply the same change to the second occurrence around
lines referenced (the other addSnapshotWatcher callback).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java`:
- Around line 102-114: The watchService.register(...) call in
PathSotwConfigSourceSubscriptionFactory is unprotected and can throw, leaving
the subscription inactive; wrap that registration in a try/catch inside the
CommonPools.blockingTaskExecutor() runnable, catch Throwable from
watchService.register(PathWatcher.ofFile(...)), and on error schedule an
eventLoop.execute to surface the failure (e.g., call
close0(cancellableCandidate) or a dedicated failure handler) and log/report the
exception; ensure watchCancellable is only set on success and that
close0/watchCancellable cleanup paths are invoked from the event loop so the
subscription doesn't silently stall (refer to symbols: watchService.register,
PathWatcher.ofFile, parseAndPush, watchCancellable, close0,
CommonPools.blockingTaskExecutor, eventLoop).
- Around line 61-67: The factory method
PathSotwConfigSourceSubscriptionFactory.create currently assumes
configSource.getPathConfigSource() exists and contains a non-empty path; add
validation in create(ConfigSource, SotwSubscriptionCallbacks, EventExecutor) to
call configSource.hasPathConfigSource() and verify the returned
PathConfigSource's path is non-null and not blank (e.g., path() or getPath()),
and if invalid throw an IllegalArgumentException (or another appropriate runtime
exception) with a clear message describing the missing/empty path instead of
constructing a PathConfigSourceSubscription; this ensures
PathConfigSourceSubscription is only created with a valid PathConfigSource.

---

Nitpick comments:
In
`@it/xds-controlplane-api/src/test/java/com/linecorp/armeria/xds/it/ControlPlaneApiRegressionTest.java`:
- Around line 115-119: The watcher callback passed to addSnapshotWatcher
currently ignores the throwable parameter t, causing test failures to surface
only via timeouts; modify the callback used in ControlPlaneApiRegressionTest
(the lambda passed to addSnapshotWatcher that updates snapshotRef) to capture
the throwable (t) into a shared AtomicReference or similar (e.g., errorRef) and
then, in the await() assertion after the watcher runs, assert that that errorRef
is null (or rethrow/assert the throwable) so any watcher error fails fast and
surfaces immediately; apply the same change to the second occurrence around
lines referenced (the other addSnapshotWatcher callback).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b7dd28e-f645-49f9-9eb6-a464ded20e24

📥 Commits

Reviewing files that changed from the base of the PR and between a088c57 and 5b7ecdf.

📒 Files selected for processing (5)
  • it/xds-controlplane-api/build.gradle
  • it/xds-controlplane-api/src/test/java/com/linecorp/armeria/xds/it/ControlPlaneApiRegressionTest.java
  • settings.gradle
  • xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceMapper.java
  • xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java

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.

♻️ Duplicate comments (2)
xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java (2)

101-121: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Watcher-registration failure handling still incomplete

The catch block silently calls lifecycleObserver.close() from the blocking executor thread (not the event loop), which is inconsistent with every other lifecycle-observer call in this class (all of which happen on the event loop via parseAndPush or close0). Additionally, the failure is still not surfaced to callbacks, leaving the subscription silently inoperative.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java`
around lines 101 - 121, In startWatching(), the catch block currently closes
lifecycleObserver from the blocking executor and doesn't notify the subscription
callbacks; instead, schedule the failure handling onto the eventLoop: inside the
catch, capture the exception and call eventLoop.execute(() -> {
lifecycleObserver.close(); close0(null) or the appropriate cleanup; invoke the
subscription callbacks with the error (e.g., callbacks.onError /
callbacks.onSubscriptionFailed or the class's existing error-reporting callback)
so the failure is surfaced; ensure watchCancellable remains null. Reference:
startWatching, watchService.register, lifecycleObserver, eventLoop, close0,
watchCancellable, callbacks, parseAndPush.

61-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validation still missing in create()

configSource.getPathConfigSource() is called without first checking configSource.hasPathConfigSource(), and neither configSource nor callbacks nor eventLoop are null-checked. This is unchanged since the prior review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java`
around lines 61 - 67, In create(), validate inputs before using them: check
configSource is non-null and configSource.hasPathConfigSource() is true before
calling configSource.getPathConfigSource(), and null-check callbacks and
eventLoop; if any validation fails, throw an appropriate exception (e.g.,
IllegalArgumentException or NullPointerException) with a clear message. After
these guards, construct and return the PathConfigSourceSubscription using the
verified configSource.getPathConfigSource(), watchService, callbacks, eventLoop,
meterRegistry, and meterIdPrefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java`:
- Around line 101-121: In startWatching(), the catch block currently closes
lifecycleObserver from the blocking executor and doesn't notify the subscription
callbacks; instead, schedule the failure handling onto the eventLoop: inside the
catch, capture the exception and call eventLoop.execute(() -> {
lifecycleObserver.close(); close0(null) or the appropriate cleanup; invoke the
subscription callbacks with the error (e.g., callbacks.onError /
callbacks.onSubscriptionFailed or the class's existing error-reporting callback)
so the failure is surfaced; ensure watchCancellable remains null. Reference:
startWatching, watchService.register, lifecycleObserver, eventLoop, close0,
watchCancellable, callbacks, parseAndPush.
- Around line 61-67: In create(), validate inputs before using them: check
configSource is non-null and configSource.hasPathConfigSource() is true before
calling configSource.getPathConfigSource(), and null-check callbacks and
eventLoop; if any validation fails, throw an appropriate exception (e.g.,
IllegalArgumentException or NullPointerException) with a clear message. After
these guards, construct and return the PathConfigSourceSubscription using the
verified configSource.getPathConfigSource(), watchService, callbacks, eventLoop,
meterRegistry, and meterIdPrefix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e60e99b-7932-40cc-9b1c-02146db71b35

📥 Commits

Reviewing files that changed from the base of the PR and between 03cdbb3 and 5816f14.

📒 Files selected for processing (1)
  • xds/src/main/java/com/linecorp/armeria/xds/PathSotwConfigSourceSubscriptionFactory.java

@jrhee17 jrhee17 marked this pull request as ready for review May 6, 2026 07:18
@jrhee17 jrhee17 requested review from ikhoon, minwoox and trustin as code owners May 6, 2026 07:18
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.

👍 👍


// custom_config_source is an Armeria extension (field 1000) that may not exist
// when a different proto artifact (e.g. java-control-plane api) is used at runtime.
static final boolean HAS_CUSTOM_CONFIG_SOURCE;
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.

private?


@Override
public void onDiscoveryResponse(DiscoveryResponse response) {
checkArgument(eventLoop.inEventLoop(), "eventLoop must be inEventLoop");
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.

checkState?

@jrhee17 jrhee17 merged commit af6b035 into line:main May 8, 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