Skip to content

Derive RPC readiness probe and shutdown host from rpc-bind-address - #327

Open
boomshadow wants to merge 1 commit into
linuxserver:masterfrom
boomshadow:fix-rpc-bind-address-readiness-probe
Open

Derive RPC readiness probe and shutdown host from rpc-bind-address#327
boomshadow wants to merge 1 commit into
linuxserver:masterfrom
boomshadow:fix-rpc-bind-address-readiness-probe

Conversation

@boomshadow

Copy link
Copy Markdown

linuxserver.io


  • I have read the contributing guideline and understand that I have made the correct modifications

closes #326

Description:

svc-transmission/run gates readiness on nc -z localhost ${PORT}, and svc-transmission/finish sends the graceful exit to 127.0.0.1:${PORT}. Both read rpc-port from settings.json but ignore rpc-bind-address.

This PR derives the host from rpc-bind-address in both scripts. Wildcard binds (0.0.0.0, ::, [::]) keep targeting loopback exactly as today, so the default configuration is unchanged.

Two details worth flagging for review:

  • finish now builds a full RPC URL. transmission-remote's host:port shorthand cannot express an IPv6 literal — given [fd00::5]:9091 it constructs http://[fd00::5]:9091:9091/transmission/rpc/ and fails with "URL using bad/illegal format". The URL form is the only one that works for IPv6. Since that means spelling out the path, it is taken from rpc-url rather than hardcoded, which also makes shutdown work for anyone who has customised that setting.
  • A timeout-finish file is added. There is currently none, so s6 SIGKILLs the finish script at its 5000 ms default. On a large library the daemon legitimately needs longer than that (~8–9 s for ~1300 torrents on my host; ~16 s when RPC is bound to a global IPv6 address), so tail --pid=${pid} gets killed before the daemon is actually gone. 30000 ms gives it room while still bounding a wedged finish script; Docker's stop timeout remains the outer bound. This was found while investigating the above and is included here because it is part of the same "container shuts down cleanly" story — happy to split it out if you would rather it stood alone.

Benefits of this PR and context:

Fixes #326. With a specific non-loopback rpc-bind-address the probe can never succeed, so:

  1. s6-notifyoncheck never signals readiness
  2. s6-svlisten1 -U -- /run/s6-rc/servicedirs/svc-transmission blocks forever
  3. rc.init top / s6-rc -v1 -u -t 0 -- change top never complete — init stays permanently in progress
  4. because init never finished, s6's shutdown path never delivers SIGTERM to the daemon
  5. every docker stop ends in SIGKILL — no resume data flushed, no "stopped" tracker announce

The failure is silent: the daemon is healthy and serving RPC the whole time, and nothing in the logs indicates a problem. It is invisible on the default config because localhost matches the wildcard socket, which is likely why it has gone unreported. Note that the workaround currently being suggested in #324 (hand-editing rpc-bind-address) is a route into it.

How Has This Been Tested?

Tested against ghcr.io/linuxserver/transmission:4.1.3@sha256:6b78970a0cb00709d3817fcb47b7cf74da56a43384bbd50d03955ca87e38c5f0 (4.1.3-r0-ls356) with the patched run, finish and timeout-finish bind-mounted over the servicedir, on both an IPv4 bridge network and a dual-stack one. Measured init completion ([ls.io-init] done.) and docker stop duration/exit code.

Before (stock image), rpc-bind-address: 172.30.0.5:

result
init completed no — still running >60 s
docker stop -t 30 31 s, exit 137
shutdown log lines none — daemon never signalled

After, across every bind form:

rpc-bind-address network init docker stop exit graceful exit acked
172.30.0.5 (specific v4) IPv4 ~3 s 4 s 0 yes
127.0.0.1 IPv4 ~3 s 4 s 0 yes
0.0.0.0 IPv4 ~2 s 4 s 0 yes
[::] (current default) IPv4 ~2 s 4 s 0 yes
fd00:dead:beef::5 (specific v6) dual-stack ~2 s 20 s 0 yes
[fd00:dead:beef::5] (bracketed) dual-stack ~2 s 20 s 0 yes

Control: the stock image with the default [::] gives ~2 s init / 4 s stop / exit 0 — identical to patched, confirming no change to existing behaviour.

"graceful exit acked" = transmission-remote logs <addr>:9091/transmission/rpc/ acknowledged notification and the daemon logs Closing transmission session... done.

The 20 s in the two IPv6 rows is transmission-daemon itself taking ~16 s to finish exiting once bound to a global IPv6 address — the daemon logs shutting down ~1 s after the exit request, then the process lingers. That is outside these scripts (and is precisely the case the timeout-finish addition covers); on stock, that configuration never gets that far at all.

Also verified shellcheck reports no new findings on either script (the one remaining SC2086 info on tail --pid=${pid} is pre-existing and untouched), and regenerated README.md with lscr.io/linuxserver/jenkins-builder after adding the changelog entry.

Source / References:

The readiness probe in svc-transmission/run and the graceful shutdown in
svc-transmission/finish both hardcoded localhost. When rpc-bind-address is a
specific non-loopback address, `nc -z localhost` can never succeed, so
s6-notifyoncheck never signals readiness, container init never completes, and
s6 therefore never delivers SIGTERM to transmission-daemon -- every docker stop
ends in SIGKILL with no resume data flushed and no "stopped" tracker announce.

Derive the host from rpc-bind-address in both scripts, keeping wildcard binds
(0.0.0.0, ::, [::]) on loopback as before. finish now builds a full RPC URL,
since transmission-remote's host:port shorthand cannot express an IPv6 literal;
the path comes from rpc-url rather than being hardcoded.

Also add a timeout-finish, as s6 otherwise SIGKILLs the finish script at its
5000ms default while a large library legitimately needs longer to shut down.

closes linuxserver#326

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening this pull request! Be sure to follow the pull request template!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

[BUG] RPC readiness probe hardcodes localhost, so a non-loopback rpc-bind-address leaves container init incomplete and every stop is a SIGKILL

2 participants