Skip to content

Allow overriding the default ClientFactory via FlagsProvider#6671

Open
JAEKWANG97 wants to merge 3 commits intoline:mainfrom
JAEKWANG97:feature/override-default-client-factory-minimal
Open

Allow overriding the default ClientFactory via FlagsProvider#6671
JAEKWANG97 wants to merge 3 commits intoline:mainfrom
JAEKWANG97:feature/override-default-client-factory-minimal

Conversation

@JAEKWANG97
Copy link
Copy Markdown
Contributor

@JAEKWANG97 JAEKWANG97 commented Mar 17, 2026

Motivation:

The built-in default ClientFactory is currently fixed, so applications cannot customize default client entry points such as WebClient.of().

Rather than overriding the default ClientFactory instance itself, this change narrows the scope to customizing the built-in default client factories through FlagsProvider.

Modifications:

  • Add FlagsProvider.defaultClientFactoryConfigurator().
  • Add Flags.defaultClientFactoryConfigurator().
  • Add ClientFactoryConfigurator for customizing ClientFactoryBuilder.
  • Apply the configurator when creating DefaultClientFactory.DEFAULT and DefaultClientFactory.INSECURE.
  • Keep ClientFactory.ofDefault() returning DefaultClientFactory.DEFAULT.
  • Keep lifecycle behavior such as closeDefault(), direct close(), and shutdown hook ownership unchanged.
  • Add SPI coverage in FlagsProviderTest.

Result:

  • Users can customize the built-in default client factories via FlagsProvider.
  • Default client entry points that use ClientFactory.ofDefault() pick up the customized settings.
  • The canonical default factory identity and lifecycle remain unchanged.

Closes #6425.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 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

A new ClientFactoryConfigurator interface and corresponding SPI hook are introduced to enable customization of built-in default ClientFactory instances. The Flags system is extended with defaultClientFactoryConfigurator() methods, and DefaultClientFactory refactored to apply the configurator during singleton creation.

Changes

Cohort / File(s) Summary
ClientFactoryConfigurator Interface
core/src/main/java/com/linecorp/armeria/client/ClientFactoryConfigurator.java
New @FunctionalInterface providing a configure(ClientFactoryBuilder) method and static noop() factory for customizing built-in default client factories during creation.
Flags System Extensions
core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java, core/src/main/java/com/linecorp/armeria/common/Flags.java, core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
Added defaultClientFactoryConfigurator() SPI hook method to FlagsProvider, public accessor to Flags, and default no-op implementation in DefaultFlagsProvider. Includes API documentation on configurator constraints and behavior during default/insecure factory creation.
DefaultClientFactory Refactoring
core/src/main/java/com/linecorp/armeria/client/DefaultClientFactory.java
Extracted singleton initialization into newDefaultClientFactory(insecure, configurator) helper method; DEFAULT and INSECURE instances now apply the configurator from Flags.defaultClientFactoryConfigurator() before optional tlsNoVerify() configuration.
Integration Tests
it/flags-provider/src/test/java/com/linecorp/armeria/common/BaseFlagsProvider.java, it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java
Test implementation overrides defaultClientFactoryConfigurator() to customize connect timeout; new test cases verify configurator is applied to both default and insecure client factories via reflection-based assertions on channel options.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A factory now bends to our will!
With configurators, builders we fill,
Default clients dance to our tune,
Customization blooms, just in time, not too soon! 🌸

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR implements a configurator-based approach to allow overriding the default ClientFactory via FlagsProvider, partially addressing issue #6425. The implementation uses a ClientFactoryConfigurator SPI instead of direct Flags.defaultClientFactory() as described in #6425; clarify if this addresses the requirement or if additional changes are needed.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: allowing overridden default ClientFactory via FlagsProvider.
Out of Scope Changes check ✅ Passed All code changes relate to implementing the ClientFactoryConfigurator SPI and integrating it into Flags, DefaultFlagsProvider, and ClientFactory creation paths.
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the motivation, modifications, and expected results. It details the addition of FlagsProvider.defaultClientFactoryConfigurator() and associated infrastructure for customizing default ClientFactory instances.

