-
Notifications
You must be signed in to change notification settings - Fork 914
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
Enrich ConnectionPoolListener interface to listen more connection events #5580
base: main
Are you sure you want to change the base?
Enrich ConnectionPoolListener interface to listen more connection events #5580
Conversation
b52f5a3
to
2851ee6
Compare
9f7edb8
to
1da16f2
Compare
@@ -181,6 +189,26 @@ public final void onReadOrWrite() { | |||
lastConnectionIdleTime = System.nanoTime(); | |||
} | |||
|
|||
if (!isServer && !isActive) { |
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.
It should be useful if we also support ConnectionPoolListener
for servers. Although we are focusing on the client-side implementation, the public API design should take extensibility into consideration.
I propose to add a new ConnectionPoolListener
to c.l.a.common
package. The new methods will be added to the new ConnectionPoolListener
which extends the old ConnectionPoolListener
for compatibility. For example:
armeria/core/src/main/java/com/linecorp/armeria/common/encoding/StreamDecoder.java
Line 27 in 1d268aa
public interface StreamDecoder extends com.linecorp.armeria.client.encoding.StreamDecoder { |
Related issue: #3722
core/src/main/java/com/linecorp/armeria/internal/common/AbstractKeepAliveHandler.java
Outdated
Show resolved
Hide resolved
416f42b
to
4be97ef
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5580 +/- ##
============================================
- Coverage 74.75% 74.03% -0.73%
+ Complexity 21409 21197 -212
============================================
Files 1877 1844 -33
Lines 79126 78438 -688
Branches 10201 10010 -191
============================================
- Hits 59152 58069 -1083
- Misses 15179 15668 +489
+ Partials 4795 4701 -94 ☔ View full report in Codecov by Sentry. |
918d3ac
to
6250594
Compare
core/src/main/java/com/linecorp/armeria/client/ConnectionPoolListener.java
Outdated
Show resolved
Hide resolved
edcff6b
to
5a4530a
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.
This is really good. Thanks for your high quality contribution, @festinalente91!
core/src/main/java/com/linecorp/armeria/client/ConnectionPoolListener.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/ConnectionPoolListenerAdapter.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/ConnectionPoolListener.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/ConnectionPoolMetrics.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/MetricCollectingConnectionPoolListener.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/ConnectionPoolListener.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/ConnectionPoolListener.java
Outdated
Show resolved
Hide resolved
5a4530a
to
89e0257
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.
Basically, looks good. 👍
core/src/main/java/com/linecorp/armeria/client/ClientConnectionEventListener.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/ClientConnectionEventListener.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/Http1ClientKeepAliveHandler.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java
Outdated
Show resolved
Hide resolved
final ConnectionEventKey connectionEventKey = connectionEventKey(channel); | ||
|
||
try { | ||
connectionEventListener.connectionOpened(connectionEventKey, channel); |
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.
If NoopKeepAliveHandler
is used, this event isn't called anymore.
keepAliveHandler = new NoopKeepAliveHandler(); |
Is there any reason that you've moved this logic from
HttpChannelPool
?
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 tried to move open and close event trigger logic into the AbstractKeepAliveHandler to reuse this on the server side. However, as you said, I had to make extra changes to do that. Any feedback for this? @minwoox @ikhoon
- Added connection opened and closed event trigger logic into the NoopKeepAliveHandler
- To share the open and close event trigger logic on server side.
- Handled HTTP Upgrade cases with context-checking(HttpProtocolUpgradeHandler)
- When the protocol is upgraded, AbstractKeepAliveHandler is called twice.
- So, I had to ignore the first one which was related to the protocol upgrade process.
- (Still struggling to handle HTTP2 Max connection setting test case.
- void exceededMaxStreamsPropagatesFailureCorrectly() throws Exception {)
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 tried to move open and close event trigger logic into the AbstractKeepAliveHandler to reuse this on the server side
Since the connection mechanisms differ between the server-side and client-side, I don't think we have to put the logic into the same class.
Instead, we could consider incorporating the logic for the server-side into ConnectionLimitingHandler, which is already used for managing connections. You can find the ConnectionLimitingHandler implementation here: Link
core/src/main/java/com/linecorp/armeria/common/ConnectionEventKey.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/ConnectionEventKey.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/ConnectionEventKey.java
Outdated
Show resolved
Hide resolved
e99fa5d
to
6e4e7e5
Compare
When the sessionPromise fails, sessionPromise.getNow() returns a FailedFuture, which returns the channel as null. To keep the connectionEventState, I added connectionEventStates to the HttpChannelPool.
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.
Left some more comments 👍
// When the transport type(-Dcom.linecorp.armeria.transportType) is NIO, | ||
// sometimes local address is not available yet. |
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 believe this is because the channel isn't bound yet at this point. Is there a reason we can't just remove L434-L445?
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/ConnectionEventState.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/NoopKeepAliveHandler.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/NoopKeepAliveHandler.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/ClientConnectionEventListenerAdapter.java
Outdated
Show resolved
Hide resolved
public ClientFactoryBuilder connectionPoolListener( | ||
ConnectionPoolListener connectionPoolListener) { | ||
option(ClientFactoryOptions.CONNECTION_POOL_LISTENER, | ||
requireNonNull(connectionPoolListener, "connectionPoolListener")); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the listener which is notified on a connection event. |
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 slightly prefer raising an IllegalStateException
when both ConnectionPoolListener
and ClientConnectionEventListener
are set.
We may store a specified ClientConnectionEventListener
in a member field and set to options
in buildOptions()
.
public ClientFactoryBuilder connectionPoolListener(
ConnectionPoolListener connectionPoolListener) {
checkState(connectionEventListener == null, "connectionPoolListener() and
connectionEventListener() are mutually exclusive")
this.connectionPoolListener = connectionPoolListener;
}
...
private ClientFactoryOptions buildOptions() {
if (connectionPoolListener != null) {
// Convert connectionPoolListener to connectionEventListener.
}
}
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java
Outdated
Show resolved
Hide resolved
This became a big PR with more than 2000 lines. As a first-time contributor, you are not familiar with Armeria's code internals and conventions. It is also not easy for us to review over 2000 lines. Let me add a clean-up commit and build together. 😉 |
…tion-pool-listener-event-interface
Is this PR re-ready for review? I prefer at least the CI be passing before reviewing since it is time/effort consuming on my end as well. |
@jrhee17 Not yet. I apologize for the delay. I will update this PR over the weekend and let you know. |
I saw this comment late: #5580 (comment) |
I refactored the code but I asked @festinalente91 to finish this PR. I may add some minor fixes but @festinalente91 will handle the remaining part. |
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.
Left some high level comments 🙇
core/src/main/java/com/linecorp/armeria/client/ClientConnectionEventListener.java
Show resolved
Hide resolved
@@ -120,14 +127,22 @@ protected AbstractKeepAliveHandler(Channel channel, String name, Timer keepAlive | |||
public final void initialize(ChannelHandlerContext ctx) { |
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 believe this method is called when the HttpSessionHandler
is added and not necessarily when the connection is ready for sending/receiving requests.
|
||
keepAliveState = KeepAliveState.INITIALIZED; | ||
if (connectionEventListener != null) { | ||
connectionEventListener.connectionOpened(); |
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.
It seems like if initialize
is called due to channelActive
or handlerAdded
, the HttpSession#protocol
may not exist. Can you check this point?
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.
You are right. connectionEventListener.connectionOpened() should be called after sessionPromise is completed.
Furthermore, (to support maxUnfinishedRequest filter) this should called before checking unfinished request.
And I was struggling to solve this problem. 🫠
final class HttpChannelPool implements AsyncCloseable {
....
private void notifyConnect(SessionProtocol desiredProtocol,
PoolKey key, Future<Channel> future,
ChannelAcquisitionFuture promise,
ClientConnectionTimingsBuilder timingsBuilder) {
....
// In the current implementation(master branch), connectionOpen is called at here
if (session.incrementNumUnfinishedResponses()) {
if (protocol.isMultiplex()) {
final Http2PooledChannel pooledChannel = new Http2PooledChannel(channel, protocol);
addToPool(protocol, key, pooledChannel);
promise.complete(pooledChannel);
} else {
promise.complete(new Http1PooledChannel(channel, protocol, key));
}
} else {
// Server set MAX_CONCURRENT_STREAMS to 0, which means we can't send anything.
channel.close();
promise.completeExceptionally(
UnprocessedRequestException.of(RefusedStreamException.get()));
}
...
}
return actualProtocol; | ||
} | ||
|
||
public void connectionPending() { |
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.
connectionPending
sounds a little odd since ClientConnectionTimings#pendingAcquisition
seems to be measuring a different timing although it has a similar name.
Would sessionPending
make more sense? @line/dx
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 prefer a method name starting with connection
to be consistent with other methods.
How about connectionNegotiating()
?
Motivation:
ConnectionPoolListener provides a limited way to listen to connection events. (open, close)
We could provide a more sophisticated event interface like the one below.
Furthermore, as @ikhoon mentioned here
we can also expand this connection event interface to be used on the server side.
Modifications:
Result:
ConnectionPoolListener
interface to listen to more connection events #4649 .