fix(controller): never close() the primary channel's raw fd (bug-1865)

handle_ctrlhello stored the netio endpoint's fd (primary_ctrl_fd = ep->fd)
and ha_on_primary_ctrl_fd_rdhup close()d it in three branches. netio owns
that socket and closes it on endpoint teardown, so by the time the
liveness poll fired the RDHUP the number could be reused -- in a chaotic
soak by the standby's WAL segment: HA's close() shut the WAL, fdatasync
failed 2305 times (EBADF, then EINVAL once a socket took the number),
durable_lsn stuck, and one round's jobsub failed EIO.

handle_ctrlhello now also holds a reference on the endpoint
(primary_ctrl_ep), which keeps its socket open and its fd number ours.
ha_drop_primary_ctrl_conn() replaces the raw closes: shutdown() the
still-owned socket so netio sees the hang-up and tears it down itself,
then release the reference. A bare fd with no endpoint is only forgotten.

Test test_rdhup_never_closes_an_fd_netio_already_released: real
ctrlhello -> netio teardown -> file opened on the freed number -> RDHUP;
the file was closed before, survives now. test_rdhup_closes_and_nulls_fd
updated to the new contract (peer reads EOF, fields cleared). Controller
suite 5/5.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-25 14:55:04 +00:00
parent 98c52b7d96
commit 910b43cd37
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
5 changed files with 127 additions and 22 deletions

View file

@ -2190,6 +2190,11 @@ int handle_ctrlhello(cluster_t *c, endpoint_t *ep, xce_request_t *req)
memory_order_acquire) == CTRL_STANDBY &&
c->ha.primary_ctrl_fd == -1) {
c->ha.primary_ctrl_fd = ep->fd;
/* bug-1865: hold the endpoint so netio cannot close (and the kernel
* reuse) this fd while HA treats it as the primary channel; released
* by ha_drop_primary_ctrl_conn */
endpoint_ref(ep);
c->ha.primary_ctrl_ep = ep;
/* bug-1701: ha->primary_host/primary_port (aliased into a redirect
* reply by fill_redirect_reply(), join.c:27) were never written by