From af82884ab7c485c8b4c5ac93c308127c39c196be Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Wed, 29 Oct 2014 18:08:31 +0100 Subject: [PATCH] Add "warmup mode" for RPC server. Start the RPC server before doing all the (expensive) startup initialisations like loading the block index. Until the node is ready, return all calls immediately with a new error signalling "in warmup" with an appropriate status message (similar to the init message). This is useful for RPC clients to know that the server is there (e. g., they don't have to start it) but not yet available. It is used in Namecoin and Huntercoin already for some time, and there exists a UI hooked onto the RPC interface that actively uses this to its advantage. --- doc/release-notes.md | 11 ++++++ src/bitcoin-cli.cpp | 81 +++++++++++++++++++++++++++++--------------- src/init.cpp | 14 ++++++-- src/rpcprotocol.h | 1 + src/rpcserver.cpp | 24 +++++++++++++ src/rpcserver.h | 7 ++++ 6 files changed, 108 insertions(+), 30 deletions(-) diff --git a/doc/release-notes.md b/doc/release-notes.md index 169ad71a0f..6aaea67790 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -84,3 +84,14 @@ Using wildcards will result in the rule being rejected with the following error Error: Invalid -rpcallowip subnet specification: *. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). +RPC Server "Warm-Up" Mode +========================= + +The RPC server is started earlier now, before most of the expensive +intialisations like loading the block index. It is available now almost +immediately after starting the process. However, until all initialisations +are done, it always returns an immediate error with code -28 to all calls. + +This new behaviour can be useful for clients to know that a server is already +started and will be available soon (for instance, so that they do not +have to start it themselves). diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 38fbc29faf..11840e62a8 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -46,6 +46,21 @@ std::string HelpMessageCli() // // Start // + +// +// Exception thrown on connection error. This error is used to determine +// when to wait if -rpcwait is given. +// +class CConnectionFailed : public std::runtime_error +{ +public: + + explicit inline CConnectionFailed(const std::string& msg) : + std::runtime_error(msg) + {} + +}; + static bool AppInitRPC(int argc, char* argv[]) { // @@ -101,15 +116,9 @@ Object CallRPC(const string& strMethod, const Array& params) SSLIOStreamDevice d(sslStream, fUseSSL); iostreams::stream< SSLIOStreamDevice > stream(d); - bool fWait = GetBoolArg("-rpcwait", false); // -rpcwait means try until server has started - do { - bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort()))); - if (fConnected) break; - if (fWait) - MilliSleep(1000); - else - throw runtime_error("couldn't connect to server"); - } while (fWait); + const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort()))); + if (!fConnected) + throw CConnectionFailed("couldn't connect to server"); // HTTP basic authentication string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); @@ -168,27 +177,43 @@ int CommandLineRPC(int argc, char *argv[]) std::vector strParams(&argv[2], &argv[argc]); Array params = RPCConvertValues(strMethod, strParams); - // Execute - Object reply = CallRPC(strMethod, params); + // Execute and handle connection failures with -rpcwait + const bool fWait = GetBoolArg("-rpcwait", false); + do { + try { + const Object reply = CallRPC(strMethod, params); - // Parse reply - const Value& result = find_value(reply, "result"); - const Value& error = find_value(reply, "error"); + // Parse reply + const Value& result = find_value(reply, "result"); + const Value& error = find_value(reply, "error"); - if (error.type() != null_type) { - // Error - strPrint = "error: " + write_string(error, false); - int code = find_value(error.get_obj(), "code").get_int(); - nRet = abs(code); - } else { - // Result - if (result.type() == null_type) - strPrint = ""; - else if (result.type() == str_type) - strPrint = result.get_str(); - else - strPrint = write_string(result, true); - } + if (error.type() != null_type) { + // Error + const int code = find_value(error.get_obj(), "code").get_int(); + if (fWait && code == RPC_IN_WARMUP) + throw CConnectionFailed("server in warmup"); + strPrint = "error: " + write_string(error, false); + nRet = abs(code); + } else { + // Result + if (result.type() == null_type) + strPrint = ""; + else if (result.type() == str_type) + strPrint = result.get_str(); + else + strPrint = write_string(result, true); + } + + // Connection succeeded, no need to retry. + break; + } + catch (const CConnectionFailed& e) { + if (fWait) + MilliSleep(1000); + else + throw; + } + } while (fWait); } catch (boost::thread_interrupted) { throw; diff --git a/src/init.cpp b/src/init.cpp index d622af69ef..fe58c68fda 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -752,6 +752,17 @@ bool AppInit2(boost::thread_group& threadGroup) threadGroup.create_thread(&ThreadScriptCheck); } + /* Start the RPC server already. It will be started in "warmup" mode + * and not really process calls already (but it will signify connections + * that the server is there and will be ready later). Warmup mode will + * be disabled when initialisation is finished. + */ + if (fServer) + { + uiInterface.InitMessage.connect(SetRPCWarmupStatus); + StartRPCThreads(); + } + int64_t nStart; // ********************************************************* Step 5: verify wallet database integrity @@ -1248,8 +1259,6 @@ bool AppInit2(boost::thread_group& threadGroup) #endif StartNode(threadGroup); - if (fServer) - StartRPCThreads(); #ifdef ENABLE_WALLET // Generate coins in the background @@ -1259,6 +1268,7 @@ bool AppInit2(boost::thread_group& threadGroup) // ********************************************************* Step 11: finished + SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading")); #ifdef ENABLE_WALLET diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 9926daaf3d..f0d0f3445c 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -52,6 +52,7 @@ enum RPCErrorCode RPC_VERIFY_ERROR = -25, // General error during transaction or block submission RPC_VERIFY_REJECTED = -26, // Transaction or block was rejected by network rules RPC_VERIFY_ALREADY_IN_CHAIN = -27, // Transaction already in chain + RPC_IN_WARMUP = -28, // Client still warming up // Aliases for backward compatibility RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR, diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 08ed73f6de..cc80887ba4 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -34,6 +34,10 @@ using namespace std; static std::string strRPCUserColonPass; static bool fRPCRunning = false; +static bool fRPCInWarmup = true; +static std::string rpcWarmupStatus("RPC server started"); +static CCriticalSection cs_rpcWarmup; + //! These are created by StartRPCThreads, destroyed in StopRPCThreads static asio::io_service* rpc_io_service = NULL; static map > deadlineTimers; @@ -744,6 +748,19 @@ bool IsRPCRunning() return fRPCRunning; } +void SetRPCWarmupStatus(const std::string& newStatus) +{ + LOCK(cs_rpcWarmup); + rpcWarmupStatus = newStatus; +} + +void SetRPCWarmupFinished() +{ + LOCK(cs_rpcWarmup); + assert(fRPCInWarmup); + fRPCInWarmup = false; +} + void RPCRunHandler(const boost::system::error_code& err, boost::function func) { if (!err) @@ -870,6 +887,13 @@ static bool HTTPReq_JSONRPC(AcceptedConnection *conn, if (!read_string(strRequest, valRequest)) throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); + // Return immediately if in warmup + { + LOCK(cs_rpcWarmup); + if (fRPCInWarmup) + throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); + } + string strReply; // singleton request diff --git a/src/rpcserver.h b/src/rpcserver.h index 2f34b11d22..2a258dd89a 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -45,6 +45,13 @@ void StopRPCThreads(); /* Query whether RPC is running */ bool IsRPCRunning(); +/* Set the RPC warmup status. When this is done, all RPC calls will error out + * immediately with RPC_IN_WARMUP. + */ +void SetRPCWarmupStatus(const std::string& newStatus); +/* Mark warmup as done. RPC calls will be processed from now on. */ +void SetRPCWarmupFinished(); + /** * Type-check arguments; throws JSONRPCError if wrong type given. Does not check that * the right number of arguments are passed, just that any passed are the correct type.