✏️ 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java`:
- Around line 378-381: The current defaultClientFactory() creates a new
DefaultClientFactory each call causing the static DefaultClientFactory.DEFAULT
to never be closed; change DefaultFlagsProvider.defaultClientFactory() to return
the existing static instance (DefaultClientFactory.DEFAULT or
DefaultClientFactory.INSECURE as appropriate) instead of
ClientFactory.builder().build(), or alternatively ensure
DefaultClientFactory.DEFAULT is closed from ClientFactory.closeDefault();
specifically update the DefaultFlagsProvider.defaultClientFactory() method to
return the shared DefaultClientFactory.DEFAULT/INSECURE instances and remove
per-call builder() usage so the shutdown hook (ClientFactory.closeDefault())
will actually close the static DEFAULT.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2090aaec-6248-45f1-88ec-336f039e0060

📥 Commits

Reviewing files that changed from the base of the PR and between 1c00618 and b431fa6.

📒 Files selected for processing (7)
  • core/src/main/java/com/linecorp/armeria/client/ClientFactory.java
  • core/src/main/java/com/linecorp/armeria/client/DefaultClientFactory.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/common/Flags.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/BaseFlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java

@JAEKWANG97
Copy link
Copy Markdown
Contributor Author

JAEKWANG97 commented Mar 17, 2026

While working on this, I also explored a broader draft that touched follow-up lifecycle-related behaviors such as closeDefault(), direct close(), shutdown hook handling, and the interaction with insecure().

Since those expanded the scope significantly, I narrowed this PR to focus on the default override mechanism itself and am opening it as a draft first.

That broader exploration also suggested there may be room for structural cleanup around the ownership boundary between Flags and ClientFactory, especially if those lifecycle-related behaviors are revisited.

For reference, the earlier broader draft is here:
#6666

I'd appreciate feedback on whether those lifecycle-related behaviors should be handled in follow-up changes, and whether that kind of structural cleanup would be preferred before expanding the scope.

@ikhoon
Copy link
Copy Markdown
Contributor

ikhoon commented Mar 25, 2026

That broader exploration also suggested there may be room for structural cleanup around the ownership boundary between Flags and ClientFactory, especially if those lifecycle-related behaviors are revisited.

The overall changes look good. Could you explain in more detail what issues there are with the lifecycle of Flags and ClientFactory?

@jrhee17
Copy link
Copy Markdown
Contributor

jrhee17 commented Mar 27, 2026

I'm unsure if Flags#clientFactory won't have race conditions with existing flags.
e.g. Assume defaultResponseTimeoutMillis, defaultClientFactory is set via flags. Will Flags.defaultResponseTimeoutMillis be applied to defaultClientFactory? I'm unsure if this will be set properly - this may also depend on the location of the property within the Flags java file as we rely on java initialization logic to initialize flags at the moment.

Perhaps a saner approach may be to define a defaultClientFactoryProvider which is a Supplier type to Flags. At the very least, this would ensure decoupling between Flags initialization and default ClientFactory initialization.

Long term, we'll probably need a separate mechanism for flag loading that doesn't rely on class initialization ordering (e.g. dependency injection)

@JAEKWANG97
Copy link
Copy Markdown
Contributor Author

JAEKWANG97 commented Mar 29, 2026

The overall changes look good. Could you explain in more detail what issues there are with the lifecycle of Flags and ClientFactory?

@ikhoon What I had in mind there was that the ownership of the default ClientFactory is now split across Flags, ClientFactory, and DefaultClientFactory.

Concretely:

  • ClientFactory.ofDefault() now delegates to Flags.defaultClientFactory()
  • Flags.defaultClientFactory() uses a lazy holder that resolves FlagsProvider::defaultClientFactory
  • if no user provider returns a value, DefaultFlagsProvider.defaultClientFactory() currently falls back to ClientFactory.builder().build()

The main follow-up issues I ran into in the draft PR I closed were:

  1. Fallback identity
  • When no FlagsProvider returns a value, the fallback creates a new ClientFactory via ClientFactory.builder().build(), so ClientFactory.ofDefault() and DefaultClientFactory.DEFAULT can end up referring to different objects.
  • So the effective default can diverge from the built-in default identity, which makes the ownership of the default lifecycle less clear.
  1. Split lifecycle ownership
  • ClientFactory exposes lifecycle entry points such as ofDefault() and closeDefault(),
  • while built-in singleton semantics, direct-close protection, and shutdown-hook registration still live in DefaultClientFactory.
  • Once the effective default is no longer guaranteed to be the built-in one, that ownership boundary becomes less obvious.
  1. Shutdown hook ownership
  • The shutdown hook is currently registered from DefaultClientFactory static initialization.
  • If a custom FlagsProvider.defaultClientFactory() supplies the effective default and DefaultClientFactory is never otherwise initialized, the hook registration path may not be exercised even though the effective default has been overridden.
  1. Direct-close / lazy-init side effects
  • DefaultClientFactory.checkDefault() now checks ClientFactory.ofDefault() as well.
  • A close-related code path should not be responsible for resolving or initializing the effective default. That inversion itself is the problem.

So the concern I had was less that any one class is individually wrong, and more that once the effective default is resolved through Flags, the responsibilities around lookup, fallback identity, close semantics, and shutdown ownership are spread across multiple places.

That was the context behind my comment about possible structural cleanup between Flags and ClientFactory.

@JAEKWANG97
Copy link
Copy Markdown
Contributor Author

JAEKWANG97 commented Mar 30, 2026

@jrhee17 Agreed. The root issue is that Flags not only does a lookup but also owns factory construction in the fallback path, and ClientFactory directly references DefaultClientFactory in multiple places.

Rather than the Supplier approach, I think the right fix is to make Flags a pure lookup layer, and introduce an abstract class between ClientFactory and DefaultClientFactory to own the effective default and lifecycle logic. This would decouple Flags initialization from factory construction entirely.

What do you think?

@jrhee17
Copy link
Copy Markdown
Contributor

jrhee17 commented Apr 1, 2026

I think I need to understand exactly what the issue is:

So you are saying even if Supplier<ClientFactory> defaultClientFactory is provided via Flags and ClientFactory.DEFAULT = Flags.defaultClientFactory.get(), there is still a initialization error.
Am I understanding correctly?

@JAEKWANG97
Copy link
Copy Markdown
Contributor Author

@jrhee17 I think I focused more on the lifecycle/ownership concern than on your initialization-order point, so let me address that first.

I have not identified a concrete initialization failure in this draft so far. With DefaultClientFactoryHolder, Flags.defaultClientFactory() is resolved only after the existing static fields in Flags have been initialized.

That said, I agree with the broader concern that the current design still depends on Java class-initialization ordering, since Flags is still part of the fallback path that constructs the default ClientFactory.

So a Supplier<ClientFactory> seems like a cleaner direction for the initialization concern.

Would it make sense to proceed in that direction?

@jrhee17
Copy link
Copy Markdown
Contributor

jrhee17 commented Apr 3, 2026

ClientFactory.ofDefault() and DefaultClientFactory.DEFAULT can end up referring to different objects.

I think this is an issue though - they should refer to the same object.

I don't think changing the lifecycle of flags is trivial, we may want to design this separately if needed.
Hence, for this issue, what do you think of first trying DefaultClientFactory DEFAULT = Flags.defaultClientFactory()?

If the CI fails, we can analyze together and talk about whether/how this can be fixed.

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/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java (1)

225-229: Minor: setAccessible(true) is unnecessary for public methods.

The setAccessible(true) call is redundant when invoking public methods via getMethod(). However, this is a harmless nit.

♻️ Optional simplification
     private static Object invokePublic(Object target, String methodName) throws Throwable {
         final Method method = target.getClass().getMethod(methodName);
-        method.setAccessible(true);
         return method.invoke(target);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java`
