Skip to content

Commit 64a2795

Browse files
Sjorsryanofsky
andcommitted
rpc: handle shutdown during long poll and wait methods
The waitTipChanged() now returns nullopt if the node is shutting down. Previously it would return the last known tip during shutdown, but this creates an ambiguous circumstance in the scenario where the node is started and quickly shutdown, before notifications().TipBlock() is set. The getblocktemplate, waitfornewblock and waitforblockheight RPC are updated to handle this. Existing behavior is preserved. Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
1 parent a3bf433 commit 64a2795

File tree

4 files changed

+63
-28
lines changed

4 files changed

+63
-28
lines changed

src/interfaces/mining.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,16 @@ class Mining
8989

9090
/**
9191
* Waits for the connected tip to change. During node initialization, this will
92-
* wait until the tip is connected.
92+
* wait until the tip is connected (regardless of `timeout`).
9393
*
9494
* @param[in] current_tip block hash of the current chain tip. Function waits
9595
* for the chain tip to differ from this.
9696
* @param[in] timeout how long to wait for a new tip (default is forever)
9797
*
98-
* @returns Hash and height of the current chain tip after this call.
98+
* @retval BlockRef hash and height of the current chain tip after this call.
99+
* @retval std::nullopt if the node is shut down.
99100
*/
100-
virtual BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
101+
virtual std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout = MillisecondsDouble::max()) = 0;
101102

