diff --git a/Makefile.am b/Makefile.am
index 83480f3f..54ef6e80 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -96,6 +96,7 @@ console_bs_SOURCES = \
console/executor_store.cpp \
console/executor_test_reader.cpp \
console/executor_test_writer.cpp \
+ console/executor_windows.cpp \
console/localize.hpp \
console/main.cpp \
console/stack_trace.cpp \
diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt
index 878836d5..ef062c6b 100644
--- a/builds/cmake/CMakeLists.txt
+++ b/builds/cmake/CMakeLists.txt
@@ -334,6 +334,7 @@ if (with-console)
"../../console/executor_store.cpp"
"../../console/executor_test_reader.cpp"
"../../console/executor_test_writer.cpp"
+ "../../console/executor_windows.cpp"
"../../console/libbitcoin.ico"
"../../console/localize.hpp"
"../../console/main.cpp"
diff --git a/builds/msvc/libbitcoin.ico b/builds/msvc/libbitcoin.ico
new file mode 100644
index 00000000..a15428a0
Binary files /dev/null and b/builds/msvc/libbitcoin.ico differ
diff --git a/builds/msvc/resource.h b/builds/msvc/resource.h
index b13f4e28..44ace40a 100644
Binary files a/builds/msvc/resource.h and b/builds/msvc/resource.h differ
diff --git a/builds/msvc/resource.rc b/builds/msvc/resource.rc
index 1bf31eb2..e6d99437 100644
Binary files a/builds/msvc/resource.rc and b/builds/msvc/resource.rc differ
diff --git a/builds/msvc/vs2022/bs/bs.vcxproj b/builds/msvc/vs2022/bs/bs.vcxproj
index a1decddc..a33ff0aa 100644
--- a/builds/msvc/vs2022/bs/bs.vcxproj
+++ b/builds/msvc/vs2022/bs/bs.vcxproj
@@ -139,6 +139,7 @@
+
diff --git a/builds/msvc/vs2022/bs/bs.vcxproj.filters b/builds/msvc/vs2022/bs/bs.vcxproj.filters
index 8a10a8c0..ff4d012d 100644
--- a/builds/msvc/vs2022/bs/bs.vcxproj.filters
+++ b/builds/msvc/vs2022/bs/bs.vcxproj.filters
@@ -81,6 +81,9 @@
src
+
+ src
+
src
@@ -116,4 +119,4 @@
resource
-
\ No newline at end of file
+
diff --git a/console/executor.cpp b/console/executor.cpp
index da034e98..7b27be3d 100644
--- a/console/executor.cpp
+++ b/console/executor.cpp
@@ -31,9 +31,11 @@ namespace server {
using boost::format;
using namespace std::placeholders;
-std::atomic_bool executor::cancel_{};
+// static initializers.
std::thread executor::stop_poller_{};
std::promise executor::stopping_{};
+std::atomic executor::initialized_{};
+std::atomic executor::signal_{ unsignalled };
executor::executor(parser& metadata, std::istream& input, std::ostream& output,
std::ostream&)
@@ -56,45 +58,35 @@ executor::executor(parser& metadata, std::istream& input, std::ostream& output,
metadata.configured.log.verbose
}
{
- initialize_stop();
-}
+ BC_ASSERT(!initialized_);
+ initialized_ = true;
-// Stop signal.
-// ----------------------------------------------------------------------------
+ initialize_stop();
#if defined(HAVE_MSC)
-BOOL WINAPI executor::win32_handler(DWORD signal)
+ create_hidden_window();
+#endif
+}
+
+executor::~executor()
{
- ////if (auto* log = fopen("shutdown.log", "a"))
- ////{
- //// fprintf(log, "Signal %lu at %llu\n", signal, GetTickCount64());
- //// fflush(log);
- //// fclose(log);
- ////}
+ initialized_ = false;
- switch (signal)
- {
- case CTRL_C_EVENT:
- case CTRL_BREAK_EVENT:
- case CTRL_CLOSE_EVENT:
- case CTRL_LOGOFF_EVENT:
- case CTRL_SHUTDOWN_EVENT:
- executor::handle_stop({});
- return TRUE;
- default:
- return FALSE;
- }
-}
+#if defined(HAVE_MSC)
+ destroy_hidden_window();
#endif
+}
+
+// Stop signal.
+// ----------------------------------------------------------------------------
+// static
-// Call only once.
void executor::initialize_stop()
{
poll_for_stopping();
#if defined(HAVE_MSC)
- // TODO: use RegisterServiceCtrlHandlerEx for service registration.
- ::SetConsoleCtrlHandler(&executor::win32_handler, TRUE);
+ ::SetConsoleCtrlHandler(&executor::control_handler, TRUE);
#else
// Restart interrupted system calls.
struct sigaction action
@@ -123,17 +115,51 @@ void executor::initialize_stop()
#endif
}
+// Handle the stop signal and invoke stop method (requries signal safe code).
+void executor::handle_stop(int signal)
+{
+ stop(signal);
+}
+
+// Manage race between console stop and server stop.
+void executor::stop(int signal)
+{
+ ////if (auto* log = fopen("shutdown.log", "a"))
+ ////{
+ //// fprintf(log, "stop %lu at %llu\n", signal, GetTickCount64());
+ //// fflush(log);
+ //// fclose(log);
+ ////}
+
+ // Implementation is limited to signal safe code.
+ static_assert(std::atomic::is_always_lock_free);
+
+ // Capture first handled signal value.
+ auto unset = unsignalled;
+ signal_.compare_exchange_strong(unset, signal, std::memory_order_acq_rel);
+}
+
+// Any thread can monitor this for stopping.
+bool executor::canceled()
+{
+ return signal_.load(std::memory_order_acquire) != unsignalled;
+}
+
+// Spinning must be used in signal handler, cannot wait on a promise.
void executor::poll_for_stopping()
{
+ using namespace std::this_thread;
+
stop_poller_ = std::thread([]()
{
- while (!cancel_.load(std::memory_order_acquire))
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ while (!canceled())
+ sleep_for(std::chrono::milliseconds(10));
stopping_.set_value(true);
});
}
+// Blocks until stopping is signalled by poller.
void executor::wait_for_stopping()
{
stopping_.get_future().wait();
@@ -141,16 +167,20 @@ void executor::wait_for_stopping()
stop_poller_.join();
}
-// Implementation is limited to signal safe code.
-void executor::handle_stop(int)
+// Suspend verbose logging and log the stop signal.
+void executor::log_stopping()
{
- stop();
-}
+ const auto signal = signal_.load();
+ if (signal == signal_none)
+ return;
-// Manage the race between console stop and server stop.
-void executor::stop()
-{
- cancel_.store(true, std::memory_order_release);
+ // 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 3f739247..f0180b53 100644
--- a/console/executor.hpp
+++ b/console/executor.hpp
@@ -28,12 +28,10 @@
#include
#include
-// This class is just an ad-hoc user interface wrapper on the node.
-// It will be factored and cleaned up for final release.
-
namespace libbitcoin {
namespace server {
+// This class is just an ad-hoc user interface wrapper on the node.
class executor
{
public:
@@ -43,21 +41,38 @@ class executor
executor(parser& metadata, std::istream&, std::ostream& output,
std::ostream& error);
+ // Clean up.
+ ~executor();
+
// 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 poll_for_stopping();
static void wait_for_stopping();
static void handle_stop(int code);
- static void stop();
+ static void stop(int signal=signal_none);
+ static bool canceled();
+
#if defined(HAVE_MSC)
- static BOOL WINAPI win32_handler(DWORD signal);
+ 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_{};
#endif
// 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);
@@ -155,8 +170,9 @@ class executor
static const std::unordered_map fired_;
// Shutdown.
- static std::atomic_bool cancel_;
static std::thread stop_poller_;
+ static std::atomic signal_;
+ static std::atomic initialized_;
static std::promise stopping_;
std::promise log_suspended_{};
diff --git a/console/executor_runner.cpp b/console/executor_runner.cpp
index 7f6cca0d..09ff8251 100644
--- a/console/executor_runner.cpp
+++ b/console/executor_runner.cpp
@@ -36,7 +36,7 @@ void executor::stopper(const std::string& message)
capture_.stop();
// Stop log, causing final message to be buffered by handler.
- log_.stop(message, network::levels::application);
+ log_.stop(message,levels::application);
// Suspend process termination until final message is buffered.
log_suspended_.get_future().wait();
@@ -46,7 +46,7 @@ void executor::subscribe_connect()
{
node_->subscribe_connect
(
- [&](const code&, const network::channel::ptr&)
+ [&](const code&, const channel::ptr&)
{
log_.write(levels::verbose) <<
"{in:" << node_->inbound_channel_count() << "}"
@@ -162,13 +162,12 @@ bool executor::do_run()
logger(BS_NETWORK_STARTING);
node_->start(std::bind(&executor::handle_started, this, _1));
- // Wait on signal to stop node ().
+ // Wait on signal to stop node (, etc).
wait_for_stopping();
- toggle_.at(levels::protocol) = false;
- logger(BS_NETWORK_STOPPING);
// Stop network (if not already stopped by self).
// Blocks on join of server/node/network threadpool.
+ log_stopping();
node_->close();
// Sizes and records change, buckets don't.
diff --git a/console/executor_scans.cpp b/console/executor_scans.cpp
index 22149a92..edf634b1 100644
--- a/console/executor_scans.cpp
+++ b/console/executor_scans.cpp
@@ -45,7 +45,7 @@ void executor::scan_flags() const
logger(BS_OPERATION_INTERRUPT);
- for (size_t height{}; !cancel_ && height <= top; ++height)
+ for (size_t height{}; !canceled() && height <= top; ++height)
{
database::context ctx{};
const auto link = query_.to_candidate(height);
@@ -65,7 +65,7 @@ void executor::scan_flags() const
}
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
const auto span = duration_cast(logger::now() - start);
@@ -86,7 +86,7 @@ void executor::scan_slabs() const
// Tx (record) links are sequential and so iterable, however the terminal
// condition assumes all tx entries fully written (ok for stopped node).
// A running node cannot safely iterate over record links, but stopped can.
- for (auto puts = query_.put_counts(link); to_bool(puts.first) && !cancel_;
+ for (auto puts = query_.put_counts(link); to_bool(puts.first) && !canceled();
puts = query_.put_counts(++link))
{
inputs += puts.first;
@@ -95,7 +95,7 @@ void executor::scan_slabs() const
logger(format(BS_MEASURE_SLABS_ROW) % link % inputs % outputs);
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
const auto span = duration_cast(logger::now() - start);
@@ -114,7 +114,7 @@ void executor::scan_buckets() const
auto filled = zero;
auto bucket = max_size_t;
auto start = logger::now();
- while (!cancel_ && (++bucket < query_.header_buckets()))
+ while (!canceled() && (++bucket < query_.header_buckets()))
{
const auto top = query_.top_header(bucket);
if (!top.is_terminal())
@@ -125,7 +125,7 @@ void executor::scan_buckets() const
duration_cast(logger::now() - start).count());
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
auto span = duration_cast(logger::now() - start);
@@ -137,7 +137,7 @@ void executor::scan_buckets() const
filled = zero;
bucket = max_size_t;
start = logger::now();
- while (!cancel_ && (++bucket < query_.tx_buckets()))
+ while (!canceled() && (++bucket < query_.tx_buckets()))
{
const auto top = query_.top_tx(bucket);
if (!top.is_terminal())
@@ -148,7 +148,7 @@ void executor::scan_buckets() const
duration_cast(logger::now() - start).count());
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
span = duration_cast(logger::now() - start);
@@ -160,7 +160,7 @@ void executor::scan_buckets() const
filled = zero;
bucket = max_size_t;
start = logger::now();
- while (!cancel_ && (++bucket < query_.point_buckets()))
+ while (!canceled() && (++bucket < query_.point_buckets()))
{
const auto top = query_.top_point(bucket);
if (!top.is_terminal())
@@ -171,7 +171,7 @@ void executor::scan_buckets() const
duration_cast(logger::now() - start).count());
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
span = duration_cast(logger::now() - start);
@@ -219,7 +219,7 @@ void executor::scan_collisions() const
const auto header_records = query_.header_records();
std_vector header(header_buckets, empty);
std_vector txs(header_buckets, empty);
- while (!cancel_ && (++index < header_records))
+ while (!canceled() && (++index < header_records))
{
const header_link link{ possible_narrow_cast(index) };
const auto key = query_.get_header_key(link.value);
@@ -233,7 +233,7 @@ void executor::scan_collisions() const
duration_cast(logger::now() - start).count());
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
// ........................................................................
@@ -275,7 +275,7 @@ void executor::scan_collisions() const
const auto tx_records = query_.tx_records();
std_vector tx(tx_buckets, empty);
std_vector strong_tx(tx_buckets, empty);
- while (!cancel_ && (++index < tx_records))
+ while (!canceled() && (++index < tx_records))
{
const tx_link link{ possible_narrow_cast(index) };
const auto key = query_.get_tx_key(link.value);
@@ -288,7 +288,7 @@ void executor::scan_collisions() const
duration_cast(logger::now() - start).count());
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
// ........................................................................
@@ -352,7 +352,7 @@ void executor::scan_collisions() const
size_t window{};
const auto top = query_.get_top_associated();
- for (index = zero; index <= top && !cancel_; ++index)
+ for (index = zero; index <= top && !canceled(); ++index)
{
++coinbases;
const auto link = query_.to_candidate(index);
@@ -404,7 +404,7 @@ void executor::scan_collisions() const
}
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
// ........................................................................
diff --git a/console/executor_test_reader.cpp b/console/executor_test_reader.cpp
index e685368d..81e16dc2 100644
--- a/console/executor_test_reader.cpp
+++ b/console/executor_test_reader.cpp
@@ -36,7 +36,7 @@ void executor::read_test(const hash_digest&) const
const auto concurrency = metadata_.configured.node.maximum_concurrency_();
size_t size{};
- for (auto height = zero; !cancel_ && height <= top; ++height)
+ for (auto height = zero; !canceled() && height <= top; ++height)
{
const auto link = query_.to_candidate(height);
if (link.is_terminal())
@@ -89,7 +89,7 @@ void executor::read_test(const hash_digest&) const
logger(format("Getting first [%1%] output address hashes.") % target_count);
auto start = fine_clock::now();
- while (!cancel_ && keys.size() < target_count)
+ while (!canceled() && keys.size() < target_count)
{
const auto outputs = query_.get_outputs(tx++);
if (is_null(outputs))
@@ -101,7 +101,7 @@ void executor::read_test(const hash_digest&) const
for (const auto& put: *outputs)
{
keys.emplace(put->script().hash());
- if (cancel_ || keys.size() == target_count)
+ if (canceled() || keys.size() == target_count)
break;
}
}
@@ -138,7 +138,7 @@ void executor::read_test(const hash_digest&) const
start = fine_clock::now();
for (auto& key: keys)
{
- if (cancel_)
+ if (canceled())
return;
////size_t found{};
@@ -151,7 +151,7 @@ void executor::read_test(const hash_digest&) const
do
{
- if (cancel_)
+ if (canceled())
break;
table::address::record address{};
@@ -276,7 +276,7 @@ void executor::read_test(const hash_digest&) const
for (const auto& row: outs)
{
- if (cancel_)
+ if (canceled())
break;
const auto output = !row.output ? "{error}" :
@@ -322,7 +322,7 @@ void executor::read_test(const hash_digest&) const
uint32_t block{ one };
logger("Find strong blocks.");
- while (!cancel_ && (block < count) && query_.is_strong_block(block))
+ while (!canceled() && (block < count) && query_.is_strong_block(block))
{
++block;
}
@@ -334,7 +334,7 @@ void executor::read_test(const hash_digest&) const
uint32_t milestone{ 295'001 };
logger("Find milestone blocks.");
- while (!cancel_ && (milestone < count) && query_.is_milestone(milestone))
+ while (!canceled() && (milestone < count) && query_.is_milestone(milestone))
{
++milestone;
}
@@ -346,7 +346,7 @@ void executor::read_test(const hash_digest&) const
logger("Find strong txs.");
count = query_.tx_records();
- while (!cancel_ && (tx < count) && query_.is_strong_tx(tx))
+ while (!canceled() && (tx < count) && query_.is_strong_tx(tx))
{
++tx;
}
@@ -366,7 +366,7 @@ void executor::read_test(const hash_digest&) const
size_t total{};
logger("Get all coinbases.");
- while (!cancel_ && (block <= top))
+ while (!canceled() && (block <= top))
{
const auto count = query_.get_tx_count(query_.to_candidate(block++));
if (is_zero(count))
@@ -422,7 +422,7 @@ void executor::read_test(const hash_digest&) const
database::tx_link spender_link{};
const auto hash_spender = system::base16_hash("1ff970ec310c000595929bd290bbc8f4603ee18b2b4e3239dfb072aaca012b28");
- for (auto position = zero; !cancel_ && position < txs.size(); ++position)
+ for (auto position = zero; !canceled() && position < txs.size(); ++position)
{
const auto temp = txs.at(position);
if (query_.get_tx_key(temp) == hash_spender)
@@ -466,7 +466,7 @@ void executor::read_test(const hash_digest&) const
database::tx_link spent_link{};
const auto hash_spent = system::base16_hash("85f65b57b88b74fd945a66a6ba392a5f3c8a7c0f78c8397228dece885d788841");
- for (auto position = zero; !cancel_ && position < txs.size(); ++position)
+ for (auto position = zero; !canceled() && position < txs.size(); ++position)
{
const auto temp = txs.at(position);
if (query_.get_tx_key(temp) == hash_spent)
@@ -640,7 +640,7 @@ void executor::read_test(const hash_digest&) const
auto tx = 664'400'000_size;
// Read all data except genesis (ie. for validation).
- while (!cancel_ && (++tx < query_.tx_records()))
+ while (!canceled() && (++tx < query_.tx_records()))
{
const tx_link link{
system::possible_narrow_cast(tx) };
@@ -681,7 +681,7 @@ void executor::read_test(const hash_digest&) const
duration_cast(fine_clock::now() - start).count());
}
- if (cancel_)
+ if (canceled())
logger(BS_OPERATION_CANCELED);
const auto span = duration_cast(fine_clock::now() - start);
@@ -698,7 +698,7 @@ void executor::read_test(const hash_digest&) const
std::getline(input_, line);
const auto start = fine_clock::now();
- for (size_t height = 492'224; (height <= 492'224) && !cancel_; ++height)
+ for (size_t height = 492'224; (height <= 492'224) && !canceled(); ++height)
{
// 2s 0s
const auto link = query_.to_header(hash492224);
diff --git a/console/executor_windows.cpp b/console/executor_windows.cpp
new file mode 100644
index 00000000..e61fd2d5
--- /dev/null
+++ b/console/executor_windows.cpp
@@ -0,0 +1,157 @@
+/**
+ * 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"
+
+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
+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;
+ }
+}
+
+// static
+LRESULT CALLBACK executor::window_proc(HWND handle, UINT message,
+ WPARAM wparam, LPARAM lparam)
+{
+ switch (message)
+ {
+ // Reject session close until process completion, initiate stop, and
+ // 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));
+ return FALSE;
+ }
+ default:
+ {
+ return ::DefWindowProcW(handle, message, wparam, lparam);
+ }
+ }
+}
+
+void executor::create_hidden_window()
+{
+ thread_ = std::thread([this]()
+ {
+ const auto instance = ::GetModuleHandleW(NULL);
+ const WNDCLASSEXW window_class
+ {
+ .cbSize = sizeof(WNDCLASSEXW),
+ .style = CS_HREDRAW | CS_VREDRAW,
+ .lpfnWndProc = &executor::window_proc,
+ .hInstance = instance,
+ .hIcon = ::LoadIconW(instance, MAKEINTRESOURCEW(101)),
+ .lpszClassName = window_name,
+ .hIconSm = ::LoadIconW(instance, MAKEINTRESOURCEW(101))
+ };
+
+ // fault
+ if (is_zero(::RegisterClassExW(&window_class)))
+ return;
+
+ // Zero sizing results in title bar only.
+ // WS_EX_NOACTIVATE: prevents focus-stealing.
+ // WS_VISIBLE: required to capture WM_QUERYENDSESSION.
+ window_ = ::CreateWindowExW
+ (
+ WS_EX_NOACTIVATE,
+ window_name,
+ window_title,
+ WS_VISIBLE,
+ 0, 0, 0, 0,
+ NULL,
+ NULL,
+ ::GetModuleHandleW(NULL),
+ NULL);
+
+ // fault
+ if (is_null(window_))
+ return;
+
+ MSG message{};
+ BOOL result{};
+ while (!is_zero(result = ::GetMessageW(&message, NULL, 0, 0)))
+ {
+ // fault
+ if (is_negative(result))
+ return;
+
+ ::TranslateMessage(&message);
+ ::DispatchMessageW(&message);
+ }
+ });
+}
+
+void executor::destroy_hidden_window()
+{
+ if (!is_null(window_))
+ ::PostMessageW(window_, WM_QUIT, 0, 0);
+
+ if (thread_.joinable())
+ thread_.join();
+
+ if (!is_null(window_))
+ {
+ ::DestroyWindow(window_);
+ window_ = NULL;
+ }
+
+ const auto handle = ::GetConsoleWindow();
+ if (!is_null(handle))
+ ::ShutdownBlockReasonDestroy(handle);
+}
+
+#endif // HAVE_MSC
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/libbitcoin.ico b/console/libbitcoin.ico
deleted file mode 100644
index 79464a84..00000000
Binary files a/console/libbitcoin.ico and /dev/null differ
diff --git a/console/localize.hpp b/console/localize.hpp
index 914fc738..09f25a87 100644
--- a/console/localize.hpp
+++ b/console/localize.hpp
@@ -180,6 +180,8 @@ namespace server {
"Node failed to start with error '%1%'."
#define BS_NODE_UNAVAILABLE \
"Command not available until node started."
+#define BS_NODE_INTERRUPTED \
+ "Node was interrupted by signal (%1%)."
#define BS_NODE_BACKUP_STARTED \
"Snapshot is started."