around lines 225 - 229, The helper method invokePublic unnecessarily calls
method.setAccessible(true) for a public method retrieved via getMethod(); remove
the setAccessible(true) invocation from invokePublic (the Method method
variable) so the method simply obtains the Method via
target.getClass().getMethod(methodName) and invokes it, keeping behavior
unchanged.
🤖 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/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java`:
- Around line 225-229: The helper method invokePublic unnecessarily calls
method.setAccessible(true) for a public method retrieved via getMethod(); remove
the setAccessible(true) invocation from invokePublic (the Method method
variable) so the method simply obtains the Method via
target.getClass().getMethod(methodName) and invokes it, keeping behavior
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 556444f3-960b-4e2c-964f-597d629059e7

📥 Commits

Reviewing files that changed from the base of the PR and between b431fa6 and c3b8765.

📒 Files selected for processing (8)
  • core/src/main/java/com/linecorp/armeria/client/ClientFactory.java
  • core/src/main/java/com/linecorp/armeria/client/DefaultClientFactory.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/common/Flags.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/BaseFlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/client/DefaultClientFactory.java
  • core/src/main/java/com/linecorp/armeria/client/ClientFactory.java

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 4, 2026

Codecov Report

❌ Patch coverage is 70.58824% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.95%. Comparing base (8150425) to head (e6b1b9e).
⚠️ Report is 395 commits behind head on main.

Files with missing lines Patch % Lines
...ava/com/linecorp/armeria/client/ClientFactory.java 60.00% 1 Missing and 1 partial ⚠️
...c/main/java/com/linecorp/armeria/common/Flags.java 77.77% 2 Missing ⚠️
.../linecorp/armeria/client/DefaultClientFactory.java 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6671      +/-   ##
============================================
- Coverage     74.46%   73.95%   -0.51%     
- Complexity    22234    24044    +1810     
============================================
  Files          1963     2171     +208     
  Lines         82437    90196    +7759     
  Branches      10764    11828    +1064     
============================================
+ Hits          61385    66707    +5322     
- Misses        15918    17893    +1975     
- Partials       5134     5596     +462     

☔ 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.

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/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java (1)

228-246: Consider adding cleanup protection for test robustness.

Unlike overrideDefaultClientFactory(), this test lacks a try-finally to ensure closeDefault() is called if an assertion fails before line 243. This could leave resources open in failure scenarios, potentially affecting subsequent tests.

♻️ Suggested fix to add try-finally for cleanup
     `@Test`
     `@SetSystemProperty`(key = "com.linecorp.armeria.test.decoratingDefaultClientFactory", value = "true")
     void closeDefaultClosesDecoratingClientFactory() throws Throwable {
         final Method defaultClientFactoryMethod = flags.getDeclaredMethod("defaultClientFactory");
         final Object clientFactory = defaultClientFactoryMethod.invoke(null);
         final Class<?> clientFactoryClass = defaultClientFactoryMethod.getReturnType();

         assertThat(clientFactory.getClass().getSimpleName()).isEqualTo("TestDecoratingClientFactory");
         final CompletableFuture<?> whenClosed =
                 (CompletableFuture<?>) invokePublic(clientFactory, "whenClosed");

-        assertThat(invokePublic(clientFactory, "isClosed")).isEqualTo(false);
-        invokePublic(clientFactory, "close");
-        assertThat(invokePublic(clientFactory, "isClosed")).isEqualTo(false);
-
-        invokeDeclaredStatic(clientFactoryClass, "closeDefault");
-        whenClosed.join();
-        assertThat(invokePublic(clientFactory, "isClosed")).isEqualTo(true);
+        try {
+            assertThat(invokePublic(clientFactory, "isClosed")).isEqualTo(false);
+            invokePublic(clientFactory, "close");
+            assertThat(invokePublic(clientFactory, "isClosed")).isEqualTo(false);
+
+            invokeDeclaredStatic(clientFactoryClass, "closeDefault");
+            whenClosed.join();
+            assertThat(invokePublic(clientFactory, "isClosed")).isEqualTo(true);
+        } finally {
+            if (!whenClosed.isDone()) {
+                invokeDeclaredStatic(clientFactoryClass, "closeDefault");
+                whenClosed.join();
+            }
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java`
around lines 228 - 246, Wrap the body of
closeDefaultClosesDecoratingClientFactory (the code that invokes
defaultClientFactory, checks isClosed, calls close, and asserts) in a
try-finally so that invokeDeclaredStatic(clientFactoryClass, "closeDefault") is
always executed in the finally block (and ensure whenClosed.join() is called to
wait for closure), preserving any thrown assertion exception (i.e., do not
swallow exceptions in the finally).
🤖 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/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java`:
- Around line 228-246: Wrap the body of
closeDefaultClosesDecoratingClientFactory (the code that invokes
defaultClientFactory, checks isClosed, calls close, and asserts) in a
try-finally so that invokeDeclaredStatic(clientFactoryClass, "closeDefault") is
always executed in the finally block (and ensure whenClosed.join() is called to
wait for closure), preserving any thrown assertion exception (i.e., do not
swallow exceptions in the finally).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c250394a-1edd-4751-95de-c285d5d2a3e8

📥 Commits

Reviewing files that changed from the base of the PR and between 41c8537 and e6b1b9e.

📒 Files selected for processing (3)
  • core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/BaseFlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/test/java/com/linecorp/armeria/client/ClientFactoryBuilderTest.java

@ikhoon
Copy link
Copy Markdown
Contributor

ikhoon commented Apr 6, 2026

what do you think of first trying DefaultClientFactory DEFAULT = Flags.defaultClientFactory()?

I thought DefaultClientFactory.DEFAULT should be removed and Flags.defaultClientFactory() replaces it.

I'm not sure whether it is technically impossible to handle the side effects caused by the initialization order. If you are concerned about it, what do you think about returning ClientFactoryConfigurer from Flags instead of an instance? The Flags.defaultClientFactoryConfigurer() could be applied to DefaultClientFactory.DEFAULT and DefaultClientFactory.INSECURE, which could be another way to solve the original issue.

@jrhee17
Copy link
Copy Markdown
Contributor

jrhee17 commented Apr 7, 2026

I thought DefaultClientFactory.DEFAULT should be removed and Flags.defaultClientFactory() replaces it.

I see - my original preference was that Flags values are not eagerly fetched within Flags as we've had plenty of issues regarding it.

Since it seems like you have a idea of how this PR should move forward, I think it's probably better I revisit later when the PR is in a more reviewable state.

@JAEKWANG97
Copy link
Copy Markdown
Contributor Author

I think the configurer/customizer direction you suggested seems simpler here.

It keeps DefaultClientFactory.DEFAULT / INSECURE as the canonical defaults and avoids having Flags resolve a ClientFactory instance directly.

The tradeoff is narrowing the SPI from "full override" to "customizing the built-in default". If that still matches the intended scope of #6425, I’ll rework the PR in that direction unless there are objections.

@ikhoon
Copy link
Copy Markdown
Contributor

ikhoon commented Apr 15, 2026

The tradeoff is narrowing the SPI from "full override" to "customizing the built-in default". If that still matches the intended scope of #6425, I’ll rework the PR in that direction unless there are objections.

👍 I think it would be fine to process that way.

@JAEKWANG97 JAEKWANG97 force-pushed the feature/override-default-client-factory-minimal branch from e6b1b9e to 55fbebf Compare April 19, 2026 12:14
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: 1

🧹 Nitpick comments (1)
core/src/main/java/com/linecorp/armeria/common/Flags.java (1)

1638-1652: Memoize the resolved configurator to keep the flag stable.

Unlike most Flags accessors, this re-runs provider resolution on every call. If a provider returns a stateful or newly-created configurator, ofDefault() and insecure() can be built with different effective configuration depending on initialization timing.

♻️ Proposed lazy memoization
     `@UnstableApi`
     public static ClientFactoryConfigurator defaultClientFactoryConfigurator() {
+        return DefaultClientFactoryConfiguratorHolder.INSTANCE;
+    }
+
+    private static final class DefaultClientFactoryConfiguratorHolder {
+        static final ClientFactoryConfigurator INSTANCE = defaultClientFactoryConfigurator0();
+    }
+
+    private static ClientFactoryConfigurator defaultClientFactoryConfigurator0() {
         final ClientFactoryConfigurator configurator =
                 getValue(FlagsProvider::defaultClientFactoryConfigurator,
                          "defaultClientFactoryConfigurator");
         return configurator != null ? configurator : ClientFactoryConfigurator.noop();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/main/java/com/linecorp/armeria/common/Flags.java` around lines 1638
