Repository navigation
Conversation
Every completed HTTP/1.1 exchange paid for atomic operations that had nothing left to do, and every channel read paid for a fenced store: - finishUpdate cancels the timeouts and done() cancels them again, so the second getAndSet always swapped null for null. Read the field first, as takePartitionKeyLock already does; the getAndSet still decides who cancels a holder that is there. - done() offers the request back for release, but on the HTTP/1.1 path it was handed to the encoder, so the CAS always failed. No state returns to OWNED_BY_AHC, so a plain volatile read can rule the CAS out; the CAS still arbitrates the race it was added for. - touch() runs on every channelRead and stored the clock even when the millisecond had not moved. Skip the store when the value is already there; the field stays volatile and a skipped store would have written the same value. - loadContent publishes "no more retries" with a release store. The only other access is an atomic increment, which reads the latest value regardless of the store's ordering, and the completion that follows happens-after it. ResponseFutureCompletionBenchmark, JMH 1.37, JDK 17.0.2 on Windows, 3-5 forks: the second timeout cancellation 4.5-5.0 -> 0.45 ns, the release of a handed request 5.1 -> 0.45 ns, touch 7.2 -> 3.9 ns, cancel + done on a fresh future 225 -> 206 ns. Allocation unchanged. Claude Code on behalf of Pavel Ptashyts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hyperxpro
requested changes
Nov 5, 2026
Review feedback on AsyncHttpClient#2372. The release store for currentRetry saved about 5 ns per response and would have been the only relaxed store in NettyResponseFuture, so it goes back to a plain volatile set. The read before the CAS in NettyRequest.release() leaves the CAS as the only thing that keeps release() and markHandedToChannel() from both claiming the body, yet no test raced the two: replacing the CAS with a check and a store kept NettyRequestTest green. The new test starts both calls on two threads in lockstep for 20,000 rounds and checks that each body ends with exactly one owner. With the CAS swapped for a check and a store it fails in the first rounds. Local clean verify ran on JDK 17.0.2; CI covers JDK 11. Claude Code on behalf of Pavel Ptashyts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hyperxpro
approved these changes
Nov 6, 2026
pavel-ptashyts
added a commit
to maygemdev/async-http-client
that referenced
this pull request
Nov 6, 2026
Keeps the branch current with main at f9eafb9 (includes AsyncHttpClient#2372 and AsyncHttpClient#2380). Merged without conflicts. Claude Code on behalf of Pavel Ptashyts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Every completed HTTP/1.1 exchange paid for atomic operations that had nothing left to do, and every
channelRead(every body chunk) paid for a fenced store.Changes
NettyResponseFuture.cancelTimeouts():finishUpdatecancels the timeouts anddone()cancels them again, so the secondgetAndSetalways swappednullfornull. Read the field first, astakePartitionKeyLockalready does; thegetAndSetstill decides who cancels a holder that is there.NettyRequest.release():done()offers the request back for release, but on the HTTP/1.1 path it was handed to the encoder, so the CAS always failed. No state returns toOWNED_BY_AHC, so a plain volatile read can rule the CAS out; the CAS still arbitrates the abort-vs-write race it was added for.NettyResponseFuture.touch(): skip the store when the millisecond has not moved. The field stays volatile, and a skipped store would have written the value already there.No public API change.
Benchmark
New
ResponseFutureCompletionBenchmark(JMH 1.37, JDK 17.0.2, Windows, average time, 3-5 forks, base and patched runs interleaved over two rounds):cancelClearedTimeouts(the second cancellation)releaseHandedRequesttouch(perchannelRead)completeHandedRequest(cancel +done()on a fresh future)A release store for
currentRetryinloadContent()would save another 4.6 ns there; per review it stays a volatileset. Allocation is unchanged at 216 B/op. What remains oftouchisSystem.currentTimeMillis()itself.perfasmwas not available on this Windows host.Testing
NettyRequestTest:release()aftermarkHandedToChannel()leaves the buffer to the encoder; a released request cannot be handed to the channel.NettyRequestTest:release()andmarkHandedToChannel()raced on two threads in lockstep for 20,000 rounds leave each body with exactly one owner. Replacing the CAS inrelease()with a check and a store fails it in the first rounds.NettyResponseFutureTest: completion cancels the timeouts holder exactly once.mvnw.cmd clean verifyran locally on JDK 17.0.2 (no JDK 11 on the host): 1861 tests, 0 failures, Revapi clean. CI covers JDK 11.Claude Code on behalf of Pavel Ptashyts
🤖 Generated with Claude Code