Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/json_rpc_request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,17 @@ int CurlContext::on_timer(CURLM* /*multi*/, long timeout_ms)
}

if (timeout_ms < 0) {
uv_timer_stop(m_timer);
return 0;
// curl says it needs no timeout right now -- but do NOT stop the timer
// entirely. If this request's socket poll later dies (e.g. uv_poll returns
// EBADF), no socket events arrive and, with the timer stopped,
// curl_multi_socket_action() is never called again. Then even the
// per-request CURLOPT_TIMEOUT is never enforced and the transfer hangs
// forever: it neither completes nor errors, so its completion callback
// never fires. For get_miner_data that leaves m_getMinerDataPending stuck
// true and deadlocks the whole node (it serves stale templates and gets
// banned by peers). Keep a slow periodic tick instead, so curl always
// re-checks the transfer and can time it out and complete normally.
timeout_ms = 5000;
}

const int result = uv_timer_start(m_timer, on_timeout, timeout_ms, 0);
Expand Down
18 changes: 17 additions & 1 deletion src/p2pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1689,10 +1689,24 @@ void p2pool::parse_get_version_rpc(const char* data, size_t size)

void p2pool::get_miner_data(bool retry)
{
// Only one get_miner_data request is normally in flight at a time. But if a
// request wedges -- its JSONRPCRequest completion callback never fires (e.g.
// uv_poll returns EBADF on the curl socket, orphaning the request so it
// neither completes nor times out) -- m_getMinerDataPending would stay true
// forever, suppressing every future poll. The node then serves stale
// templates and gets banned by peers. Guard against that: a healthy request
// completes in milliseconds, so if one has been "pending" for far longer,
// treat it as wedged and issue a fresh request instead of returning.
constexpr uint64_t STUCK_TIMEOUT = 30; // seconds
if (m_getMinerDataPending) {
return;
const uint64_t now = seconds_since_epoch();
if (!m_getMinerDataPendingSince || (now < m_getMinerDataPendingSince + STUCK_TIMEOUT)) {
return;
}
LOGWARN(1, "get_miner_data has been pending for " << (now - m_getMinerDataPendingSince) << " seconds (RPC wedged); forcing a new request");
}
m_getMinerDataPending = true;
m_getMinerDataPendingSince = seconds_since_epoch();

const Params::Host& host = current_host();

Expand All @@ -1709,6 +1723,7 @@ void p2pool::get_miner_data(bool retry)
if (!m_stopped && retry) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
m_getMinerDataPending = false;
m_getMinerDataPendingSince = 0;
get_miner_data();
return;
}
Expand All @@ -1722,6 +1737,7 @@ void p2pool::get_miner_data(bool retry)
#endif

m_getMinerDataPending = false;
m_getMinerDataPendingSince = 0;
});
}

Expand Down
6 changes: 6 additions & 0 deletions src/p2pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ class p2pool : public MinerCallbackHandler, public nocopy_nomove

hash m_getMinerDataHash;
bool m_getMinerDataPending = false;
// When the current get_miner_data request was issued. Used to break the
// overlap guard if the underlying JSONRPCRequest wedges (its completion
// callback never fires, e.g. uv_poll EBADF on the curl socket), which would
// otherwise leave m_getMinerDataPending stuck true and suppress all further
// polls forever -> the node serves stale templates and gets banned by peers.
uint64_t m_getMinerDataPendingSince = 0;

std::atomic<uint64_t> m_lastMinerDataReceived;

Expand Down
Loading