Skip to content

feat(java): implement Async Connection Pooling using FixedChannelPool#2606

Open
rythm-sachdeva wants to merge 1 commit intoapache:masterfrom
rythm-sachdeva:feature/async-connection-pool
Open

feat(java): implement Async Connection Pooling using FixedChannelPool#2606
rythm-sachdeva wants to merge 1 commit intoapache:masterfrom
rythm-sachdeva:feature/async-connection-pool

Conversation

@rythm-sachdeva
Copy link

Description

Implements connection pooling for AsyncTcpConnection using Netty's FixedChannelPool to handle concurrent async operations efficiently.

Changes

  • Connection Pooling: Replaced single channel with FixedChannelPool for concurrent request handling
  • Pool Metrics: Added PoolMetrics class to track:
    • Active connections
    • Total requests and errors
    • Wait times (min/max/average in nanoseconds, microseconds, and milliseconds)
    • Error rate percentage
  • Configuration: Added TCPConnectionPoolConfig with builder pattern for customizing:
    • Max connections (default: 5)
    • Max pending acquires (default: 1000)
    • Acquire timeout (default: 3000ms)
  • Request Correlation: Implemented FIFO queue-based request/response matching using ConcurrentLinkedQueue
  • Broadcast Operations: Added broadcastAsync() method to execute commands across all pooled connections (useful for authentication)
  • Client Integration: Exposed pool configuration via AsyncIggyTcpClient.Builder.connectionPoolSize()

Implementation Details

  • Uses Netty's FixedChannelPool with ChannelHealthChecker.ACTIVE
  • Each channel has its own IggyResponseHandler with a response queue
  • Pool automatically releases channels after request completion
  • Handles connection errors gracefully with proper cleanup

Testing

  • Added integration test testConnectionPoolMetrics() to verify metrics tracking
  • Existing async integration tests validate concurrent operations
  • Pool properly handles concurrent sends and polls

@mmodzelewski mmodzelewski linked an issue Jan 27, 2026 that may be closed by this pull request
20 tasks
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs.

If you need a review, please ensure CI is green and the PR is rebased on the latest master. Don't hesitate to ping the maintainers - either @core on Discord or by mentioning them directly here on the PR.

Thank you for your contribution!

