Repository navigation
fix(zombie): cap zombie detection by the latest send time (0.7.3, hot-patchable) - #72
Merged
Merged
Conversation
Zombie detection fired at request timeout + max_inactive after the
newest send, so it scaled with the caller's timeout. With a 600 s
request timeout a dead connection was reconnected only after 610 s.
Requests with an infinity timeout never recorded an expiry, so
detection never fired for them.
put_sent_req/3 now records the send time in the process dictionary
under last_sent_ts. When it is set and requests are in flight,
check_gun_jammed/3 also reconnects when the latest send is older than
max(max_inactive, ?INACTIVE_CAP), with the cap at 60 s. The existing
expiry check stays, so detection is never later than before.
The send time lives in the process dictionary, not in #state{} or the
requests map, so the worker state and message formats do not change.
The module can then be hot-patched by loading the beam into a running
node. A worker that has not sent since the load has no last_sent_ts and
uses only the expiry check.
The reconnect branch no longer asserts that an expiry was recorded,
because a pool of infinity-timeout requests now reaches it with none.
The zombie log adds the latest send time and the cap.
Under TEST the cap is 2 s, so the tests exercise it in seconds.
Refs #70
zmstone
marked this pull request as ready for review
September 25, 2026 19:51
The workflow ran only for pushes to and pull requests against main, so pull requests against patch branches such as patch-0.7.3 got no CI run. Match patch-* as well.
zmstone
force-pushed
the
260925-0.7.3-hotpatch-zombie-cap
branch
from
September 25, 2026 19:57
2cf696c to
29d9cec
Compare
thalesmg
approved these changes
Sep 25, 2026
| -ifdef(TEST). | ||
| -define(INACTIVE_CAP, 2_000). | ||
| -else. | ||
| -define(INACTIVE_CAP, 60_000). |
Contributor
There was a problem hiding this comment.
since max_inactive is always finite, instead of hardcoding the limit at 60 s, would it make sense to use max_inactive as the cap in max_expire when the request ttl is infinity?
Member
Author
There was a problem hiding this comment.
the problem is max_inactive might be too small.
e.g. max_inactive is set to 5s (real config from the reporter), for large payload, if it takes longer than 5s to send, then the request is doomed to expire.
2 of 3 tasks
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.
Refs #70. A hot-patchable variant of #71 for
patch-0.7.3.Why
Zombie-connection detection fires at
request timeout + max_inactiveafter the latest send. A production deployment runs with a 600 s request timeout, so a dead connection is reconnected only after 610 s. Requests with aninfinitytimeout never record an expiry, so detection never fires for them.What
put_sent_req/3records the send time in the process dictionary underlast_sent_ts.check_gun_jammed/3readslast_sent_ts. When it is an integer and requests are in flight, the connection is also reconnected whennow - last_sent_ts > max(max_inactive, ?INACTIVE_CAP).?INACTIVE_CAPis 60 s.last_sent_tsisundefined, only the existing expiry check runs.The combined rule is the clamp from #71,
max(MaxInactive, min(Cap, Timeout + MaxInactive))after the latest send, written without a stored timeout:max_inactiveinfinityHot patch
#state{}, therequestsmap and all message formats are unchanged. The module can be loaded into a running node withcode:load_file/1orc:nl/1, and nocode_changeis needed. A worker that has not sent a request since the load has nolast_sent_ts, so it uses only the expiry check until its next send.The reconnect branch in
maybe_shoot/1no longer assertsMaxExpire > 0, because a pool ofinfinity-timeout requests now reaches it with no expiry recorded. The zombie log addslast_request_sent_atandinactive_duration_cap.Incompatible case
A response that legitimately takes longer than
max(max_inactive, 60s)now has its connection killed at that point. Received data does not count as activity. Raisemax_inactiveto the slowest expected response.Tests
?INACTIVE_CAPis 2 s in theTESTbuild. With the defaultmax_inactiveof 10 s the effective cap stays 10 s, so the other tests are not affected.zombie_detect_long_timeout_test:max_inactive = 1s, request timeout 30 s, server never answers.reconnectfires between 2 s and 3.5 s.zombie_detect_infinity_timeout_test: the same, with request timeoutinfinity.zombie_detect_inflight_*tests pass unchanged.Both new tests fail against unpatched
patch-0.7.3.Run locally on OTP 28:
make fmt-check,./check-style.sh,make compile,make xref,make dialyzer,make eunit.make eunithas 9 failures, all inehttpc_google_tests:proxy_test_, becausetinyproxyis not installed on the host. The hot load itself was not tested on a live node.CI
.github/workflows/erlang.ymlran only formain, so this PR got no CI run. A separateci:commit adds'patch-*'to thepushandpull_requestbranch filters.