You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit fd9d2f3
Browse filesBrowse the repository at this point in the historyBrowse files
Review round one on the absolute deadline.
Clamping a spent deadline to zero only delayed the abort. The next hop
still took a connection permit, took a connection and wrote the request,
and the timeout arrived a tick later - so a 307 put its body on the
redirect target while the caller was handed a TimeoutException that
reads as though nothing had been sent. sendNextRequest, which every
redirect, auth replay and retry funnels through, now fails the exchange
before the write when the deadline has passed. Worse than reported, as
it turns out: on a coarse wheel the exchange did not merely abort late,
it ran both hops and succeeded, the deadline exceeded and nobody told.
The anchor was getStart(), which is currentTimeMillis. A per-attempt
timeout can only be distorted by a clock step for the length of one hop;
an anchor spanning a whole exchange carries the step to every hop after
it, so a correction back would hand a later hop a budget it never had
and one forward would abort it on a healthy connection. The future now
records System.nanoTime() at submission and the budget is netted off
that, leaving wall clock only where main already had it, per hop.
The comment justifying the clamp said the task still cancels its
read-timeout sibling. There is no sibling at that point: the read
timeout is armed after the write. The reason is simply that a scheduler
has no use for a negative delay and the task has to run, running being
what fails the exchange.
DefaultRequest's widened constructor is package private. Public, it
would be pinned by revapi at twenty-seven arguments, the next
per-request option would make it twenty-eight, and the two parameter
lists would have to be kept in step by hand with a tail that is all
reference types for the compiler to confuse. RequestBuilderBase#build is
the only caller.
Two fields were being dropped by hand-copied lists, which is the same
fault this change exists to fix. Redirect30xInterceptor rebuilds the
next request from a hand-picked set and carried neither the read timeout
- reverting a per-request value to the config default on every hop after
the first - nor the deadline flag, which left the Request disagreeing
with the behaviour a filter or a signature calculator would read off it.
RequestBuilderBase's signature-calculator copy block dropped the read
timeout the same way.
The interface default returns false rather than reading the property, as
every other option on it does; the javadoc now says so, so that a custom
config setting the property and getting nothing is documented rather
than surprising.
Three of the tests asserted only that nothing was thrown, which a
dropped Location header or redirects turned off would have satisfied
having run a single hop. They assert the final status and which hop it
came from. The five were also all wall clock, so TimeoutsHolderTest
covers the anchor and the clamp directly: what a second holder makes of
the same exchange is the whole difference between the two modes.
AsyncHttpClientDefaultsTest was asserting no default for this option,
nor for the event-loop one merged alongside it, and now does both.
Claude Code on behalf of Pavel Ptashyts
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0 commit comments