@github-actions github-actions bot added the stale Inactive issue or pull request label Feb 4, 2026
@hubcio hubcio changed the title feat(java): Implemented Async Connection Pooling using FixedChannelPool (#2232) feat(java): implement Async Connection Pooling using FixedChannelPool Feb 4, 2026
@github-actions github-actions bot removed the stale Inactive issue or pull request label Feb 5, 2026
@rythm-sachdeva rythm-sachdeva force-pushed the feature/async-connection-pool branch 2 times, most recently from 2cdb131 to 50e8a36 Compare February 7, 2026 06:56
@rythm-sachdeva
Copy link
Author

Hi @mmodzelewski Can you please review .

@mmodzelewski
Copy link
Member

Hi @mmodzelewski Can you please review .

Hey @rythm-sachdeva, I will, but I need some time. I might not get round to it until the beginning of next week.

@rythm-sachdeva rythm-sachdeva force-pushed the feature/async-connection-pool branch from 50e8a36 to ca4583f Compare February 17, 2026 15:08
@slbotbm
Copy link
Contributor

slbotbm commented Feb 17, 2026

@rythm-sachdeva Please see why CI is failing and fix the errors.

@mmodzelewski
Copy link
Member

@rythm-sachdeva Please see why CI is failing and fix the errors.

@rythm-sachdeva I've added a verification if all ByteBufs are properly released (not leaked) and it appears that the new code does not release all resources. See the logs, or run the tests locally, you'll see reports of the leaks in there.

@rythm-sachdeva rythm-sachdeva force-pushed the feature/async-connection-pool branch 4 times, most recently from 4eaf354 to 181682b Compare February 22, 2026 01:40
@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 57.81991% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.50%. Comparing base (aff29a5) to head (181682b).

Files with missing lines Patch % Lines
...ache/iggy/client/async/tcp/AsyncTcpConnection.java 60.86% 40 Missing and 14 partials ⚠️
.../org/apache/iggy/client/async/tcp/PoolMetrics.java 45.28% 26 Missing and 3 partials ⚠️
...g/apache/iggy/client/async/tcp/UsersTcpClient.java 50.00% 6 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2606      +/-   ##
============================================
- Coverage     68.52%   68.50%   -0.02%     
- Complexity      656      673      +17     
============================================
  Files           743      744       +1     
  Lines         62808    62934     +126     
  Branches      59221    59235      +14     
============================================
+ Hits          43039    43114      +75     
- Misses        17656    17700      +44     
- Partials       2113     2120       +7     
Flag Coverage Δ
java 52.42% <57.81%> (+0.23%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ache/iggy/client/async/tcp/AsyncIggyTcpClient.java 98.11% <100.00%> (+0.28%) ⬆️
...g/apache/iggy/client/async/tcp/UsersTcpClient.java 73.33% <50.00%> (-3.59%) ⬇️
.../org/apache/iggy/client/async/tcp/PoolMetrics.java 45.28% <45.28%> (ø)
...ache/iggy/client/async/tcp/AsyncTcpConnection.java 61.62% <60.86%> (+3.44%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@mmodzelewski mmodzelewski left a comment

Choose a reason for hiding this comment

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

Hey @rythm-sachdeva, I left a few comments, please let me know if you have any questions.

public CompletableFuture<Void> connect() {
connection = new AsyncTcpConnection(host, port, enableTls, tlsCertificate);
TCPConnectionPoolConfig.Builder poolConfigBuilder = new TCPConnectionPoolConfig.Builder();
if (connectionPoolSize.isPresent()) {
Copy link
Member

Choose a reason for hiding this comment

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

instead of isPresent/get, it would be more idiomatic to just call ifPresent with an update lambda


public AsyncTcpConnection(String host, int port) {
this(host, port, false, Optional.empty());
this(host, port, false, Optional.empty(), new TCPConnectionPoolConfig(5, 1000, 1000));
Copy link
Member

Choose a reason for hiding this comment

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

Please create a no-args constructor for the config, and use it here instead.

public static final class Builder {
private int maxConnections = 5;
private int maxPendingAcquires = 1000;
private long acquireTimeoutMillis = 5000;
Copy link
Member

Choose a reason for hiding this comment

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

assign above values to constants, and use them in no-args constructor for the config

}
}

// Inner Class for Channel pool configurations
Copy link
Member

Choose a reason for hiding this comment

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

Please drop all comments that do not introduce any new information. Comments are justified in situations where code is not obvious, something is done in a non-standard way, etc.

*/
public CompletableFuture<Void> connect() {
if (isClosed.get()) {
return CompletableFuture.failedFuture(new IllegalStateException("Client is Closed"));
Copy link
Member

Choose a reason for hiding this comment

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

in the SDK code, please only use exceptions that inherit from IggyException

poolConfigBuilder.setMaxConnections(connectionPoolSize.get());
}
if (connectionTimeout.isPresent()) {
poolConfigBuilder.setAcquireTimeoutMillis(connectionTimeout.get().toMillis());
Copy link
Member

Choose a reason for hiding this comment

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

seems like connection timeout should map to CONNECT_TIMEOUT_MILLIS, acquire timeout is semantically different thing

Copy link
Author

Choose a reason for hiding this comment

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

I think by mistake I have given the wrong name it should be acquireTimeout

* Metrics for monitoring connection pool performance.
* Tracks active connections, wait times, and errors.
*/
public class PoolMetrics {
Copy link
Member

Choose a reason for hiding this comment

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

I'd drop the metrics for now. Let's make sure that the connection pool is working fine first. This can be a follow up.

if (channelPool != null) {
channelPool.close();
}
return CompletableFuture.runAsync(eventLoopGroup::shutdownGracefully);
Copy link
Member

Choose a reason for hiding this comment

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

It would make sense to wait for the result of shutdownGracefully. Currently, it is ignored.


public Builder() {}

public Builder setMaxConnections(int maxConnections) {
Copy link
Member

Choose a reason for hiding this comment

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

let's at least add some simple validations for the values inserted to the config

* Returns the result of the LAST connection's execution, allowing the caller
* to treat this like a single request.
*/
public CompletableFuture<ByteBuf> broadcastAsync(int commandCode, ByteBuf payload) {
Copy link
Member

Choose a reason for hiding this comment

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

broadcastAsync() for authentication is unreliable - FixedChannelPool creates channels lazily, so sending poolSize login requests may hit the same channel multiple times (it gets released in channelRead0 and re-acquired) rather than reaching every distinct connection. Connections created later (e.g. reconnect after health-check failure) will also be unauthenticated.

A simpler approach: authenticate lazily in send(). After acquiring a channel, check a channel attribute (e.g. channel.attr(AUTH_KEY)). If not authenticated, send login first, set the attribute on success, then send the actual command. This works naturally with the async model, handles all channel lifecycle events (pool growth, reconnection, idle eviction), and eliminates the need for broadcastAsync() entirely.

Copy link
Author

Choose a reason for hiding this comment

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

Currently the login is called by the user once After client creation. In this case how would the flow work ?

Copy link
Member

Choose a reason for hiding this comment

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

The user-facing API stays the same - login() is still called once. Internally, login() would store the credentials and authenticate the one channel it acquires. Then in send(), after acquiring a channel, you check a channel attribute (e.g. AUTH_KEY). If the channel isn't authenticated yet, send login with the stored credentials first, set the attribute, then send the actual command. This way, every channel gets authenticated transparently on first use, including channels created later by the pool.

}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Copy link
Member

Choose a reason for hiding this comment

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

A channel should also be released to the pool in case of exception

fix(java-sdk):Added Missing PoolMetrics and linting issues

fix(java):fixed connection error

fix(java):Fixed Linting Issues

fix(java):fixed builder issues

fix(java):fixed ci issues

fix(java): fixed memory leaks

fix(java):fixed memory leak at channelread0

fix(java):fixed broadcastasync memory leak

feat(java): Added attribute based channel login

fix(java): fixed error handling in inbound handler
@rythm-sachdeva rythm-sachdeva force-pushed the feature/async-connection-pool branch from 181682b to a31643a Compare February 28, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Java SDK] Implement Connection Pooling for Async Client

4 participants