-
Notifications
You must be signed in to change notification settings - Fork 972
GH-6372: Expose client request metrics. #6502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
9062ebf to
54dc0e3
Compare
| /** | ||
| * Collects simple client-side metrics such as: | ||
| * <ul> | ||
| * <li>The number of pending requests per {@link SessionProtocol}</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) If the objective is to determine the optimal number of event loops depending on pending requests for each endpoint group/protocol, would it be enough to check the duration instead?
final EndpointGroup endpointGroup = ctx.endpointGroup();
ctx.log().whenComplete().thenAccept(log -> {
final long pendingDuration = log.connectionTimings().pendingAcquisitionDurationNanos();
final SessionProtocol sessionProtocol = log.sessionProtocol();
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrhee17 thanks for your comments!
Your comment also make sense to me!
However, IMHO, it may not be enough or enough. 😄
So I would like to vote for counting the number of pending requests.
The pendingAcquisitionDurationNanos seems to be set after request get channel.
In that case, pendingAcquisitionDurationNanos can be -1 or large value.
If the acquisition of a channel is delayed due to a sudden surge in requests, the value of pendingAcquisitionDurationNanos will continuously remain -1. Or, if the channel is acquired after a very long time, the value of pendingAcquisitionDurationNanos will change from -1 to a very large value. Therefore, the responsiveness of the operation that increases the EventLoop is likely to be degraded.
Additionally, when the Channel is Busy, we need to consider two states:
- The value is -1.
- The value is an enormously large number.
Consequently, the ClientMetrics object would need to contain code to account for this, which potentially implies that ClientMetrics is tightly coupled with the logic of ConnectionTimings.
For these reasons, I vote for counting the number of pending requests.
What do you think?
Also, @ikhoon , please give your opinion when you have time!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using pending duration is also a good idea. However, adding ClientMetrics would be worthwhile because:
ClientMetricsitself seems to provide useful information at theClientFactorylevel.- Using
ConnectionTimingsinsidemaxNumEventLoopsFunctionmethod doesn't look straightforward as it may require additional processing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see - I don't think I can meaningfully review this PR at this point since I'm not sure from the issue/PR description how ClientMetrics is expected to be used.
Do you envision ClientMetrics as a general metric collector that collects metrics on the overall Client (or ClientFactory)? What would be the relationship between this metric collector and other metrics that are already being exported?
Otherwise, it might help if the class name were more specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most APIs that are exposed at the request level will primarily be used for logging or collected by metric collectors such as Prometheus. Since they contain per-request details, it does not seem easy to use them directly to control the server’s runtime behavior.
ServerMetrics was first exposed as a Java API because it was difficult to obtain information about in-flight requests at runtime when implementing a custom graceful shutdown. Similarly, ClientMetrics will expose the information needed to control runtime behavior at the client or client-factory level. I expect these values to be exposed mostly as simple counters rather than histograms.
Otherwise, it might help if the class name were more specific.
I’m open to changing it if you think there’s a better name.
| // EndpointGroup does not override equals() and hashCode(). | ||
| // Call sites must use the same 'EndpointGroup' instance when invoking | ||
| // 'incrementActiveRequest(...)' and 'decrementActiveRequest'. | ||
| private final ConcurrentMap<EndpointGroup, LongAdder> activeRequestsPerEndpointGroup; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Couldn't we use Endpoint instead of EndpointGroup as the key to collect metrics?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ikhoon nim, thanks for your comments.
Addressed it!
|
After discussing with other team members, we came to the conclusion that exposing client metrics in an event-listener form would be a better approach. public interface ClientRequestLifecycleListener {
/**
* Called when a request is created and scheduled but not yet executed.
*/
void onRequestPending(ClientRequestContext ctx);
/**
* Called when the client begins execution (connection acquired, headers sent).
*/
void onRequestStart(ClientRequestContext ctx);
/**
* Called when the request is fully sent.
*/
void onRequestSendComplete(ClientRequestContext ctx);
/**
* Called when the first response headers are received.
*/
void onResponseHeaders(ClientRequestContext ctx, ResponseHeaders headers);
/**
* Called when the full response body is received successfully.
*/
void onResponseComplete(ClientRequestContext ctx);
/**
* Called when a request is completed with either success or failure.
*/
void onRequestComplete(ClientRequestContext ctx, @Nullable Throwable cause);
}I’m thinking of allowing this listener to be configured at the ClientRequestLifecycleListener.counting(); |
|
@ikhoon |
WalkthroughAdds a ClientRequestLifecycleListener API and implementations (noop, counting), a RequestLog bridge and listener, threadsafe client request metrics, wires the listener through ClientFactoryBuilder → HttpClientFactory, invokes it in HttpClientDelegate, adds integration tests, and inserts a debug print in DefaultRequestLog. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant ClientFactoryBuilder
participant HttpClientFactory
participant HttpClientDelegate
participant RequestLog
participant ClientRequestLifecycleListener
Client->>ClientFactoryBuilder: clientRequestLifecycleListener(listener)
ClientFactoryBuilder->>HttpClientFactory: build(..., listener)
Client->>HttpClientDelegate: execute(request, ctx)
HttpClientDelegate->>ClientRequestLifecycleListener: onRequestPending(ctx)
HttpClientDelegate->>RequestLog: addListener(listener.asRequestLogListener())
Note right of RequestLog: RequestLog emits properties over time
RequestLog->>ClientRequestLifecycleListener: onRequestStart(ctx)
RequestLog->>ClientRequestLifecycleListener: onRequestSendComplete(ctx)
RequestLog->>ClientRequestLifecycleListener: onResponseHeaders(ctx, headers)
RequestLog->>ClientRequestLifecycleListener: onResponseComplete(ctx)
RequestLog->>ClientRequestLifecycleListener: onRequestComplete(ctx, cause)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 (3)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java (1)
88-94: Fix method naming for consistency.The method name
pendingRequest()should bependingRequests()(plural) to match the naming pattern ofactiveRequests()and align with conventions for aggregate query methods.Apply this diff:
- public long pendingRequest() { + public long pendingRequests() { long sum = 0L; for (LongAdder adder : pendingRequestsPerProtocol.values()) { sum += adder.sum(); } return sum; }core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java (1)
345-346: Fix formatting for consistency.The getter method formatting doesn't match the style of other methods in this file. It should be properly formatted with the return statement on a new line.
Apply this diff:
- - ClientRequestLifecycleListener clientRequestLifecycleListener() {return clientRequestLifecycleListener;} + + ClientRequestLifecycleListener clientRequestLifecycleListener() { + return clientRequestLifecycleListener; + }core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java (1)
8-45: LGTM with optional documentation suggestions.The interface design is clean and follows good practices with the factory method pattern. The lifecycle callbacks provide comprehensive coverage of the request lifecycle.
Consider enhancing the interface-level javadoc to document:
- The order in which callbacks are invoked
- Threading guarantees (which thread calls each method)
- Exception handling behavior (what happens if a callback throws)
- Whether callbacks are optional or all must be implemented
Example addition to interface javadoc:
/** * Listener interface for client request lifecycle events. * * <p>Callbacks are invoked in the following order: * <ol> * <li>{@link #onRequestPending(ClientRequestContext)} - on the caller thread</li> * <li>{@link #onRequestStart(ClientRequestContext)} - on the event loop</li> * <li>{@link #onRequestSendComplete(ClientRequestContext)} - on the event loop</li> * <li>{@link #onResponseHeaders(ClientRequestContext, ResponseHeaders)} - on the event loop</li> * <li>{@link #onResponseComplete(ClientRequestContext)} - on the event loop</li> * <li>{@link #onRequestComplete(ClientRequestContext, Throwable)} - on the event loop</li> * </ol> * * <p>Implementations should not throw exceptions. If an exception is thrown, * it will be logged but will not affect request processing. */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java(4 hunks)core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java(4 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java (1)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java (1)
ClientRequestMetrics(14-96)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: Kubernetes Chaos test
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: site
- GitHub Check: lint
- GitHub Check: flaky-tests
- GitHub Check: Summary
🔇 Additional comments (1)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java (1)
139-140: Verify default metrics collection behavior is intentional.Every
ClientFactorywill have metrics tracking enabled by default via the automatically instantiatedClientRequestMetrics. While the overhead is likely minimal due toLongAdder, you may want to verify:
- Whether this default behavior is documented for users
- Whether a no-op implementation should be available for users who want to opt out of metrics collection
Consider documenting this default behavior in the class-level javadoc or providing a static factory method like
ClientRequestLifecycleListener.noop()for users who want to disable metrics tracking.
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java (2)
11-39: Consider enhancing lifecycle method documentation.The current documentation is concise but could benefit from additional details:
- Expected callback ordering (e.g.,
onRequestPending→onRequestStart→ ... →onRequestComplete)- Relationship between
onResponseComplete(success only) andonRequestComplete(always called, success or failure)- Threading guarantees for implementations
- Exception handling behavior
These clarifications would help implementers understand the contract more clearly.
49-82: Add private constructor to prevent external instantiation.The singleton pattern should include an explicit private constructor to prevent external instantiation of
NoopClientRequestLifecycleListener.Apply this diff to add a private constructor:
class NoopClientRequestLifecycleListener implements ClientRequestLifecycleListener { private static final NoopClientRequestLifecycleListener INSTANCE = new NoopClientRequestLifecycleListener(); + + private NoopClientRequestLifecycleListener() { + } @Override public void onRequestPending(ClientRequestContext ctx) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java(4 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: flaky-tests
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: site
- GitHub Check: lint
- GitHub Check: Kubernetes Chaos test
- GitHub Check: Summary
🔇 Additional comments (2)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java (2)
8-9: LGTM!The
@UnstableApiannotation is appropriate for this new lifecycle listener interface.
41-43: Verify ClientRequestMetrics exists and implements this interface.The
counting()method returnsClientRequestMetrics, which according to the PR description should implementClientRequestLifecycleListener. Ensure this class exists and properly implements all lifecycle methods.#!/bin/bash # Verify ClientRequestMetrics exists and implements ClientRequestLifecycleListener # Find the ClientRequestMetrics class file fd -t f "ClientRequestMetrics.java" # Check if it implements ClientRequestLifecycleListener ast-grep --pattern $'class ClientRequestMetrics implements $$$ClientRequestLifecycleListener$$$ { $$$ }'
Motivation: A single `RequestLogProperty` can be received through `RequestLog.whenAvailable()`, but it is not straightforward to receive all events published by `RequestLog` using `.whenAvailable()`. To address the limitation, I propose introducing an interface that listens to all events by adding it to `RequestLog`. This will also simplify the integration of other implementations used for collecting metrics. Related: #6502 Modifications: - Introduced `RequestLogListener` API that can be attached to `RequestLog`. - `RequestLogAccess.addListener()` API was added and `DefaultRequestLog` implemented it. - The listener will be notified whenever a new property is set to `RequestLog`. If some properties have already been set, it will notified of them immediately. - Add `REQUEST_COMPLETE`, `RESPONSE_COMPLETE` and `ALL_COMPLETE` to `RequestLogProperty`. - Previously, there were APIs such as `whenRequestComplete()` and `whenComplete()` that computed and signaled request or response completion and but no explicit properties exist for them. `RequestLogProperty` should represent all states in `RequestLogListener`, I added the new completion properties. - Simplified child log propagation in `DefaultRequestLog` and the `Observation{Service,Client}` implementations by using `RequestLogListener`. Result: You can now use `RequestLogListener` to observe all `RequestLog` events.
|
You may want to use |
af7797a to
606bf40
Compare
There was a problem hiding this 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)
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java (1)
161-168: Clarify test assertion comment.The logic is correct, but the comment "it will never reach here" is slightly misleading. The
await()call does execute but times out as expected (since the endpoint is never invoked due to early cancellation). Consider clarifying:try { - // because of early cancel, - // it will never reach here. + // Because of early cancel, the endpoint should never be invoked, + // so the latch will timeout. SCENARIO2_NEVER_CALLED.await(1, TimeUnit.SECONDS); } catch (Exception e) { assertInstanceOf(InterruptedException.class, e);
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java(4 hunks)core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java(6 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java(1 hunks)core/src/main/java/com/linecorp/armeria/common/logging/DefaultRequestLog.java(1 hunks)core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
- core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
- core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/main/java/com/linecorp/armeria/common/logging/DefaultRequestLog.javacore/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.javacore/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.javacore/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
🧬 Code graph analysis (1)
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java (3)
core/src/main/java/com/linecorp/armeria/client/UnprocessedRequestException.java (1)
UnprocessedRequestException(32-70)sangria/sangria_2.13/src/main/scala/com/linecorp/armeria/server/sangria/SangriaGraphqlService.scala (1)
builder(156-161)sangria/sangria_2.13/src/main/scala/com/linecorp/armeria/server/sangria/SangriaGraphqlServiceBuilder.scala (1)
build(146-161)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: flaky-tests
- GitHub Check: site
- GitHub Check: lint
- GitHub Check: Kubernetes Chaos test
- GitHub Check: Summary
🔇 Additional comments (5)
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java (1)
102-106: LGTM! Well-positioned lifecycle integration.The lifecycle listener hooks are correctly placed:
onRequestPendingis invoked early, before connection acquisition- The
RequestLogListenerbridge is attached to capture subsequent lifecycle eventsThis implementation aligns with the event-listener approach discussed in the PR comments.
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java (2)
94-134: LGTM! Comprehensive test coverage.The test properly validates the complete lifecycle of concurrent requests, including:
- Pending state before connection acquisition
- Active state during request processing
- Proper cleanup after completion
The use of
CountDownLatchfor synchronization is appropriate.
172-201: LGTM! Proper failure scenario coverage.The test correctly validates metrics behavior during connection failures, ensuring:
- Pending requests are tracked during the failed connection attempt
- Proper exception handling (
UnprocessedRequestException)- Complete cleanup of metrics after failure
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java (2)
56-72: LGTM! Correct event triggers for metrics.The choice to use
REQUEST_FIRST_BYTES_TRANSFERRED_TIMEas the trigger for transitioning from pending to active is correct. This accurately reflects when a request starts sending data (has acquired a connection) rather than just being scheduled.
95-196: LGTM! Thread-safe and defensive implementation.The metrics tracking implementation is well-designed:
- Uses
AtomicBooleanflags inStateto prevent double-counting- Thread-safe data structures (
LongAdder,ConcurrentHashMap,EnumMap)- Defensive null checks for
endpoint(which can be null during early failures)- Clean separation between pending (per-protocol) and active (per-endpoint) metrics
The accessor methods correctly aggregate counters across all tracked entities.
| * completed. Tracked per {@link Endpoint}.</li> | ||
| * </ul> | ||
| */ | ||
| public class ClientRequestMetrics implements ClientRequestLifecycleListener { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add @UnstableApi annotation to the class.
Per the coding guidelines, all newly added public classes must have the @UnstableApi annotation unless they are in .internal or .testing packages, or in test source sets. This class is public and in the main source set, so it requires the annotation.
As per coding guidelines, apply this diff:
+import com.linecorp.armeria.common.annotation.UnstableApi;
+
/**
* A {@link ClientRequestLifecycleListener} that collects the number of pending and active requests.
*
* <p>This class tracks:
* <ul>
* <li><b>Pending requests:</b> Requests that have been created but have not yet started execution
* (e.g., waiting for a connection). Tracked per {@link SessionProtocol}.</li>
* <li><b>Active requests:</b> Requests that have started execution (headers sent) but have not yet
* completed. Tracked per {@link Endpoint}.</li>
* </ul>
*/
+@UnstableApi
public class ClientRequestMetrics implements ClientRequestLifecycleListener {🤖 Prompt for AI Agents
In
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
around line 45, the new public class lacks the required @UnstableApi annotation;
add the import for com.linecorp.armeria.common.annotation.UnstableApi (or adjust
existing imports) and annotate the class with @UnstableApi directly above the
class declaration so the public class complies with the coding guideline for
newly added public API.
core/src/main/java/com/linecorp/armeria/common/logging/DefaultRequestLog.java
Outdated
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6502 +/- ##
============================================
- Coverage 74.46% 74.25% -0.22%
- Complexity 22234 23573 +1339
============================================
Files 1963 2120 +157
Lines 82437 88359 +5922
Branches 10764 11586 +822
============================================
+ Hits 61385 65608 +4223
- Misses 15918 17209 +1291
- Partials 5134 5542 +408 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this 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
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: lint
- GitHub Check: site
- GitHub Check: Kubernetes Chaos test
- GitHub Check: flaky-tests
- GitHub Check: Summary
🔇 Additional comments (5)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java (5)
47-54: LGTM: Well-chosen data structures for concurrent metrics.The combination of
ConcurrentHashMapfor dynamic endpoints,EnumMapfor fixed protocols, andLongAdderfor high-contention counters is appropriate and efficient for thread-safe metric tracking.
56-72: LGTM: Clean integration with RequestLog system.The bridge listener correctly maps log properties to lifecycle methods. The idempotent design (via
AtomicBooleanguards in the lifecycle methods) ensures correctness even if events are delivered multiple times.
77-81: LGTM: Proper initialization of protocol metrics.Pre-populating the
EnumMapwith allSessionProtocolvalues ensures consistent behavior and avoids runtime null checks.
83-151: LGTM: Lifecycle methods correctly handle all request states.The implementation correctly tracks pending→active→complete transitions with proper edge-case handling:
AtomicBooleanguards prevent double-counting.- Early termination (cancelled before start) is handled correctly in
onRequestComplete.- Null-safety checks are defensive and appropriate.
198-212: LGTM: Correct per-context state management.The lazy initialization pattern and use of context attributes ensures each request has its own isolated
Stateinstance, preventing cross-request interference.
| public long pendingRequest() { | ||
| long sum = 0L; | ||
| for (LongAdder adder : pendingRequestsPerProtocol.values()) { | ||
| sum += adder.sum(); | ||
| } | ||
| return sum; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix method naming: use plural form pendingRequests().
The method name pendingRequest() is inconsistent with:
- The naming convention used in other methods (
activeRequests(),pendingRequestsPerProtocol()). - The PR objectives which explicitly specify
pendingRequests()(plural).
Apply this diff to fix the naming:
/**
* Returns the total number of pending requests across all protocols.
*/
- public long pendingRequest() {
+ public long pendingRequests() {
long sum = 0L;
for (LongAdder adder : pendingRequestsPerProtocol.values()) {
sum += adder.sum();
}
return sum;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public long pendingRequest() { | |
| long sum = 0L; | |
| for (LongAdder adder : pendingRequestsPerProtocol.values()) { | |
| sum += adder.sum(); | |
| } | |
| return sum; | |
| } | |
| /** | |
| * Returns the total number of pending requests across all protocols. | |
| */ | |
| public long pendingRequests() { | |
| long sum = 0L; | |
| for (LongAdder adder : pendingRequestsPerProtocol.values()) { | |
| sum += adder.sum(); | |
| } | |
| return sum; | |
| } |
🤖 Prompt for AI Agents
In
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
around lines 190 to 196, rename the method from pendingRequest() to
pendingRequests() to match existing naming conventions and PR requirements;
update the method signature and any internal references or callers to use
pendingRequests(), keeping the implementation identical (summing
pendingRequestsPerProtocol values), and ensure visibility and javadoc (if
present) are updated to reflect the new plural name.
| * defined in this interface. The returned listener is registered to the {@link ClientRequestContext} | ||
| * automatically when the request starts. | ||
| */ | ||
| RequestLogListener asRequestLogListener(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove this from public API but convert this to RequestLogListener internally? Users are only interested in implementing the callbacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Someone might want to define and use their own RequestLogListener.
For now, I revised it in a way that provides a default method so users don’t have to worry about it as much.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we need such a extension. What do you think of considering it later if necessary?
| /** | ||
| * A {@link ClientRequestLifecycleListener} that does nothing. | ||
| */ | ||
| class NoopClientRequestLifecycleListener implements ClientRequestLifecycleListener { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this internal implementation to the top level class? NoopClientRequestLifecycleListener will be exposed to users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed it!
| final Endpoint endpoint = ctx.endpoint(); | ||
| if (endpoint != null) { | ||
| activeRequestsPerEndpoint | ||
| .computeIfAbsent(endpoint, e -> new LongAdder()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to remove the cached Endpoint from activeRequestsPerEndpoint if the Endpoint is no longer used. Otherwise, it could be a memory leak.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, Addressed It.
FYI, I replace computeIfAbsent(...) to compute(...) to ensure atomic behavior during removing, adding adder.
There was a problem hiding this 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
♻️ Duplicate comments (2)
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java (2)
44-44: Add @UnstableApi annotation to the class.This issue has already been flagged in previous review comments.
173-179: Fix method naming: use plural formpendingRequests().This issue has already been flagged in previous review comments.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.javacore/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.javacore/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Summary
🔇 Additional comments (3)
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java (1)
46-72: LGTM!The event-to-lifecycle mapping logic is correct. The defensive type check ensures the listener only processes client requests, and each property is correctly mapped to its corresponding lifecycle callback.
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java (1)
114-139: Memory management correctly implemented.The logic properly addresses the memory leak concern raised in previous comments. Line 136 returns
nullwhen the active request count reaches zero, which causesConcurrentHashMap.compute()to remove the endpoint entry from the map. This ensures unused endpoints are cleaned up and prevents unbounded memory growth.core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java (1)
29-77: LGTM!The singleton pattern is correctly implemented with proper encapsulation. All lifecycle methods are appropriately no-op, and the
asRequestLogListener()method correctly returns a no-opRequestLogListenerlambda, making this an efficient no-overhead implementation when lifecycle tracking is not needed.
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java
Show resolved
Hide resolved
There was a problem hiding this 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 (2)
core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java (2)
35-41: Improve Javadoc clarity.The Javadoc is redundant. Consider making it more concise and informative.
Apply this diff:
/** - * Returns NoopClientRequestLifecycleListener. - * @return Returns NoopClientRequestLifecycleListener. + * Returns the singleton instance. + * @return the singleton instance */ public static NoopClientRequestLifecycleListener getInstance() {
31-33: Simplify lambda syntax.The extra parentheses around the lambda parameters are unnecessary and unconventional.
Apply this diff:
private static final NoopClientRequestLifecycleListener INSTANCE = new NoopClientRequestLifecycleListener(); -private final RequestLogListener requestLogListener = ((property, log) -> {}); +private final RequestLogListener requestLogListener = (property, log) -> {};
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.javacore/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java
🧬 Code graph analysis (2)
core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java (1)
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java (1)
UnstableApi(34-75)
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java (1)
core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java (1)
UnstableApi(28-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Summary
🔇 Additional comments (2)
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java (1)
48-74: LGTM!The event-to-lifecycle mapping is correct and the defensive type check ensures safe execution. The switch statement properly handles all relevant
RequestLogPropertyvalues and delegates to the appropriate lifecycle methods.core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java (1)
45-79: LGTM!The no-op implementations are correct and thread-safe. The singleton pattern is properly implemented, and all lifecycle methods correctly do nothing as intended.
| public DefaultClientRequestLogListener(ClientRequestLifecycleListener lifecycleListener) { | ||
| this.lifecycleListener = lifecycleListener; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add null validation for the constructor parameter.
The constructor should validate that lifecycleListener is not null to prevent potential NPE when onEvent is called. Consider using Objects.requireNonNull() to fail fast with a clear error message.
Apply this diff to add validation:
+import static java.util.Objects.requireNonNull;
+
/**
* Creates a new instance with the specified {@link ClientRequestLifecycleListener}.
*
* @param lifecycleListener the listener to which the {@link RequestLog} events will be delegated
*/
public DefaultClientRequestLogListener(ClientRequestLifecycleListener lifecycleListener) {
- this.lifecycleListener = lifecycleListener;
+ this.lifecycleListener = requireNonNull(lifecycleListener, "lifecycleListener");
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java
around lines 44 to 46, the constructor does not validate the lifecycleListener
parameter; add a null check using Objects.requireNonNull(lifecycleListener,
"lifecycleListener") (or equivalent) so the constructor fails fast with a clear
message if null is passed, assigning the validated value to
this.lifecycleListener.
There was a problem hiding this 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
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java(4 hunks)core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java(1 hunks)core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.javacore/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: Kubernetes Chaos test
- GitHub Check: flaky-tests
- GitHub Check: site
- GitHub Check: lint
- GitHub Check: Summary
🔇 Additional comments (2)
core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java (2)
139-140: LGTM!The default noop() implementation is appropriate for an optional listener, ensuring clients can omit configuration without null checks.
1148-1148: LGTM!The listener is correctly wired into the HttpClientFactory, enabling lifecycle observation for client requests.
| /** | ||
| * Sets the {@link ClientRequestLifecycleListener} that listens to the lifecycle events of | ||
| * client requests created by the {@link ClientFactory}. | ||
| * | ||
| * <p>If not set, {@link ClientRequestLifecycleListener#noop()} is used by default. | ||
| * | ||
| * @param listener the listener to be notified of client request lifecycle events. Must not be {@code null}. | ||
| * @return {@code this} to support method chaining. | ||
| */ | ||
| public ClientFactoryBuilder clientRequestLifecycleListener(ClientRequestLifecycleListener listener) { | ||
| requireNonNull(listener, "listener"); | ||
| clientRequestLifecycleListener = listener; | ||
| return this; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add @UnstableApi annotation to the new public method.
This new public API method should be annotated with @UnstableApi to signal that the API is still evolving. As per coding guidelines, newly added public methods require this annotation unless the class itself is already annotated or the method is in an internal/testing package.
🔎 Proposed fix
/**
* Sets the {@link ClientRequestLifecycleListener} that listens to the lifecycle events of
* client requests created by the {@link ClientFactory}.
*
* <p>If not set, {@link ClientRequestLifecycleListener#noop()} is used by default.
*
* @param listener the listener to be notified of client request lifecycle events. Must not be {@code null}.
* @return {@code this} to support method chaining.
*/
+ @UnstableApi
public ClientFactoryBuilder clientRequestLifecycleListener(ClientRequestLifecycleListener listener) {
requireNonNull(listener, "listener");
clientRequestLifecycleListener = listener;
return this;
}Based on coding guidelines, new public API methods require @UnstableApi annotation.
🤖 Prompt for AI Agents
In core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java
around lines 267 to 280, the newly added public method
clientRequestLifecycleListener(...) is missing the @UnstableApi annotation
required for new public APIs; add the
@com.linecorp.armeria.common.annotation.UnstableApi annotation (or the correct
import) to the method declaration, ensure the import statement for UnstableApi
is present/updated, and keep the existing null check and return behavior
unchanged.
| factory.clientRequestLifecycleListener().onRequestPending(ctx); | ||
| ctx.log().addListener( | ||
| factory.clientRequestLifecycleListener() | ||
| .asRequestLogListener() | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Cache the lifecycle listener to avoid redundant method calls.
The listener is retrieved twice from the factory on the hot path. Store it in a local variable for efficiency.
🔎 Proposed fix
+ final ClientRequestLifecycleListener lifecycleListener =
+ factory.clientRequestLifecycleListener();
- factory.clientRequestLifecycleListener().onRequestPending(ctx);
+ lifecycleListener.onRequestPending(ctx);
ctx.log().addListener(
- factory.clientRequestLifecycleListener()
- .asRequestLogListener()
+ lifecycleListener.asRequestLogListener()
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| factory.clientRequestLifecycleListener().onRequestPending(ctx); | |
| ctx.log().addListener( | |
| factory.clientRequestLifecycleListener() | |
| .asRequestLogListener() | |
| ); | |
| final ClientRequestLifecycleListener lifecycleListener = | |
| factory.clientRequestLifecycleListener(); | |
| lifecycleListener.onRequestPending(ctx); | |
| ctx.log().addListener( | |
| lifecycleListener.asRequestLogListener() | |
| ); |
🤖 Prompt for AI Agents
In core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java around
lines 107 to 111, the call factory.clientRequestLifecycleListener() is invoked
twice on the hot path; cache it in a local variable (e.g.,
ClientRequestLifecycleListener listener =
factory.clientRequestLifecycleListener()) and then call
listener.onRequestPending(ctx) and
ctx.log().addListener(listener.asRequestLogListener()) to avoid redundant method
calls while preserving behavior.
🧩 Analysis chain
🏁 Script executed:
# Find ClientRequestLifecycleListener interface
fd -t f -e java -p . | xargs rg -l "interface ClientRequestLifecycleListener" 2>/dev/null | head -5
# Find HttpClientDelegate.java
fd -t f -name "HttpClientDelegate.java" 2>/dev/null
# Find any classes with "ClientRequestLifecycle" in their name
fd -t f -e java -p . | xargs rg -l "ClientRequestLifecycle" 2>/dev/nullRepository: line/armeria
Length of output: 741
🏁 Script executed:
# Read ClientRequestLifecycleListener.java
echo "=== ClientRequestLifecycleListener.java ==="
head -30 core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java
echo -e "\n=== DefaultClientRequestLogListener.java ==="
head -30 core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java
echo -e "\n=== ClientRequestMetrics.java ==="
head -30 core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
echo -e "\n=== NoopClientRequestLifecycleListener.java ==="
head -30 core/src/main/java/com/linecorp/armeria/client/metric/NoopClientRequestLifecycleListener.java
# Check HttpClientDelegate.java location and lines around 107-111
echo -e "\n=== HttpClientDelegate.java (around lines 100-115) ==="
sed -n '100,115p' core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.javaRepository: line/armeria
Length of output: 5927
🏁 Script executed:
# Get the full ClientRequestLifecycleListener interface declaration
echo "=== ClientRequestLifecycleListener interface ==="
sed -n '1,50p' core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java | tail -25
# Get the full DefaultClientRequestLogListener class declaration
echo -e "\n=== DefaultClientRequestLogListener class declaration ==="
sed -n '1,65p' core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java | tail -25
# Get the asRequestLogListener method signature
echo -e "\n=== asRequestLogListener method in ClientRequestLifecycleListener ==="
rg -A5 "asRequestLogListener" core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java
# Check ClientRequestMetrics for @UnstableApi
echo -e "\n=== ClientRequestMetrics @UnstableApi check ==="
rg "@UnstableApi|^public class ClientRequestMetrics" core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java | head -5Repository: line/armeria
Length of output: 3016
🏁 Script executed:
# Check ClientRequestMetrics full class declaration with annotations
echo "=== ClientRequestMetrics full header ==="
sed -n '1,45p' core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
# Also check if there are any other public classes/methods in ClientRequestLifecycleListener
echo -e "\n=== Full ClientRequestLifecycleListener interface ==="
wc -l core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java
# Get full interface
cat core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java | head -150Repository: line/armeria
Length of output: 6333
🏁 Script executed:
# Check if ClientRequestMetrics has @UnstableApi annotation
echo "=== ClientRequestMetrics class with preceding annotations ==="
rg -B2 "^public class ClientRequestMetrics" core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
# Also verify DefaultClientRequestLogListener has @UnstableApi
echo -e "\n=== DefaultClientRequestLogListener with annotations ==="
rg -B2 "^public class DefaultClientRequestLogListener" core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java
# Check if DefaultClientRequestLogListener is in logging package (not .internal)
echo -e "\n=== Package declarations ==="
rg "^package" core/src/main/java/com/linecorp/armeria/client/logging/DefaultClientRequestLogListener.java
rg "^package" core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
rg "^package" core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.javaRepository: line/armeria
Length of output: 523
🏁 Script executed:
# Get the full ClientRequestMetrics class definition to confirm it's missing @UnstableApi
echo "=== ClientRequestMetrics lines 30-50 ==="
sed -n '30,50p' core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
# Double-check by looking at all annotations before the class
echo -e "\n=== ClientRequestMetrics all lines before class declaration ==="
grep -n "@\|^public class" core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java | head -10Repository: line/armeria
Length of output: 1488
Add @UnstableApi annotation to ClientRequestMetrics.
The ClientRequestMetrics class at core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java is missing the @UnstableApi annotation. Per coding guidelines, all newly added public classes must be annotated with @UnstableApi unless they are located in .internal or .testing packages or test source sets. The related classes ClientRequestLifecycleListener, DefaultClientRequestLogListener, and NoopClientRequestLifecycleListener are correctly annotated.
🤖 Prompt for AI Agents
In
core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestMetrics.java
around lines 1 to 999 (the class declaration), the public class
ClientRequestMetrics is missing the required @UnstableApi annotation; add the
annotation above the class declaration, import
com.linecorp.armeria.common.annotation.UnstableApi (or the correct package used
in the project), and run a quick compile to ensure there are no import or symbol
issues. Ensure you do not annotate classes in .internal or .testing packages,
but since this is a public non-internal class, the annotation must be applied.
There was a problem hiding this 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
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: lint
- GitHub Check: flaky-tests
- GitHub Check: site
- GitHub Check: Kubernetes Chaos test
- GitHub Check: Summary
🔇 Additional comments (4)
core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java (4)
41-41: LGTM!Import is correct and appropriately placed.
127-127: LGTM!Field declaration follows proper conventions with appropriate visibility and immutability.
129-133: LGTM!Constructor delegation with
noop()default maintains backward compatibility while enabling the new listener functionality.
319-321: LGTM!Package-private getter is appropriately scoped for internal use within the client framework.
| HttpClientFactory(ClientFactoryOptions options, boolean autoCloseConnectionPoolListener, | ||
| ClientTlsSpec baseClientTlsSpec, | ||
| ClientRequestLifecycleListener clientRequestLifecycleListener) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add null validation for the listener parameter.
The clientRequestLifecycleListener parameter should be validated to prevent potential NullPointerException. Other parts of this class use requireNonNull for parameter validation (e.g., line 362, line 510).
🔎 Proposed fix
HttpClientFactory(ClientFactoryOptions options, boolean autoCloseConnectionPoolListener,
ClientTlsSpec baseClientTlsSpec,
ClientRequestLifecycleListener clientRequestLifecycleListener) {
+ this.clientRequestLifecycleListener = requireNonNull(clientRequestLifecycleListener,
+ "clientRequestLifecycleListener");
workerGroup = options.workerGroup();And update line 211:
this.options = options;
- this.clientRequestLifecycleListener = clientRequestLifecycleListener;Also applies to: 211-211
🤖 Prompt for AI Agents
In core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java around
lines 135-137 (and also at line 211), the clientRequestLifecycleListener
parameter is not being null-checked; add a requireNonNull validation for
clientRequestLifecycleListener in the constructor (and the other affected
constructor/factory method at line 211) so the parameter is validated like the
other parameters, throwing a clear NPE message if null.
There was a problem hiding this 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)
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java (1)
144-160: Consider a more deterministic delay mechanism.While
Thread.sleep(3000)effectively simulates a slow connection for testing early cancellation, it makes the test slower and potentially brittle. Consider using a more controllable synchronization mechanism (e.g., a latch that the test explicitly releases) to gain finer control over timing and reduce test execution time.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java
⚙️ CodeRabbit configuration file
**/*.java: - The primary coding conventions and style guide for this project are defined insite/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.2. Specific check for
@UnstableApi
- Review all newly added public classes and methods to ensure they have the
@UnstableApiannotation.- However, this annotation is NOT required under the following conditions:
- If the class or method is located in a package containing
.internalor.testing.- If the class or method is located in a test source set.
- If a public method is part of a class that is already annotated with
@UnstableApi.
Files:
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
- GitHub Check: build-windows-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
- GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
- GitHub Check: build-ubicloud-standard-16-jdk-8
- GitHub Check: build-macos-latest-jdk-25
- GitHub Check: build-ubicloud-standard-16-jdk-17-leak
- GitHub Check: build-ubicloud-standard-16-jdk-11
- GitHub Check: build-ubicloud-standard-16-jdk-25
- GitHub Check: site
- GitHub Check: flaky-tests
- GitHub Check: lint
- GitHub Check: Kubernetes Chaos test
- GitHub Check: Summary
🔇 Additional comments (1)
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java (1)
48-96: Server setup looks well-structured.The use of CountDownLatches to synchronize client and server behavior is appropriate for integration testing, and the server handlers correctly use
blockingTaskExecutorfor blocking operations.
| assertEquals(2L, metrics.pendingRequest()); | ||
| assertEquals(0L, metrics.activeRequests()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in metrics assertions.
These assertions check metrics immediately after initiating requests without synchronization. Depending on thread scheduling and network latency, the requests might transition from pending to active before these assertions execute, potentially causing test flakiness.
🔎 Suggested fix using Awaitility
- assertEquals(2L, metrics.pendingRequest());
- assertEquals(0L, metrics.activeRequests());
+ await().pollInSameThread().atMost(3, TimeUnit.SECONDS).untilAsserted(() -> {
+ assertEquals(2L, metrics.pendingRequest());
+ assertEquals(0L, metrics.activeRequests());
+ });Alternatively, if the intent is to verify the pending state exists even momentarily, consider adding a brief synchronization point (e.g., a short polling interval) to confirm metrics stabilize at the expected values before proceeding.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java
around lines 115-116, the assertions assertEquals(2L, metrics.pendingRequest())
and assertEquals(0L, metrics.activeRequests()) can race with request state
transitions; replace the immediate assertions with a short wait/poll (e.g.,
Awaitility or a loop with timeout) that repeatedly checks metrics until
pendingRequest() == 2 and activeRequests() == 0 or a timeout occurs, to ensure
the metrics stabilize before asserting and avoid flakiness.
| assertEquals(1L, metrics.pendingRequest()); | ||
| assertEquals(0L, metrics.activeRequests()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in metrics assertions.
Similar to the first test, these assertions check metrics immediately after initiating a request to an invalid host. The connection failure might occur very quickly (especially with 255.255.255.255, which is typically unreachable), causing the request to transition through its lifecycle before these assertions execute.
🔎 Suggested fix using Awaitility
- assertEquals(1L, metrics.pendingRequest());
- assertEquals(0L, metrics.activeRequests());
+ await().pollInSameThread().atMost(3, TimeUnit.SECONDS).untilAsserted(() -> {
+ assertEquals(1L, metrics.pendingRequest());
+ assertEquals(0L, metrics.activeRequests());
+ });This ensures the test captures the pending state even if the failure happens quickly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assertEquals(1L, metrics.pendingRequest()); | |
| assertEquals(0L, metrics.activeRequests()); | |
| await().pollInSameThread().atMost(3, TimeUnit.SECONDS).untilAsserted(() -> { | |
| assertEquals(1L, metrics.pendingRequest()); | |
| assertEquals(0L, metrics.activeRequests()); | |
| }); |
🤖 Prompt for AI Agents
In
core/src/test/java/com/linecorp/armeria/client/metric/ClientRequestMetricsIntegrationTest.java
around lines 209-210, the assertions check metrics immediately and may race with
a fast connection failure; replace the direct assertEquals calls with an
Awaitility wait that repeatedly checks until metrics.pendingRequest() == 1 and
metrics.activeRequests() == 0 (with a sensible timeout and poll interval) so the
test reliably observes the pending state even if the failure happens quickly.
| @Override | ||
| public void onRequestPending(ClientRequestContext ctx) { | ||
| final State s = state(ctx); | ||
| if (s.pendingCounted.compareAndSet(false, true)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Why do we need this guard? Is this method invoked more than once?
| } | ||
|
|
||
| @Override | ||
| public void onEvent(RequestLogProperty property, RequestLog log) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we create one RequestLogListener that invokes all registered lifecycleListener?
public void onEvent(RequestLogProperty property, RequestLog log) {
...
switch (property) {
case REQUEST_FIRST_BYTES_TRANSFERRED_TIME:
for (var lifecycleListener: lifecycleListeners) {
lifecycleListener.onRequestStart(ctx);
}
break;
}Otherwise, orElse() could be added to compose two ClientRequestLifecycleListener easily.
| * @param listener the listener to be notified of client request lifecycle events. Must not be {@code null}. | ||
| * @return {@code this} to support method chaining. | ||
| */ | ||
| public ClientFactoryBuilder clientRequestLifecycleListener(ClientRequestLifecycleListener listener) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For simplicity.
| public ClientFactoryBuilder clientRequestLifecycleListener(ClientRequestLifecycleListener listener) { | |
| public ClientFactoryBuilder requestLifecycleListener(ClientRequestLifecycleListener listener) { |
| */ | ||
| public ClientFactoryBuilder clientRequestLifecycleListener(ClientRequestLifecycleListener listener) { | ||
| requireNonNull(listener, "listener"); | ||
| clientRequestLifecycleListener = listener; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we allow multiple ClientRequestLifecycleListeners?
Motivation:
Because of #6372 .
Modifications:
ClientMetrics.HttpClientFactoryhasClientMetricsas its field.HttpChannelPoolhasClientMetricsas its field.HttpChannelPoolcallsClientMetricswhenever callingsetPendingAcquisition(...)andremovePendingAcquisition(...).HttpSessionHandlercallsClientMetricsto increment count for active request and reserve to decrement count of active request.Result: