Repository navigation
RFC: SIGTERM handling and a gradual SV1 drain on tProxy shutdown #914
average-gary
started this conversation in
Ideas
Replies: 1 comment 2 replies
|
I think the |
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
TL;DR. The three binaries only listen for SIGINT. Every supervisor sends SIGTERM, so a stop is never a clean shutdown. Fixing that is a small cross-app change. On top of it, tProxy could drain SV1 miners gradually instead of dropping them all at once, opt-in. Six things are proposed as settled (below, object if you disagree). Seven are open, each with its own thread.
Open questions
client.reconnectbefore closing each miner?/api/v1/healthreport "draining"?Reconnectalready exists (#780, #909)stop_signal: SIGINTfromdocker/docker-compose.yml?Please reply inside the thread rather than opening a new one.
Settled
main.rs, libraries keep usingCancellationToken(noise_connectioninstalls a process-wide SIGINT handler, breaking Ctrl+C for every consumer #688).drain_secs = 0is today'sshutdown().What exists today
All three binaries await
tokio::signal::ctrl_c()and nothing else: Pool, JDC, tProxy.The image
execs the app as PID 1. A PID 1 only receives signals it has installed handlers for (kill(2)), so SIGTERM fromdocker stop, systemd or an orchestrator is dropped and the process is SIGKILLed when the stop timeout expires (10s by default fordocker stop). Measured onstratumv2/translator_sv2:mainas built on 2026-10-06, with the upstream a TCP listener that never completes the handshake so the proxy is alive when the signal lands:docker stop -t 3--signal SIGINT(the compose workaround)--init, app is not PID 1, SIGTERMThe exit 1 on SIGINT is the existing bootstrap error path (
CouldNotInitiateSystem), not the signal handling. The compose file works around the dropped SIGTERM withstop_signal: SIGINTon all three services; outside compose there is no workaround. Any deployment with a PID-1 shim (--init,tini) gets the default action instead: immediate exit 143, noshutdown().Even a clean shutdown cancels one global token, so every SV1 miner behind a tProxy loses its connection in the same instant and reconnects in the same instant. One instance or several behind a load balancer, a restart is a reconnect storm.
Proposal
Part 1, cross-app, one PR. A
shutdown_signal()in eachmain.rsthat resolves on SIGINT or (Unix) SIGTERM and feeds the existingshutdown(). Behaviour change: SIGTERM goes from dropped-or-instant to graceful, bounded by the existing 5s runtime timeout. Thread 1 decides the exit status, thread 7 the compose lines.Part 2, tProxy only, one PR, sub-issue of #31. A
drain_secsconfig key. When set, a signal cancels a drain token: the SV1 listener closes at once; connected miners are disconnected one at a time, one per equal slot across the window less a small margin, while the upstream SV2 connection stays up; shutdown starts when the last miner leaves or the window elapses, whichever comes first. Second signal, upstream lost mid-drain, and signal during upstream bootstrap all shut down promptly. Paused-time unit tests cover slot spacing, early exit and the deadline; one integration test usesMockUpstreamand raw TCP SV1 clients (the primitive #18 asks for). Threads 2 to 5 shape the details.A prototype of both parts exists, enough to know the shape above is implementable and testable. Nothing about it is fixed; the threads decide what gets posted. Part 2 touches code that #909 rewrites, so it would rebase on whichever lands second.
Not in scope
SIGHUP as reload, SIGQUIT, and load balancing across replacement instances (a drain spreads reconnects in time, it does not steer them). The
ERROR … ChannelErrorReceiverline each disconnect logs today is pre-existing and stays.History
noise_connectioninstalls a process-wide SIGINT handler, breaking Ctrl+C for every consumer #688 / fix bad SIGINT patterns #689 move SIGINT handling out ofnoise_connectionintomain.rs. This proposal keeps it there.shutdown()can miss a concurrent teardown. A drain makes that interleaving routine; Part 2 carries the tProxy-side reorder with aRefs, not a close.shutdown()came from. tProxy: handle Reconnect and ChannelEndpointChanged without panicking #780 / harden tProxy channel state, difficulty targets and upstream endpoint changes #909 cover the opposite direction, upstream-initiated SV2Reconnect.All reactions