Federation link keeps a dead downstream channel when it has nothing buffered

Author: lukebakkenCreated Sep 13, 2026Updated Sep 13, 2026
Labelsbugeffort-mediumrabbitmq-federation

[!NOTE] This issue was drafted by Claude (Anthropic's Claude Code) under the direction of @lukebakken. The code references were verified against the branch of #17238. The behaviour predates that pull request, which fixes only the buffering case.

rabbit_federation_link_util:handle_downstream_down/3 treats a clean downstream channel death as survivable:

erlang
handle_downstream_down(normal, _Args, State) -> {noreply, State};
handle_downstream_down(shutdown, _Args, State) -> {noreply, State};

The premise is that a clean reason means the link is being shut down anyway. That is not true in general: a vhost restart, or rabbitmqctl close_connection on the link's downstream connection, produces shutdown while the link is expected to keep running. The link then holds a dead pid in downstream_channel and still reports running.

What happens to the next delivery depends on ack-mode:

  • on-publish: amqp_channel:cast_flow/2 to a dead pid is a silent no-op, and the next line acknowledges the delivery upstream. The message is destroyed with no log line.
  • on-confirm, the default: amqp_channel:next_publish_seqno/1 is a gen_server:call to a dead pid, so the link exits {noproc, ...} and supervisor2 logs a crash report.

There is also a credit_flow consequence. credit_flow:peer_down/1 has already erased the peer, so once the initial credit window is used up credit_flow:send/1 blocks against a peer that can never send bump_credit.

#17238 fixes this for a link that is blocked or holding buffered deliveries, by restarting instead of surviving. It deliberately leaves the idle case alone, because changing when links restart has a wider blast radius than that pull request should take on.

The condition the original premise was reaching for already exists as rabbit_federation_app_state:is_shutting_down/0. Guarding on that, rather than on the exit reason, would cover both cases.

Source: rabbitmq/rabbitmq-server