ltc: Send() socket deadline + desync-safe teardown (parity with dash #781)#784
Merged
Conversation
…781) LTC (the live LTC-DOGE prod reference) already has the thread_pool decouple and m_rpc_mutex, but NodeRPC::Send() had no deadline: a wedged-but-connected litecoind/dogecoind (socket open, no bytes) hangs the blocking http::read forever -> permanent refresh-wedge + SIGINT-hang on join. Port DASH #781's mechanism verbatim into impl/ltc/coin/rpc.cpp: - apply_socket_timeouts(): force the socket back to BLOCKING mode (async_connect leaves asio's non_blocking flag set, under which SO_*TIMEO is a no-op) then set kernel SO_RCVTIMEO/SO_SNDTIMEO = RPC_IO_TIMEOUT_SECONDS (12). Called after every successful (re)connect. beast's synchronous read_some/write_some do not honour tcp_stream::expires_after (async-only), so the kernel timeout is the robust bound. #ifndef _WIN32 guarded (Linux release target; Windows keeps pre-parity no-deadline behaviour). - close_stream(): the desync-safe teardown on the FINAL-attempt Send() exits (both write-fail and read-fail). This is the critical half: the deadline alone reintroduces the off-by-one response desync. LTC's NodeRPC shares DASH's shape -- the constant id "curltest" driving jsonrpccxx, which never validates response ids -- so a timed-out-but-open stream reused by the next Send() would read this request's late response (a GBT parsing a getbestblockhash string -> zeroed work -> a stuck honest set-gap outage). Tearing the socket down makes the next Send() write-fail into a clean sync_reconnect(). LTC already had m_rpc_mutex (class B), so no mutex work was needed. Transport/liveness only -- zero wire-byte change, consensus-neutral (no share/target/payout/vardiff logic touched).
frstrtr
marked this pull request as ready for review
July 21, 2026 07:50
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.
P1 — live LTC-DOGE prod reference: RPC socket deadline
Propagates the DASH #781 (
9ac8b3dd) socket-deadline class to LTC, the P1 lanein the cross-coin io/RPC reconciliation audit. LTC already has the class-A
thread_pooldecouple and the class-Bm_rpc_mutex; the remaining gap is theclass-C deadline.
Problem
ltc::coin::NodeRPC::Send()has no deadline — a wedged-but-connectedlitecoind/dogecoind (socket open, no bytes) hangs the blocking
http::readforever → permanent refresh-wedge + SIGINT-hang on join, on the live merged-
mining node.
Change — port DASH's mechanism verbatim
apply_socket_timeouts()— forces the socket back to BLOCKING mode(
async_connectleaves asio's non_blocking flag set, under whichSO_*TIMEOis a no-op) then sets kernel
SO_RCVTIMEO/SO_SNDTIMEO=RPC_IO_TIMEOUT_SECONDS(12). Called after every successful (re)connect (theasync_connecthandler beforecheck(), andsync_reconnect()). beast'ssynchronous
read_some/write_somedo not honourtcp_stream::expires_after(async-only), so the kernel timeout is the robustbound.
#ifndef _WIN32guarded (Linux release target; Windows keeps thepre-parity no-deadline behaviour).
close_stream()— desync-safe teardown on the final-attemptSend()exits (both write-fail and read-fail). This is the critical half — the
deadline alone reintroduces dash: decouple blocking coin-RPC + heavy work off the stratum io_context (io-thread-decouple) #781's item-2b off-by-one desync.
Why the desync applies to LTC (verified, not assumed)
LTC's
NodeRPCshares DASH's shape:const std::string ID = "curltest"(aconstant request id) driving
jsonrpccxx::JsonRpcClient, which never validatesresponse ids. A timed-out-but-still-open stream (request written, response
unread) reused by the next
Send()would read this request's late response→ permanent off-by-one: a GBT parses a
getbestblockhashhash-string → zeroedwork → a stuck "honest set-gap" outage until the connection churns.
close_stream()tears the socket down so the nextSend()write-fails into aclean
sync_reconnect(). LTC already hadm_rpc_mutex(class B) — no mutexwork needed.
Consensus-neutral
Transport/liveness only — zero wire-byte change; strict added-code diff-grep
for share/target/payout/vardiff/reward/coinbase/merkle/nbits/difficulty is
empty. Satisfies the pre-v36 transition rule.
Scope note — LTC class-D (WebServer) is a separate follow-on
LTC's miner-facing server is
core::WebServer, notcore::StratumServer,so the #781 core reap knobs do NOT reach LTC. Its zombie-session class needs a
WebServer-level keepalive + idle-reap fix — queued, out of scope for this PR
(class-C deadline only).
Build/test
c2pool-ltcbuilds clean with the change compiled in; LTC-lane regression(
test_template_builder35/35,test_doge_chain37/37) green.NodeRPCis notunit-testable without a live daemon socket.
Draft — not for merge/deploy; LTC-DOGE prod is an operator tap. Gets an
LTC-parity review first.