diff --git a/Makefile.am b/Makefile.am
index bce2ace0..25dd320b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -93,6 +93,7 @@ console_bs_SOURCES = \
console/executor_options.cpp \
console/executor_runner.cpp \
console/executor_scans.cpp \
+ console/executor_signals.cpp \
console/executor_store.cpp \
console/executor_test_reader.cpp \
console/executor_test_writer.cpp \
diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt
index 9162cbb0..cbd650dd 100644
--- a/builds/cmake/CMakeLists.txt
+++ b/builds/cmake/CMakeLists.txt
@@ -331,6 +331,7 @@ if (with-console)
"../../console/executor_options.cpp"
"../../console/executor_runner.cpp"
"../../console/executor_scans.cpp"
+ "../../console/executor_signals.cpp"
"../../console/executor_store.cpp"
"../../console/executor_test_reader.cpp"
"../../console/executor_test_writer.cpp"
diff --git a/builds/msvc/vs2022/bs/bs.vcxproj b/builds/msvc/vs2022/bs/bs.vcxproj
index 030b3ac3..90aa552c 100644
--- a/builds/msvc/vs2022/bs/bs.vcxproj
+++ b/builds/msvc/vs2022/bs/bs.vcxproj
@@ -138,6 +138,7 @@
+
diff --git a/builds/msvc/vs2022/bs/bs.vcxproj.filters b/builds/msvc/vs2022/bs/bs.vcxproj.filters
index 5da9d976..2de592cb 100644
--- a/builds/msvc/vs2022/bs/bs.vcxproj.filters
+++ b/builds/msvc/vs2022/bs/bs.vcxproj.filters
@@ -72,6 +72,9 @@
src
+
+ src
+
src
diff --git a/console/executor.cpp b/console/executor.cpp
index ac10c581..90e34f4e 100644
--- a/console/executor.cpp
+++ b/console/executor.cpp
@@ -21,9 +21,8 @@
#include
#include
-#include
#include
-#include
+#include
namespace libbitcoin {
namespace server {
@@ -32,12 +31,24 @@ using boost::format;
using namespace system;
using namespace std::placeholders;
+// Construction.
+// ----------------------------------------------------------------------------
+
// static initializers.
+std::promise executor::stopped_{};
std::promise executor::stopping_{};
-std::atomic executor::initialized_{};
std::atomic executor::signal_{ unsignalled };
-std::unique_ptr executor::stop_poller_{};
+std::optional executor::poller_thread_{};
+
+// class factory (singleton)
+executor& executor::factory(parser& metadata, std::istream& input,
+ std::ostream& output, std::ostream& error)
+{
+ static executor instance(metadata, input, output, error);
+ return instance;
+}
+// private constructor (singleton)
executor::executor(parser& metadata, std::istream& input, std::ostream& output,
std::ostream&)
: metadata_(metadata),
@@ -60,18 +71,11 @@ executor::executor(parser& metadata, std::istream& input, std::ostream& output,
}
{
initialize_stop();
-
-#if defined(HAVE_MSC)
- create_hidden_window();
-#endif
}
+//
executor::~executor()
{
-#if defined(HAVE_MSC)
- destroy_hidden_window();
-#endif
-
uninitialize_stop();
}
@@ -79,84 +83,23 @@ executor::~executor()
// ----------------------------------------------------------------------------
// static
-#if defined(HAVE_MSC)
-BOOL WINAPI executor::control_handler(DWORD signal)
-{
- switch (signal)
- {
- // Keyboard events. These prevent exit altogether when TRUE returned.
- // handle_stop(signal) therefore shuts down gracefully/completely.
- case CTRL_C_EVENT:
- case CTRL_BREAK_EVENT:
-
- // A signal that the system sends to all processes attached to a
- // console when the user closes the console (by clicking Close on the
- // console window's window menu). Returning TRUE here does not
- // materially delay exit, so aside from capture this is a noop.
- case CTRL_CLOSE_EVENT:
- executor::handle_stop(possible_narrow_sign_cast(signal));
- return TRUE;
-
- ////// Only services receive this (*any* user is logging off).
- ////case CTRL_LOGOFF_EVENT:
- ////// Only services receive this (all users already logged off).
- ////case CTRL_SHUTDOWN_EVENT:
- default:
- return FALSE;
- }
-}
-#endif
-
void executor::initialize_stop()
{
- BC_ASSERT(!initialized_);
- initialized_ = true;
-
poll_for_stopping();
-
-#if defined(HAVE_MSC)
- ::SetConsoleCtrlHandler(&executor::control_handler, TRUE);
-#else
- // struct keywork avoids name conflict with posix function sigaction.
- struct sigaction action{};
-
- // Restart interrupted system calls.
- action.sa_flags = SA_RESTART;
-
- // sa_handler is actually a macro :o
- action.sa_handler = handle_stop;
-
- // Set masking.
- sigemptyset(&action.sa_mask);
-
- // Block during handling to prevent reentrancy.
- sigaddset(&action.sa_mask, SIGINT);
- sigaddset(&action.sa_mask, SIGTERM);
- sigaddset(&action.sa_mask, SIGHUP);
- sigaddset(&action.sa_mask, SIGUSR2);
-
- sigaction(SIGINT, &action, nullptr);
- sigaction(SIGTERM, &action, nullptr);
- sigaction(SIGHUP, &action, nullptr);
- sigaction(SIGUSR2, &action, nullptr);
-
- #if defined(HAVE_LINUX)
- sigaction(SIGPWR, &action, nullptr);
- #endif
-#endif
+ create_hidden_window();
+ set_signal_handlers();
}
void executor::uninitialize_stop()
{
- BC_ASSERT(initialized_);
- initialized_ = false;
-
stop();
- if (stop_poller_ && stop_poller_->joinable())
+ if (poller_thread_.has_value() && poller_thread_.value().joinable())
{
- stop_poller_->join();
- stop_poller_.reset();
+ poller_thread_.value().join();
+ poller_thread_.reset();
}
+
+ destroy_hidden_window();
}
// Handle the stop signal and invoke stop method (requries signal safe code).
@@ -192,13 +135,13 @@ bool executor::canceled()
// Spinning must be used in signal handler, cannot wait on a promise.
void executor::poll_for_stopping()
{
- stop_poller_ = std::make_unique([]()
+ poller_thread_.emplace(std::thread([]()
{
while (!canceled())
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
stopping_.set_value(true);
- });
+ }));
}
// Blocks until stopping is signalled by poller.
@@ -207,22 +150,6 @@ void executor::wait_for_stopping()
stopping_.get_future().wait();
}
-// Suspend verbose logging and log the stop signal.
-void executor::log_stopping()
-{
- const auto signal = signal_.load();
- if (signal == signal_none)
- return;
-
- // A high level of consolve logging can obscure and delay stop.
- toggle_.at(network::levels::protocol) = false;
- toggle_.at(network::levels::verbose) = false;
- toggle_.at(network::levels::proxy) = false;
-
- logger(format(BS_NODE_INTERRUPTED) % signal);
- logger(BS_NETWORK_STOPPING);
-}
-
// Event handlers.
// ----------------------------------------------------------------------------
diff --git a/console/executor.hpp b/console/executor.hpp
index a995b531..43d19bbb 100644
--- a/console/executor.hpp
+++ b/console/executor.hpp
@@ -21,6 +21,7 @@
#include
#include
+#include
#include
#include
@@ -34,47 +35,26 @@ namespace server {
// This class is just an ad-hoc user interface wrapper on the node.
class executor
{
-public:
- DELETE_COPY(executor);
-
- // Construct.
+private:
+ // Singleton, private constructor.
executor(parser& metadata, std::istream&, std::ostream& output,
std::ostream& error);
- // Clean up.
- ~executor();
+public:
+ DELETE_COPY_MOVE(executor);
+
+ // Get the singleton instance.
+ static executor& factory(parser& metadata, std::istream& input,
+ std::ostream& output, std::ostream& error);
// Called from main.
bool dispatch();
-private:
- static constexpr int unsignalled{ -1 };
- static constexpr int signal_none{ -2 };
-
- // Executor (static).
- static void initialize_stop();
- static void uninitialize_stop();
- static void poll_for_stopping();
- static void wait_for_stopping();
- static void handle_stop(int code);
- static void stop(int signal=signal_none);
- static bool canceled();
-
-#if defined(HAVE_MSC)
- static BOOL WINAPI control_handler(DWORD signal);
- static LRESULT CALLBACK window_proc(HWND handle, UINT message,
- WPARAM wparam, LPARAM lparam);
-
- void create_hidden_window();
- void destroy_hidden_window();
-
- HWND window_{};
- std::thread thread_{};
- std::promise ready_{};
-#endif
+ // Release shutdown monitor.
+ ~executor();
+private:
// Executor.
- void log_stopping();
void handle_started(const system::code& ec);
void handle_subscribed(const system::code& ec, size_t key);
void handle_running(const system::code& ec);
@@ -148,6 +128,7 @@ class executor
void subscribe_events(std::ostream& sink);
void logger(const boost::format& message) const;
void logger(const std::string& message) const;
+ void log_stopping();
// Runner.
void stopper(const std::string& message);
@@ -171,13 +152,6 @@ class executor
// Runtime events.
static const std::unordered_map fired_;
- // Shutdown.
- static std::atomic signal_;
- static std::atomic initialized_;
- static std::promise stopping_;
- static std::unique_ptr stop_poller_;
- std::promise log_suspended_{};
-
parser& metadata_;
server_node::ptr node_{};
server_node::store store_;
@@ -189,6 +163,37 @@ class executor
network::logger log_{};
network::capture capture_{ input_, close_ };
std_array toggle_;
+
+ // Shutdown.
+ // ------------------------------------------------------------------------
+
+ static constexpr int unsignalled{ -1 };
+ static constexpr int signal_none{ -2 };
+
+ static std::atomic signal_;
+ static std::promise stopped_;
+ static std::promise stopping_;
+ static std::optional poller_thread_;
+
+ static void initialize_stop();
+ static void uninitialize_stop();
+ static void poll_for_stopping();
+ static void wait_for_stopping();
+ static void set_signal_handlers();
+ static void create_hidden_window();
+ static void destroy_hidden_window();
+ static void stop(int signal=signal_none);
+ static void handle_stop(int code);
+ static bool canceled();
+
+#if defined(HAVE_MSC)
+ static HWND window_handle_;
+ static std::promise window_ready_;
+ static std::optional window_thread_;
+ static BOOL WINAPI control_handler(DWORD signal);
+ static LRESULT CALLBACK window_proc(HWND handle, UINT message,
+ WPARAM wparam, LPARAM lparam);
+#endif
};
} // namespace server
diff --git a/console/executor_logging.cpp b/console/executor_logging.cpp
index 7d9f1bcf..e1116c28 100644
--- a/console/executor_logging.cpp
+++ b/console/executor_logging.cpp
@@ -91,7 +91,7 @@ void executor::subscribe_log(std::ostream& sink)
// only after all logging work is completed. After the above
// message is queued, no more are accepted, and the executor
// may initiate its own (and thereby this log's) destruction.
- log_suspended_.set_value(true);
+ stopped_.set_value(true);
return false;
}
else
@@ -105,6 +105,22 @@ void executor::subscribe_log(std::ostream& sink)
);
}
+// Suspend verbose logging and log the stop signal.
+void executor::log_stopping()
+{
+ const auto signal = signal_.load();
+ if (signal == signal_none)
+ return;
+
+ // A high level of consolve logging can obscure and delay stop.
+ toggle_.at(network::levels::protocol) = false;
+ toggle_.at(network::levels::verbose) = false;
+ toggle_.at(network::levels::proxy) = false;
+
+ logger(boost::format(BS_NODE_INTERRUPTED) % signal);
+ logger(BS_NETWORK_STOPPING);
+}
+
void executor::logger(const std::string& message) const
{
if (log_.stopped())
diff --git a/console/executor_runner.cpp b/console/executor_runner.cpp
index 09ff8251..39f976b5 100644
--- a/console/executor_runner.cpp
+++ b/console/executor_runner.cpp
@@ -39,7 +39,7 @@ void executor::stopper(const std::string& message)
log_.stop(message,levels::application);
// Suspend process termination until final message is buffered.
- log_suspended_.get_future().wait();
+ stopped_.get_future().wait();
}
void executor::subscribe_connect()
diff --git a/console/executor_signals.cpp b/console/executor_signals.cpp
new file mode 100644
index 00000000..01d75e1a
--- /dev/null
+++ b/console/executor_signals.cpp
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "executor.hpp"
+
+#include
+
+namespace libbitcoin {
+namespace server {
+
+// TODO: use RegisterServiceCtrlHandlerEx for service registration.
+
+#if defined(HAVE_MSC)
+BOOL WINAPI executor::control_handler(DWORD signal)
+{
+ switch (signal)
+ {
+ // Keyboard events. These prevent exit altogether when TRUE returned.
+ // handle_stop(signal) therefore shuts down gracefully/completely.
+ case CTRL_C_EVENT:
+ case CTRL_BREAK_EVENT:
+
+ // A signal that the system sends to all processes attached to a
+ // console when the user closes the console (by clicking Close on the
+ // console window's window menu). Returning TRUE here does not
+ // materially delay exit, so aside from capture this is a noop.
+ case CTRL_CLOSE_EVENT:
+ {
+ const auto value = system::possible_narrow_sign_cast(signal);
+ executor::handle_stop(value);
+ return TRUE;
+ }
+
+ ////// Only services receive this (*any* user is logging off).
+ ////case CTRL_LOGOFF_EVENT:
+ ////// Only services receive this (all users already logged off).
+ ////case CTRL_SHUTDOWN_EVENT:
+ default:
+ return FALSE;
+ }
+}
+#endif
+
+// static
+void executor::set_signal_handlers()
+{
+#if defined(HAVE_MSC)
+ ::SetConsoleCtrlHandler(&executor::control_handler, TRUE);
+#else
+ // struct keywork avoids name conflict with posix function sigaction.
+ struct sigaction action{};
+
+ // Restart interrupted system calls.
+ action.sa_flags = SA_RESTART;
+
+ // sa_handler is actually a macro :o
+ action.sa_handler = handle_stop;
+
+ // Set masking.
+ sigemptyset(&action.sa_mask);
+
+ // Block during handling to prevent reentrancy.
+ sigaddset(&action.sa_mask, SIGINT);
+ sigaddset(&action.sa_mask, SIGTERM);
+ sigaddset(&action.sa_mask, SIGHUP);
+ sigaddset(&action.sa_mask, SIGUSR2);
+
+ sigaction(SIGINT, &action, nullptr);
+ sigaction(SIGTERM, &action, nullptr);
+ sigaction(SIGHUP, &action, nullptr);
+ sigaction(SIGUSR2, &action, nullptr);
+
+#if defined(HAVE_LINUX)
+ sigaction(SIGPWR, &action, nullptr);
+#endif
+#endif
+}
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/executor_window.cpp b/console/executor_window.cpp
index 76e6ad3e..5a417c2e 100644
--- a/console/executor_window.cpp
+++ b/console/executor_window.cpp
@@ -18,19 +18,15 @@
*/
#include "executor.hpp"
+#include
+#include
+#include
+#include "localize.hpp"
+
namespace libbitcoin {
namespace server {
#if defined(HAVE_MSC)
-
-// TODO: use RegisterServiceCtrlHandlerEx for service registration.
-
-using namespace system;
-constexpr auto window_name = L"HiddenShutdownWindow";
-constexpr auto window_text = L"Flushing tables...";
-constexpr auto window_title = L"Libbitcoin Server";
-
-// static
LRESULT CALLBACK executor::window_proc(HWND handle, UINT message,
WPARAM wparam, LPARAM lparam)
{
@@ -40,8 +36,9 @@ LRESULT CALLBACK executor::window_proc(HWND handle, UINT message,
// provide reason text that the operating system may show to the user.
case WM_QUERYENDSESSION:
{
- ::ShutdownBlockReasonCreate(handle, window_text);
- executor::handle_stop(possible_narrow_sign_cast(message));
+ ::ShutdownBlockReasonCreate(handle, BS_WINDOW_TEXT);
+ const auto value = system::possible_narrow_sign_cast(message);
+ executor::handle_stop(value);
return FALSE;
}
default:
@@ -50,17 +47,29 @@ LRESULT CALLBACK executor::window_proc(HWND handle, UINT message,
}
}
}
+#endif
+
+// static initializers.
+#if defined(HAVE_MSC)
+HWND executor::window_handle_{};
+std::promise executor::window_ready_{};
+std::optional executor::window_thread_{};
+#endif
+// static
void executor::create_hidden_window()
{
- thread_ = std::thread([this]()
+#if defined(HAVE_MSC)
+ static constexpr auto window_name = L"HiddenShutdownWindow";
+
+ window_thread_.emplace(std::thread([]()
{
const auto instance = ::GetModuleHandleW(NULL);
const WNDCLASSEXW window_class
{
.cbSize = sizeof(WNDCLASSEXW),
.style = CS_HREDRAW | CS_VREDRAW,
- .lpfnWndProc = &executor::window_proc,
+ .lpfnWndProc = &window_proc,
.hInstance = instance,
.hIcon = ::LoadIconW(instance, MAKEINTRESOURCEW(101)),
.lpszClassName = window_name,
@@ -74,11 +83,11 @@ void executor::create_hidden_window()
// Zero sizing results in title bar only.
// WS_EX_NOACTIVATE: prevents focus-stealing.
// WS_VISIBLE: required to capture WM_QUERYENDSESSION.
- window_ = ::CreateWindowExW
+ window_handle_ = ::CreateWindowExW
(
WS_EX_NOACTIVATE,
window_name,
- window_title,
+ BS_WINDOW_TITLE,
WS_VISIBLE,
0, 0, 0, 0,
NULL,
@@ -87,47 +96,51 @@ void executor::create_hidden_window()
NULL);
// fault
- if (is_null(window_))
+ if (is_null(window_handle_))
return;
MSG message{};
BOOL result{};
- ready_.set_value(true);
+ window_ready_.set_value(true);
while (!is_zero(result = ::GetMessageW(&message, NULL, 0, 0)))
{
// fault
- if (is_negative(result))
+ if (system::is_negative(result))
return;
::TranslateMessage(&message);
::DispatchMessageW(&message);
}
- });
+ }));
+#endif
}
void executor::destroy_hidden_window()
{
+#if defined(HAVE_MSC)
// Wait until window is accepting messages, so WM_QUIT isn't missed.
- ready_.get_future().wait();
+ window_ready_.get_future().wait();
- if (!is_null(window_))
- ::PostMessageW(window_, WM_QUIT, 0, 0);
+ if (!is_null(window_handle_))
+ ::PostMessageW(window_handle_, WM_QUIT, 0, 0);
- if (thread_.joinable())
- thread_.join();
+ if (window_thread_.has_value() && window_thread_.value().joinable())
+ {
+ window_thread_.value().join();
+ window_thread_.reset();
+ }
- if (!is_null(window_))
+ if (!is_null(window_handle_))
{
- ::DestroyWindow(window_);
- window_ = NULL;
+ ::DestroyWindow(window_handle_);
+ window_handle_ = NULL;
}
const auto handle = ::GetConsoleWindow();
if (!is_null(handle))
::ShutdownBlockReasonDestroy(handle);
+#endif
}
-#endif // HAVE_MSC
-
} // namespace server
} // namespace libbitcoin
diff --git a/console/localize.hpp b/console/localize.hpp
index 09f25a87..d3e0eac4 100644
--- a/console/localize.hpp
+++ b/console/localize.hpp
@@ -21,8 +21,8 @@
/// Localizable messages.
-namespace libbitcoin {
-namespace server {
+#define BS_WINDOW_TITLE L"Libbitcoin Server"
+#define BS_WINDOW_TEXT L"Flushing tables..."
#define BS_OPERATION_INTERRUPT \
"Press CTRL-C to cancel operation."
@@ -273,7 +273,4 @@ namespace server {
#define BS_NODE_TERMINATE \
"Press to exit..."
-} // namespace server
-} // namespace libbitcoin
-
#endif
diff --git a/console/main.cpp b/console/main.cpp
index e0f0e5c6..e6a2549d 100644
--- a/console/main.cpp
+++ b/console/main.cpp
@@ -104,6 +104,6 @@ int bc::system::main(int argc, char* argv[])
set_memory_priority(metadata.configured.node.memory_priority_());
- executor host(metadata, cin, cout, cerr);
+ executor& host = executor::factory(metadata, cin, cout, cerr);
return host.dispatch() ? 0 : -1;
}