Skip to content
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

Open
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

festinalente91
Copy link

@festinalente91 festinalente91 commented Apr 8, 2024

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.

  • Connection pending
  • Connection failed
  • Connection active
  • Connection idle
                                                                                           
                                    +------------------+                                   
        +--------------+            | +--------------+ |            +--------------+       
        |              |            | |              | |            |              |       
        |    Pending   |----------->| |    Opened    | |----------> |    Closed    |       
        |              |            | |              | |            |              |       
        +--------------+            | +--------------+ |            +--------------+       
                |                   |                  |                                   
                |                   |      Active      |                                   
                |                   |         ^        |                                   
                |                   |         |        |                                   
                |                   |         |        |                                   
                v                   |         |        |                                   
        +--------------+            |         |        |                                   
        |              |            |         v        |                                   
        |    Failed    |            |       Idle       |                                   
        |              |            |                  |                                   
        +--------------+            +------------------+                                   

Furthermore, as @ikhoon mentioned here
we can also expand this connection event interface to be used on the server side.

Modifications:

  • (Deprecated) com.linecorp.armeria.client.ConnectionPoolListener class is deprecated.
    • Use com.linecorp.armeria.client.ClientConnectionEventListener instead.

Result:

@CLAassistant
Copy link

CLAassistant commented Apr 8, 2024

CLA assistant check
All committers have signed the CLA.

@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch from b52f5a3 to 2851ee6 Compare April 8, 2024 08:41
@ikhoon ikhoon added new feature sprint Issues for OSS Sprint participants labels Apr 8, 2024
@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch 2 times, most recently from 9f7edb8 to 1da16f2 Compare April 17, 2024 01:12
@@ -181,6 +189,26 @@ public final void onReadOrWrite() {
lastConnectionIdleTime = System.nanoTime();
}

if (!isServer && !isActive) {
Copy link
Contributor

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:

public interface StreamDecoder extends com.linecorp.armeria.client.encoding.StreamDecoder {

Related issue: #3722

@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch from 416f42b to 4be97ef Compare April 21, 2024 10:38
Copy link

codecov bot commented Apr 21, 2024

Codecov Report

Attention: Patch coverage is 78.20513% with 17 lines in your changes missing coverage. Please review.

Project coverage is 74.03%. Comparing base (e08527d) to head (4be97ef).
Report is 6 commits behind head on main.

Current head 4be97ef differs from pull request most recent head b8873e1

Please upload reports for the commit b8873e1 to get more accurate results.

Files Patch % Lines
...eria/internal/common/AbstractKeepAliveHandler.java 66.66% 8 Missing and 2 partials ⚠️
.../armeria/client/ConnectionPoolListenerWrapper.java 0.00% 4 Missing ⚠️
...a/com/linecorp/armeria/client/HttpChannelPool.java 50.00% 1 Missing ⚠️
...inecorp/armeria/common/ConnectionPoolListener.java 0.00% 1 Missing ⚠️
...linecorp/armeria/common/ConnectionPoolMetrics.java 96.00% 0 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch from 918d3ac to 6250594 Compare April 28, 2024 03:42
@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch 5 times, most recently from edcff6b to 5a4530a Compare May 7, 2024 01:52
@festinalente91 festinalente91 requested a review from ikhoon May 7, 2024 04:46
@festinalente91 festinalente91 marked this pull request as ready for review May 7, 2024 04:50
Copy link
Member

@trustin trustin left a 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!

@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch from 5a4530a to 89e0257 Compare May 12, 2024 03:34
Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Basically, looks good. 👍

final ConnectionEventKey connectionEventKey = connectionEventKey(channel);

try {
connectionEventListener.connectionOpened(connectionEventKey, channel);
Copy link
Member

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?

Copy link
Author

@festinalente91 festinalente91 May 14, 2024

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 {)

Copy link
Member

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

@festinalente91 festinalente91 force-pushed the feature/enrich-connection-pool-listener-event-interface branch 2 times, most recently from e99fa5d to 6e4e7e5 Compare May 14, 2024 13:25
festinalente91 and others added 4 commits June 21, 2024 19:08
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.
Copy link
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.

Left some more comments 👍

Comment on lines 439 to 440
// When the transport type(-Dcom.linecorp.armeria.transportType) is NIO,
// sometimes local address is not available yet.
Copy link
Contributor

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?

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.
Copy link
Contributor

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

@ikhoon
Copy link
Contributor

ikhoon commented Jun 27, 2024

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

@jrhee17
Copy link
Contributor

jrhee17 commented Jul 10, 2024

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.

@festinalente91
Copy link
Author

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.

@jrhee17
Copy link
Contributor

jrhee17 commented Jul 11, 2024

I will update this PR over the weekend and let you know.

I saw this comment late: #5580 (comment)
Let's wait to see if @ikhoon is done updating before proceeding with this PR further.

@ikhoon
Copy link
Contributor

ikhoon commented Jul 16, 2024

Let's wait to see if @ikhoon is done updating before proceeding with this PR further.

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.

Copy link
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.

Left some high level comments 🙇

@@ -120,14 +127,22 @@ protected AbstractKeepAliveHandler(Channel channel, String name, Timer keepAlive
public final void initialize(ChannelHandlerContext ctx) {
Copy link
Contributor

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();
Copy link
Contributor

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?

Copy link
Author

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() {
Copy link
Contributor

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

Copy link
Contributor

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()?

@ikhoon ikhoon modified the milestones: 1.30.0, 1.31.0 Aug 1, 2024
@github-actions github-actions bot added the Stale label Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature sprint Issues for OSS Sprint participants Stale
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enrich ConnectionPoolListener interface to listen to more connection events
6 participants