Skip to content

Fix BlockingOperationException when closing HTTP/2 connection pool - #7473

Open
tuannx wants to merge 1 commit into
aws:masterfrom
tuannx:fix-6863
Open

tuannx wants to merge 1 commit into
aws:masterfrom
tuannx:fix-6863

Conversation

@tuannx

@tuannx tuannx commented Oct 22, 2026 •

Copy link
Copy Markdown

Motivation and Context

Fixes #6863.

Http2MultiplexedChannelPool#doClose() called connectionPool.close() inside a listener running on the close event loop. SimpleChannelPool#close() blocks on channel.close().awaitUninterruptibly() for each idle connection, so when an idle connection belongs to that event loop and its close does not complete immediately (e.g. TLS close_notify), Netty throws BlockingOperationException. The exception is swallowed, closeFinishedPromise is never completed, and close() fails after 10 seconds with "Event loop didn't close after 10 seconds."

Modifications

  • Http2MultiplexedChannelPool: the event loop listener only completes closeFinishedPromise once all multiplexed connections are released. close() then calls connectionPool.close() on the calling thread after the wait. If close() itself runs on an event loop of the pool's group (as HttpOrHttp2ChannelPool#close() does), it does not wait and closes the connection pool on GlobalEventExecutor once the release completes.
  • Running connectionPool.close() on GlobalEventExecutor unconditionally was rejected: BetterFixedChannelPool#close0() already calls Http2MultiplexedChannelPool#close() on GlobalEventExecutor, which then waits on itself (reproduced as a 10 second timeout in Http2MultiplexedChannelPoolTest).
  • Added Http2MultiplexedChannelPoolCloseTest (single-thread DefaultEventLoopGroup, LocalChannel, a real SimpleChannelPool with one idle connection whose close completes on the next loop iteration): close from a non event loop thread, and close from the event loop owning the idle connection. Both fail on master with BlockingOperationException.
  • Added changelog entry.

Testing

  • mvn -B install -pl http-clients/netty-nio-client (checkstyle and spotbugs enabled): BUILD SUCCESS; 470 tests, 0 failures, 0 errors, 1 skipped (JUnit 5) and 212 tests, 0 failures, 0 errors, 70 skipped (JUnit 4/TestNG).
  • *ChannelPool*Test and NettyClientAlpnTest run 5 times: all passed.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING document
  • Local run of mvn install succeeds
  • My code follows the code style of this project
  • My change requires a change to the Javadoc documentation
  • I have updated the Javadoc documentation accordingly
  • I have added tests to cover my changes
  • All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.
  • My change is to implement 1.11 parity feature and I have updated LaunchChangelog

License

  • I confirm that this pull request can be released under the Apache 2 license

SimpleChannelPool#close() blocks until idle connections are closed, so it must
not run on an event loop. Complete the close promise on the event loop and close
the underlying connection pool on the caller thread after the wait; when close()
itself is called on an event loop, don't wait and close the connection pool on
GlobalEventExecutor instead. Fixes aws#6863.

This branch has not been deployed

No deployments
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.

Calling KinesisAsyncClient#close() throws BlockingOperationException

1 participant