102103
/**
103104
* Construct a new block template

src/node/interfaces.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,22 +1070,34 @@ class MinerImpl : public Mining
10701070
return BlockRef{tip->GetBlockHash(), tip->nHeight};
10711071
}
10721072

1073-
BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
1073+
std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
10741074
{
10751075
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
10761076
if (timeout < 0ms) timeout = 0ms;
10771077
if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
1078+
auto deadline{std::chrono::steady_clock::now() + timeout};
10781079
{
10791080
WAIT_LOCK(notifications().m_tip_block_mutex, lock);
1080-
notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
1081-
// We need to wait for m_tip_block to be set AND for the value
1082-
// to differ from the current_tip value.
1083-
return (notifications().TipBlock() && notifications().TipBlock() != current_tip) || chainman().m_interrupt;
1081+
// For callers convenience, wait longer than the provided timeout
1082+
// during startup for the tip to be non-null. That way this function
1083+
// always returns valid tip information when possible and only
1084+
// returns null when shutting down, not when timing out.
1085+
notifications().m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
1086+
return notifications().TipBlock() || chainman().m_interrupt;
1087+
});
1088+
if (chainman().m_interrupt) return {};
1089+
// At this point TipBlock is set, so continue to wait until it is
1090+
// different then `current_tip` provided by caller.
1091+
notifications().m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
1092+
return Assume(notifications().TipBlock()) != current_tip || chainman().m_interrupt;
10841093
});
10851094
}
1086-
// Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
1087-
LOCK(::cs_main);
1088-
return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};
1095+
1096+
if (chainman().m_interrupt) return {};
1097+
1098+
// Must release m_tip_block_mutex before getTip() locks cs_main, to
1099+
// avoid deadlocks.
1100+
return getTip();
10891101
}
10901102

10911103
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override

src/rpc/blockchain.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
using kernel::CCoinsStats;
6565
using kernel::CoinStatsHashType;
6666

67+
using interfaces::BlockRef;
6768
using interfaces::Mining;
6869
using node::BlockManager;
6970
using node::NodeContext;
@@ -286,12 +287,17 @@ static RPCHelpMan waitfornewblock()
286287
NodeContext& node = EnsureAnyNodeContext(request.context);
287288
Mining& miner = EnsureMining(node);
288289

289-
auto block{CHECK_NONFATAL(miner.getTip()).value()};
290-
block = timeout ? miner.waitTipChanged(block.hash, std::chrono::milliseconds(timeout)) : miner.waitTipChanged(block.hash);
290+
// Abort if RPC came out of warmup too early
291+
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
292+
std::optional<BlockRef> block = timeout ? miner.waitTipChanged(current_block.hash, std::chrono::milliseconds(timeout)) :
293+
miner.waitTipChanged(current_block.hash);
294+
295+
// Return current block upon shutdown
296+
if (block) current_block = *block;
291297

292298
UniValue ret(UniValue::VOBJ);
293-
ret.pushKV("hash", block.hash.GetHex());
294-
ret.pushKV("height", block.height);
299+
ret.pushKV("hash", current_block.hash.GetHex());
300+
ret.pushKV("height", current_block.height);
295301
return ret;
296302
},
297303
};
@@ -330,22 +336,28 @@ static RPCHelpMan waitforblock()
330336
NodeContext& node = EnsureAnyNodeContext(request.context);
331337
Mining& miner = EnsureMining(node);
332338

333-
auto block{CHECK_NONFATAL(miner.getTip()).value()};
339+
// Abort if RPC came out of warmup too early
340+
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
341+
334342
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
335-
while (block.hash != hash) {
343+
while (current_block.hash != hash) {
344+
std::optional<BlockRef> block;
336345
if (timeout) {
337346
auto now{std::chrono::steady_clock::now()};
338347
if (now >= deadline) break;
339348
const MillisecondsDouble remaining{deadline - now};
340-
block = miner.waitTipChanged(block.hash, remaining);
349+
block = miner.waitTipChanged(current_block.hash, remaining);
341350
} else {
342-
block = miner.waitTipChanged(block.hash);
351+
block = miner.waitTipChanged(current_block.hash);
343352
}
353+
// Return current block upon shutdown
354+
if (!block) break;
355+
current_block = *block;
344356
}
345357

346358
UniValue ret(UniValue::VOBJ);
347-
ret.pushKV("hash", block.hash.GetHex());
348-
ret.pushKV("height", block.height);
359+
ret.pushKV("hash", current_block.hash.GetHex());
360+
ret.pushKV("height", current_block.height);
349361
return ret;
350362
},
351363
};
@@ -385,23 +397,29 @@ static RPCHelpMan waitforblockheight()
385397
NodeContext& node = EnsureAnyNodeContext(request.context);
386398
Mining& miner = EnsureMining(node);
387399

388-
auto block{CHECK_NONFATAL(miner.getTip()).value()};
400+
// Abort if RPC came out of warmup too early
401+
BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
402+
389403
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
390404

391-
while (block.height < height) {
405+
while (current_block.height < height) {
406+
std::optional<BlockRef> block;
392407
if (timeout) {
393408
auto now{std::chrono::steady_clock::now()};
394409
if (now >= deadline) break;
395410
const MillisecondsDouble remaining{deadline - now};
396-
block = miner.waitTipChanged(block.hash, remaining);
411+
block = miner.waitTipChanged(current_block.hash, remaining);
397412
} else {
398-
block = miner.waitTipChanged(block.hash);
413+
block = miner.waitTipChanged(current_block.hash);
399414
}
415+
// Return current block on shutdown
416+
if (!block) break;
417+
current_block = *block;
400418
}
401419

402420
UniValue ret(UniValue::VOBJ);
403-
ret.pushKV("hash", block.hash.GetHex());
404-
ret.pushKV("height", block.height);
421+
ret.pushKV("hash", current_block.hash.GetHex());
422+
ret.pushKV("height", current_block.height);
405423
return ret;
406424
},
407425
};

src/rpc/mining.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <memory>
4747
#include <stdint.h>
4848

49+
using interfaces::BlockRef;
4950
using interfaces::BlockTemplate;
5051
using interfaces::Mining;
5152
using node::BlockAssembler;
@@ -801,7 +802,10 @@ static RPCHelpMan getblocktemplate()
801802
{
802803
MillisecondsDouble checktxtime{std::chrono::minutes(1)};
803804
while (tip == hashWatchedChain && IsRPCRunning()) {
804-
tip = miner.waitTipChanged(hashWatchedChain, checktxtime).hash;
805+
std::optional<BlockRef> maybe_tip{miner.waitTipChanged(hashWatchedChain, checktxtime)};
806+
// Node is shutting down
807+
if (!maybe_tip) break;
808+
tip = maybe_tip->hash;
805809
// Timeout: Check transactions for update
806810
// without holding the mempool lock to avoid deadlocks
807811
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP)

0 commit comments

Comments
 (0)