From e0ff19c5ed36cd69d0700eeb04d39b1a66fa07a2 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sat, 25 Sep 2021 19:59:45 -0400 Subject: [PATCH] Implement -rpcwait and -rpcwaittimeout This is a Bitcoin compatible implementation of -rpcwait and -rpcwaittimeout Specifying -rpcwait for the rpc client will cause it to wait for the time specified with -rpcwaittimeout (in seconds) with one second attempts to connect. If -rpcwaittimeout is specified as zero or less, the wait is indefinite. Note that the current default value of -rpcwaittimeout is zero. --- src/init.cpp | 9 +++++++++ src/rpc/client.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 0a0cda6b72..785726c6df 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -48,6 +48,10 @@ extern bool fUseFastIndex; // Dump addresses to banlist.dat every 5 minutes (300 s) static constexpr int DUMP_BANS_INTERVAL = 300; +// RPC client default timeout. +extern constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0; + + std::unique_ptr g_banman; /** @@ -534,6 +538,11 @@ void SetupServerArgs() ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcuser=", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); + argsman.AddArg("-rpcwait", "Wait for RPC server to start.", + ArgsManager::ALLOW_ANY, OptionsCategory::RPC); + argsman.AddArg("-rpcwaittimeout=", strprintf("Timeout in seconds to wait for the RPC server to start, or 0 for no " + "timeout. (default: %d)", DEFAULT_WAIT_CLIENT_TIMEOUT), + ArgsManager::ALLOW_INT, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcport=", strprintf("Listen for JSON-RPC connections on (default: %u, testnet: %u)", diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 796db8ff2a..d6994a34ac 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -31,6 +31,9 @@ using namespace std; using namespace boost; using namespace boost::asio; +/** The default timeout for an RPC client to wait for the RPC server to start in seconds */ +static constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0; + UniValue CallRPC(const string& strMethod, const UniValue& params) { if (!gArgs.IsArgSet("-rpcuser") || !gArgs.IsArgSet("-rpcpassword")) @@ -47,8 +50,28 @@ UniValue CallRPC(const string& strMethod, const UniValue& params) asio::ssl::stream sslStream(io_context, context); SSLIOStreamDevice d(sslStream, fUseSSL); iostreams::stream< SSLIOStreamDevice > stream(d); - if (!d.connect(gArgs.GetArg("-rpcconnect", "127.0.0.1"), gArgs.GetArg("-rpcport", ToString(GetDefaultRPCPort())))) - throw runtime_error("couldn't connect to server"); + + bool fWait = gArgs.GetBoolArg("-rpcwait", false); // -rpcwait means try until server has started or timeout is reached + int timeout = gArgs.GetArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT); // The max time to wait + + std::chrono::seconds deadline = GetTime() + std::chrono::seconds{timeout}; + + do { + // If connection succeeds, immediately break. No need to wait. + if (d.connect(gArgs.GetArg("-rpcconnect", "127.0.0.1"), + gArgs.GetArg("-rpcport", ToString(GetDefaultRPCPort())))) { + break; + } + + std::chrono::seconds now = GetTime(); + + // Note that timeout <= 0 means wait until connected with one second between connection attempts. + if (fWait && (timeout <= 0 || now < deadline)) { + UninterruptibleSleep(std::chrono::seconds{1}); + } else { + throw runtime_error("couldn't connect to server"); + } + } while (true); // HTTP basic authentication string strUserPass64 = EncodeBase64(gArgs.GetArg("-rpcuser", "dummy") + ":" + gArgs.GetArg("-rpcpassword", "dummy"));