- 1652, The getter defaultClientFactoryConfigurator() currently resolves the
provider on every call causing instability; change it to lazily memoize the
resolved ClientFactoryConfigurator in a private static volatile field
(initialized once on first call) and return the cached instance thereafter
(falling back to ClientFactoryConfigurator.noop() if null), ensuring thread-safe
publication so ofDefault() and insecure() see a stable configurator; locate uses
of getValue(FlagsProvider::defaultClientFactoryConfigurator,
"defaultClientFactoryConfigurator") inside defaultClientFactoryConfigurator() to
replace with the memoized lookup and assignment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/src/main/java/com/linecorp/armeria/client/ClientFactoryConfigurator.java`:
- Around line 1-15: Replace the current file header in
ClientFactoryConfigurator.java with the repository's standard copyright header
(remove the extra spaces after the '*' characters and restore the canonical
template form including the unchanged $today.year placeholder), ensuring the
header exactly matches other source files in the project so formatting and the
year placeholder are consistent.

---

Nitpick comments:
In `@core/src/main/java/com/linecorp/armeria/common/Flags.java`:
- Around line 1638-1652: The getter defaultClientFactoryConfigurator() currently
resolves the provider on every call causing instability; change it to lazily
memoize the resolved ClientFactoryConfigurator in a private static volatile
field (initialized once on first call) and return the cached instance thereafter
(falling back to ClientFactoryConfigurator.noop() if null), ensuring thread-safe
publication so ofDefault() and insecure() see a stable configurator; locate uses
of getValue(FlagsProvider::defaultClientFactoryConfigurator,
"defaultClientFactoryConfigurator") inside defaultClientFactoryConfigurator() to
replace with the memoized lookup and assignment.
🪄 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: 55b328ab-7ae9-4f83-b4e1-22c88291e259

📥 Commits

Reviewing files that changed from the base of the PR and between e6b1b9e and 55fbebf.

📒 Files selected for processing (7)
  • core/src/main/java/com/linecorp/armeria/client/ClientFactoryConfigurator.java
  • core/src/main/java/com/linecorp/armeria/client/DefaultClientFactory.java
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/common/Flags.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/BaseFlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/FlagsProviderTest.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java
  • core/src/main/java/com/linecorp/armeria/client/DefaultClientFactory.java
  • core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java
  • it/flags-provider/src/test/java/com/linecorp/armeria/common/BaseFlagsProvider.java

@JAEKWANG97
Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion! I reworked the PR in the configurator direction. :)

The current version keeps DefaultClientFactory.DEFAULT / INSECURE as the canonical defaults and applies customization through a configurator at creation time.

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.

Overall, looks good.

Comment thread core/src/main/java/com/linecorp/armeria/client/ClientFactoryConfigurator.java Outdated
@JAEKWANG97 JAEKWANG97 marked this pull request as ready for review April 25, 2026 13:38
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.

Thanks, @JAEKWANG97! 👍🚀

@ikhoon ikhoon added this to the 1.39.0 milestone Apr 27, 2026
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

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

👍 👍

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.

Override the default ClientFactory

3 participants