aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Kraft <d@domob.eu>2014-10-29 18:08:31 +0100
committerDaniel Kraft <d@domob.eu>2014-11-04 16:01:09 +0100
commitaf82884ab7c485c8b4c5ac93c308127c39c196be (patch)
tree766428da14b53e4902c87fd3fb05a1c0d1acd87a
parentbe32b5212b6ab4460080ec5ff55e2bf882259e5e (diff)
downloadbitcoin-af82884ab7c485c8b4c5ac93c308127c39c196be.tar.xz
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.
-rw-r--r--doc/release-notes.md11
-rw-r--r--src/bitcoin-cli.cpp85
-rw-r--r--src/init.cpp14
-rw-r--r--src/rpcprotocol.h1
-rw-r--r--src/rpcserver.cpp24
-rw-r--r--src/rpcserver.h7
6 files changed, 110 insertions, 32 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<asio::ip::tcp> d(sslStream, fUseSSL);
iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > 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<std::string> 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 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<string, boost::shared_ptr<deadline_timer> > 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<void(void)> 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.