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.
This commit is contained in:
parent
be32b5212b
commit
af82884ab7
6 changed files with 108 additions and 30 deletions
|
@ -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).
|
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).
|
||||||
|
|
|
@ -46,6 +46,21 @@ std::string HelpMessageCli()
|
||||||
//
|
//
|
||||||
// Start
|
// 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[])
|
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);
|
SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL);
|
||||||
iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d);
|
iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d);
|
||||||
|
|
||||||
bool fWait = GetBoolArg("-rpcwait", false); // -rpcwait means try until server has started
|
const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort())));
|
||||||
do {
|
if (!fConnected)
|
||||||
bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort())));
|
throw CConnectionFailed("couldn't connect to server");
|
||||||
if (fConnected) break;
|
|
||||||
if (fWait)
|
|
||||||
MilliSleep(1000);
|
|
||||||
else
|
|
||||||
throw runtime_error("couldn't connect to server");
|
|
||||||
} while (fWait);
|
|
||||||
|
|
||||||
// HTTP basic authentication
|
// HTTP basic authentication
|
||||||
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);
|
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]);
|
std::vector<std::string> strParams(&argv[2], &argv[argc]);
|
||||||
Array params = RPCConvertValues(strMethod, strParams);
|
Array params = RPCConvertValues(strMethod, strParams);
|
||||||
|
|
||||||
// Execute
|
// Execute and handle connection failures with -rpcwait
|
||||||
Object reply = CallRPC(strMethod, params);
|
const bool fWait = GetBoolArg("-rpcwait", false);
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
const Object reply = CallRPC(strMethod, params);
|
||||||
|
|
||||||
// Parse reply
|
// Parse reply
|
||||||
const Value& result = find_value(reply, "result");
|
const Value& result = find_value(reply, "result");
|
||||||
const Value& error = find_value(reply, "error");
|
const Value& error = find_value(reply, "error");
|
||||||
|
|
||||||
if (error.type() != null_type) {
|
if (error.type() != null_type) {
|
||||||
// Error
|
// Error
|
||||||
strPrint = "error: " + write_string(error, false);
|
const int code = find_value(error.get_obj(), "code").get_int();
|
||||||
int code = find_value(error.get_obj(), "code").get_int();
|
if (fWait && code == RPC_IN_WARMUP)
|
||||||
nRet = abs(code);
|
throw CConnectionFailed("server in warmup");
|
||||||
} else {
|
strPrint = "error: " + write_string(error, false);
|
||||||
// Result
|
nRet = abs(code);
|
||||||
if (result.type() == null_type)
|
} else {
|
||||||
strPrint = "";
|
// Result
|
||||||
else if (result.type() == str_type)
|
if (result.type() == null_type)
|
||||||
strPrint = result.get_str();
|
strPrint = "";
|
||||||
else
|
else if (result.type() == str_type)
|
||||||
strPrint = write_string(result, true);
|
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) {
|
catch (boost::thread_interrupted) {
|
||||||
throw;
|
throw;
|
||||||
|
|
14
src/init.cpp
14
src/init.cpp
|
@ -752,6 +752,17 @@ bool AppInit2(boost::thread_group& threadGroup)
|
||||||
threadGroup.create_thread(&ThreadScriptCheck);
|
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;
|
int64_t nStart;
|
||||||
|
|
||||||
// ********************************************************* Step 5: verify wallet database integrity
|
// ********************************************************* Step 5: verify wallet database integrity
|
||||||
|
@ -1248,8 +1259,6 @@ bool AppInit2(boost::thread_group& threadGroup)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
StartNode(threadGroup);
|
StartNode(threadGroup);
|
||||||
if (fServer)
|
|
||||||
StartRPCThreads();
|
|
||||||
|
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
// Generate coins in the background
|
// Generate coins in the background
|
||||||
|
@ -1259,6 +1268,7 @@ bool AppInit2(boost::thread_group& threadGroup)
|
||||||
|
|
||||||
// ********************************************************* Step 11: finished
|
// ********************************************************* Step 11: finished
|
||||||
|
|
||||||
|
SetRPCWarmupFinished();
|
||||||
uiInterface.InitMessage(_("Done loading"));
|
uiInterface.InitMessage(_("Done loading"));
|
||||||
|
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
|
|
|
@ -52,6 +52,7 @@ enum RPCErrorCode
|
||||||
RPC_VERIFY_ERROR = -25, // General error during transaction or block submission
|
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_REJECTED = -26, // Transaction or block was rejected by network rules
|
||||||
RPC_VERIFY_ALREADY_IN_CHAIN = -27, // Transaction already in chain
|
RPC_VERIFY_ALREADY_IN_CHAIN = -27, // Transaction already in chain
|
||||||
|
RPC_IN_WARMUP = -28, // Client still warming up
|
||||||
|
|
||||||
// Aliases for backward compatibility
|
// Aliases for backward compatibility
|
||||||
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
|
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
|
||||||
|
|
|
@ -34,6 +34,10 @@ using namespace std;
|
||||||
static std::string strRPCUserColonPass;
|
static std::string strRPCUserColonPass;
|
||||||
|
|
||||||
static bool fRPCRunning = false;
|
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
|
//! These are created by StartRPCThreads, destroyed in StopRPCThreads
|
||||||
static asio::io_service* rpc_io_service = NULL;
|
static asio::io_service* rpc_io_service = NULL;
|
||||||
static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers;
|
static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers;
|
||||||
|
@ -744,6 +748,19 @@ bool IsRPCRunning()
|
||||||
return fRPCRunning;
|
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)
|
void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func)
|
||||||
{
|
{
|
||||||
if (!err)
|
if (!err)
|
||||||
|
@ -870,6 +887,13 @@ static bool HTTPReq_JSONRPC(AcceptedConnection *conn,
|
||||||
if (!read_string(strRequest, valRequest))
|
if (!read_string(strRequest, valRequest))
|
||||||
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
|
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;
|
string strReply;
|
||||||
|
|
||||||
// singleton request
|
// singleton request
|
||||||
|
|
|
@ -45,6 +45,13 @@ void StopRPCThreads();
|
||||||
/* Query whether RPC is running */
|
/* Query whether RPC is running */
|
||||||
bool IsRPCRunning();
|
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
|
* 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.
|
* the right number of arguments are passed, just that any passed are the correct type.
|
||||||
|
|
Loading…
Reference in a new issue