Skip to content

Avoid per-connect lock on shared Bootstrap options map (#2218)#2219

Open
pavel-ptashyts wants to merge 2 commits into
AsyncHttpClient:mainfrom
maygemdev:bootstrap-lock-fix-issue-2218
Open

Avoid per-connect lock on shared Bootstrap options map (#2218)#2219
pavel-ptashyts wants to merge 2 commits into
AsyncHttpClient:mainfrom
maygemdev:bootstrap-lock-fix-issue-2218

Conversation

@pavel-ptashyts

@pavel-ptashyts pavel-ptashyts commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2218.

AsyncHttpClient reuses a single Bootstrap instance for every outbound
connection. On each connect, Netty's AbstractBootstrap.newOptionsArray()
copies the shared options map under synchronized (options):

synchronized (options) {
    return new ArrayList<Map.Entry<ChannelOption<?>, Object>>(options.entrySet())
            .toArray(EMPTY_OPTION_ARRAY);
}

Because the same Bootstrap (and its options map) backs every connection,
this is a single global monitor that all connection attempts contend on.
Under high connection-establishment rates — many concurrent outbound
requests, short-lived connections with low reuse, or no effective per-host
connection cap — threads queue on this lock and a convoy forms.

Fix

Stop configuring options on the Bootstrap. Instead:

  • Resolve the configured ChannelOptions once at construction into a
    fixed array (buildChannelOptions). Conditional options (connect timeout,
    SO_LINGER, send/receive buffers) and all config getters are evaluated a
    single time rather than per connection.
  • Apply that pre-resolved array to each channel from AHC's existing channel
    initializer via the public Channel.config().setOption(...) API
    (applyChannelOptions), leaving the Bootstrap options map empty.

This removes the global lock from the connect path entirely while preserving
identical behavior:

  • Same option values and ordering — user-supplied options are still
    applied last so they override defaults, matching the previous Bootstrap
    order.
  • Same timing — ChannelInitializer.initChannel runs during channel
    registration (handlerAdded, channel already registered), which is before
    the connect listener fires, so options are set before the channel connects.
    The connect-timeout option is honored because Netty reads it at connect time.
  • The SOCKS proxy path is covered automatically, since it installs the HTTP
    channel initializer into its pipeline.

Scope is limited to ChannelManager.

Benefits

  • Eliminates the lock convoy on the connect path.
  • Avoids re-reading config and rebuilding the options array per connection
    (fewer per-connect allocations).
  • No behavioral or configuration changes for users.

…nt#2218)

AHC reuses a single Bootstrap for every outbound connection. Netty's
AbstractBootstrap.newOptionsArray() copies the shared options map under
"synchronized (options)" on every connect, serializing all connection
attempts on one monitor. Under high connection-establishment rates this
becomes a lock convoy.

Resolve the configured ChannelOptions once at construction into a fixed
array and apply them to each channel from the existing channel
initializer via Channel.config().setOption(...), leaving the Bootstrap
options map empty. This removes the global lock from the connect path
and avoids re-reading config per connection, while preserving identical
option values, ordering, and timing (options are applied during channel
registration, before connect).
…nt#2218)

AHC reuses a single Bootstrap for every outbound connection. Netty's
AbstractBootstrap.newOptionsArray() copies the shared options map under
"synchronized (options)" on every connect, serializing all connection
attempts on one monitor. Under high connection-establishment rates this
becomes a lock convoy.

Resolve the configured ChannelOptions once at construction into a fixed
array and apply them to each channel from the existing channel
initializer via Channel.config().setOption(...), leaving the Bootstrap
options map empty. This removes the global lock from the connect path
and avoids re-reading config per connection, while preserving identical
option values, ordering, and timing (options are applied during channel
registration, before connect).
@pavel-ptashyts

Copy link
Copy Markdown
Contributor Author

Hi @hyperxpro could you check this PR. Thanks in advance

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.

AHC: per-connect lock contention on the shared Bootstrap options map

1 participant