Probe a gen_rpc channel after a call timeout, drop it if silent (T-613) - #445
Merged
chasers merged 1 commit intoOct 2, 2026
Conversation
chasers
added this pull request to stack #446
October 2, 2026 13:45
A call can time out to a node distribution still lists, for a partition
distribution has not noticed yet. Layer 1 only reacts to nodedown, so
that client would keep its dead socket until the kernel gives up. A
timeout alone does not prove the socket is dead, though: the remote
operation may just be slow.
On {:badrpc, :timeout}, the buffer transport and the scatter transport now
hand the destination to RpcClients.check/2. It takes the destination's
current client and calls Smolquery.Cluster.RpcProbe.pong/0 on it. gen_rpc
runs each call in its own process on the remote side, so a slow operation
does not hold the probe up. No answer within probe_timeout_ms kills that
client, the one probed; any answer, an error included, keeps it. A
destination with no client is not probed, so a check never dials. One
probe runs per destination at a time, and the caller never waits for it.
probe_timeout_ms defaults to 10 s, above gen_rpc's 5 s send_timeout: the
probe queues behind sends on the client, and a send stuck on a slow
reader fails the client by itself first.
RpcProbe joins gen_rpc's rpc_module_list. It only answers :pong.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chasers
force-pushed
the
t-613-probe-stale-channel-on-timeout
branch
from
October 2, 2026 14:11
3b134e9 to
821b092
Compare
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.
TL;DR: After a gen_rpc call times out, probe that channel's client. If the probe gets no answer, kill that client.
Tracker: T-613. Stacked on #444 (T-613).
Why
:nodedown.Node.list/0, for example during a partition distribution has not seen yet.What changed
Smolquery.Cluster.RpcProbe.pong/0. It returns:pong. Nothing else.RpcProbeis added to gen_rpc'srpc_module_list.RpcClients.check/2. It returns at once and probes in the background.Transport.GenRpccallscheck/2on{:badrpc, :timeout}.QueryService.WorkerTransportcallscheck/2on{:badrpc, :timeout}.How it works
{node, key}returns{:badrpc, :timeout}.RpcClients.RpcClientsspawns one probe process for that destination.RpcProbe.pong/0on that destination, with a 10 s timeout.Why 10 s
send_timeout(5 s). Then the client fails and stops by itself.Why a slow call keeps its socket
Tests
check/2kills the:controlclient and keeps:bulkand:scatter.:timer.sleepruns on:control. The probe runs during it. The client stays.check/2does not start one.Review
Checks
mix precommit: 3073 tests passmix cimix dialyzergen_rpc_test,worker_transport_peer_test,multi_node_ring_test) passWatch out
recv_octis not reachable without reading gen_rpc internals.check/2. That path is two lines in each transport.RpcProbeon its allowlist. It answers{:badrpc, :unauthorized}. That counts as an answer, so the client stays.{:badrpc, {:unknown_error, _}}, which holds the request. Normalising it is T-616.🤖 Generated with Claude Code