aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/httpserver.cpp32
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/rpc/server.cpp8
-rwxr-xr-xtest/functional/feature_shutdown.py28
-rwxr-xr-xtest/functional/test_framework/test_framework.py8
-rwxr-xr-xtest/functional/test_framework/test_node.py4
-rwxr-xr-xtest/functional/test_runner.py1
7 files changed, 54 insertions, 28 deletions
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
index 00434169cd..cb8578927a 100644
--- a/src/httpserver.cpp
+++ b/src/httpserver.cpp
@@ -10,6 +10,7 @@
#include <util/strencodings.h>
#include <netbase.h>
#include <rpc/protocol.h> // For HTTP status codes
+#include <shutdown.h>
#include <sync.h>
#include <ui_interface.h>
@@ -21,7 +22,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
-#include <future>
#include <event2/thread.h>
#include <event2/buffer.h>
@@ -421,7 +421,6 @@ bool UpdateHTTPServerLogging(bool enable) {
}
std::thread threadHTTP;
-std::future<bool> threadResult;
static std::vector<std::thread> g_thread_http_workers;
void StartHTTPServer()
@@ -429,9 +428,7 @@ void StartHTTPServer()
LogPrint(BCLog::HTTP, "Starting HTTP server\n");
int rpcThreads = std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L);
LogPrintf("HTTP: starting %d worker threads\n", rpcThreads);
- std::packaged_task<bool(event_base*)> task(ThreadHTTP);
- threadResult = task.get_future();
- threadHTTP = std::thread(std::move(task), eventBase);
+ threadHTTP = std::thread(ThreadHTTP, eventBase);
for (int i = 0; i < rpcThreads; i++) {
g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue);
@@ -442,10 +439,6 @@ void InterruptHTTPServer()
{
LogPrint(BCLog::HTTP, "Interrupting HTTP server\n");
if (eventHTTP) {
- // Unlisten sockets
- for (evhttp_bound_socket *socket : boundSockets) {
- evhttp_del_accept_socket(eventHTTP, socket);
- }
// Reject requests on current connections
evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr);
}
@@ -465,20 +458,14 @@ void StopHTTPServer()
delete workQueue;
workQueue = nullptr;
}
+ // Unlisten sockets, these are what make the event loop running, which means
+ // that after this and all connections are closed the event loop will quit.
+ for (evhttp_bound_socket *socket : boundSockets) {
+ evhttp_del_accept_socket(eventHTTP, socket);
+ }
+ boundSockets.clear();
if (eventBase) {
LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n");
- // Exit the event loop as soon as there are no active events.
- event_base_loopexit(eventBase, nullptr);
- // Give event loop a few seconds to exit (to send back last RPC responses), then break it
- // Before this was solved with event_base_loopexit, but that didn't work as expected in
- // at least libevent 2.0.21 and always introduced a delay. In libevent
- // master that appears to be solved, so in the future that solution
- // could be used again (if desirable).
- // (see discussion in https://github.com/bitcoin/bitcoin/pull/6990)
- if (threadResult.valid() && threadResult.wait_for(std::chrono::milliseconds(2000)) == std::future_status::timeout) {
- LogPrintf("HTTP event loop did not exit within allotted time, sending loopbreak\n");
- event_base_loopbreak(eventBase);
- }
threadHTTP.join();
}
if (eventHTTP) {
@@ -583,6 +570,9 @@ void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value)
void HTTPRequest::WriteReply(int nStatus, const std::string& strReply)
{
assert(!replySent && req);
+ if (ShutdownRequested()) {
+ WriteHeader("Connection", "close");
+ }
// Send event to main http thread to send reply message
struct evbuffer* evb = evhttp_request_get_output_buffer(req);
assert(evb);
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 2b99808c07..6f1bfb03d1 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -162,6 +162,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "rescanblockchain", 1, "stop_height"},
{ "createwallet", 1, "disable_private_keys"},
{ "getnodeaddresses", 0, "count"},
+ { "stop", 0, "wait" },
};
// clang-format on
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 97745c62d6..dc81a9a861 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -221,6 +221,9 @@ UniValue help(const JSONRPCRequest& jsonRequest)
UniValue stop(const JSONRPCRequest& jsonRequest)
{
// Accept the deprecated and ignored 'detach' boolean argument
+ // Also accept the hidden 'wait' integer argument (milliseconds)
+ // For instance, 'stop 1000' makes the call wait 1 second before returning
+ // to the client (intended for testing)
if (jsonRequest.fHelp || jsonRequest.params.size() > 1)
throw std::runtime_error(
RPCHelpMan{"stop",
@@ -229,6 +232,9 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
// Event loop will exit after current HTTP requests have been handled, so
// this reply will get back to the client.
StartShutdown();
+ if (jsonRequest.params[0].isNum()) {
+ MilliSleep(jsonRequest.params[0].get_int());
+ }
return "Bitcoin server stopping";
}
@@ -255,7 +261,7 @@ static const CRPCCommand vRPCCommands[] =
// --------------------- ------------------------ ----------------------- ----------
/* Overall control/query calls */
{ "control", "help", &help, {"command"} },
- { "control", "stop", &stop, {} },
+ { "control", "stop", &stop, {"wait"} },
{ "control", "uptime", &uptime, {} },
};
// clang-format on
diff --git a/test/functional/feature_shutdown.py b/test/functional/feature_shutdown.py
new file mode 100755
index 0000000000..b633fabb1f
--- /dev/null
+++ b/test/functional/feature_shutdown.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test bitcoind shutdown."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal, get_rpc_proxy
+from threading import Thread
+
+def test_long_call(node):
+ block = node.waitfornewblock()
+ assert_equal(block['height'], 0)
+
+class ShutdownTest(BitcoinTestFramework):
+
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 1
+
+ def run_test(self):
+ node = get_rpc_proxy(self.nodes[0].url, 1, timeout=600, coveragedir=self.nodes[0].coverage_dir)
+ Thread(target=test_long_call, args=(node,)).start()
+ # wait 1 second to ensure event loop waits for current connections to close
+ self.stop_node(0, wait=1000)
+
+if __name__ == '__main__':
+ ShutdownTest().main()
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 0dfa9e0d24..10d5c659ad 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -327,16 +327,16 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
for node in self.nodes:
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
- def stop_node(self, i, expected_stderr=''):
+ def stop_node(self, i, expected_stderr='', wait=0):
"""Stop a bitcoind test node"""
- self.nodes[i].stop_node(expected_stderr)
+ self.nodes[i].stop_node(expected_stderr, wait=wait)
self.nodes[i].wait_until_stopped()
- def stop_nodes(self):
+ def stop_nodes(self, wait=0):
"""Stop multiple bitcoind test nodes"""
for node in self.nodes:
# Issue RPC to stop nodes
- node.stop_node()
+ node.stop_node(wait=wait)
for node in self.nodes:
# Wait for nodes to stop
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 27f99c259c..ebae3faffc 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -228,13 +228,13 @@ class TestNode():
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
return self.rpc / wallet_path
- def stop_node(self, expected_stderr=''):
+ def stop_node(self, expected_stderr='', wait=0):
"""Stop the node."""
if not self.running:
return
self.log.debug("Stopping node")
try:
- self.stop()
+ self.stop(wait=wait)
except http.client.CannotSendRequest:
self.log.exception("Unable to stop node.")
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 1167b4bba2..c029abddc1 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -186,6 +186,7 @@ BASE_SCRIPTS = [
'feature_config_args.py',
'rpc_help.py',
'feature_help.py',
+ 'feature_shutdown.py',
# Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time
]