Skip to content

Commit

Permalink
Add "warmup mode" for RPC server.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
domob1812 committed Nov 4, 2014
1 parent be32b52 commit af82884
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 32 deletions.
11 changes: 11 additions & 0 deletions doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
85 changes: 55 additions & 30 deletions src/bitcoin-cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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[])
{
//
Expand Down Expand Up @@ -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"]);
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 12 additions & 2 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1248,8 +1259,6 @@ bool AppInit2(boost::thread_group& threadGroup)
#endif

StartNode(threadGroup);
if (fServer)
StartRPCThreads();

#ifdef ENABLE_WALLET
// Generate coins in the background
Expand All @@ -1259,6 +1268,7 @@ bool AppInit2(boost::thread_group& threadGroup)

// ********************************************************* Step 11: finished

SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading"));

#ifdef ENABLE_WALLET
Expand Down
1 change: 1 addition & 0 deletions src/rpcprotocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
24 changes: 24 additions & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit af82884

Please sign in to comment.