diff --git a/doc/release-notes.md b/doc/release-notes.md index 169ad71a0..6aaea6779 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 38fbc29fa..11840e62a 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); - - // 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); - } + // 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"); + + 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 d622af69e..fe58c68fd 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 9926daaf3..f0d0f3445 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 08ed73f6d..cc80887ba 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 2f34b11d2..2a258dd89 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.