From 11e0255f64b1e42493962de18447817b9fd5bb0e Mon Sep 17 00:00:00 2001 From: Enoch Azariah Date: Fri, 8 May 2026 15:27:16 +0100 Subject: [PATCH 1/5] Squashed 'src/ipc/libmultiprocess/' changes from 3edbe8f..6de92e1 6de92e1 proxy-client: tolerate exceptions from remote destroy during cleanup 90be835 test: regression for ~ProxyClient destroy after peer disconnect 3c69d12 Merge bitcoin-core/libmultiprocess#260: event loop: tolerate unexpected exceptions in `post()` callbacks b8a48c6 event loop: tolerate unexpected exceptions in `post()` callbacks f787863 Merge bitcoin-core/libmultiprocess#270: doc: Bump version 10 > 11 a22f602 doc: Bump version 10 > 11 git-subtree-dir: src/ipc/libmultiprocess git-subtree-split: 6de92e1c7324c4748d05687372256a5051c97bb4 --- doc/versions.md | 7 ++++++- include/mp/proxy-io.h | 7 ++++++- include/mp/version.h | 2 +- src/mp/proxy.cpp | 8 +++++++- test/mp/test/test.cpp | 26 ++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/doc/versions.md b/doc/versions.md index 2c2ec50e..3cfa28e3 100644 --- a/doc/versions.md +++ b/doc/versions.md @@ -7,9 +7,14 @@ Library versions are tracked with simple Versioning policy is described in the [version.h](../include/mp/version.h) include. -## v10 +## v11 - Current unstable version. +## [v10.0](https://github.com/bitcoin-core/libmultiprocess/commits/v10.0) +- Increases spawn test timeout to avoid spurious failures. +- Uses `throwRecoverableException` instead of raw `throw` to improve runtime error messages in macOS builds. +- Used in Bitcoin Core master branch, pulled in by [#34977](https://github.com/bitcoin/bitcoin/pull/34977). Also pulled into Bitcoin Core 31.x stable branch by [#35028](https://github.com/bitcoin/bitcoin/pull/35028). + ## [v9.0](https://github.com/bitcoin-core/libmultiprocess/commits/v9.0) - Fixes race conditions where worker thread could be used after destruction, where getParams() could be called after request cancel, and where m_on_cancel could be called after request finishes. - Adds `CustomHasField` hook to map Cap'n Proto null values to C++ null values. diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index d7b9f0e5..09465c04 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -538,7 +538,12 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli // the remote object, waiting for it to be deleted server side. If the // capnp interface does not define a destroy method, this will just call // an empty stub defined in the ProxyClientBase class and do nothing. - Sub::destroy(*this); + // Exceptions are caught and logged rather than propagated because + // ~ProxyClientBase is noexcept and the peer may be gone by the time + // this runs. + if (kj::runCatchingExceptions([&]{ Sub::destroy(*this); }) != nullptr) { + MP_LOG(*m_context.loop, Log::Warning) << "Remote destroy call failed during cleanup. Continuing."; + } // FIXME: Could just invoke removed addCleanup fn here instead of duplicating code m_context.loop->sync([&]() { diff --git a/include/mp/version.h b/include/mp/version.h index 964667a9..423ed460 100644 --- a/include/mp/version.h +++ b/include/mp/version.h @@ -24,7 +24,7 @@ //! pointing at the prior merge commit. The /doc/versions.md file should also be //! updated, noting any significant or incompatible changes made since the //! previous version. -#define MP_MAJOR_VERSION 10 +#define MP_MAJOR_VERSION 11 //! Minor version number. Should be incremented in stable branches after //! backporting changes. The /doc/versions.md file should also be updated to diff --git a/src/mp/proxy.cpp b/src/mp/proxy.cpp index d24208db..963050c3 100644 --- a/src/mp/proxy.cpp +++ b/src/mp/proxy.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -245,7 +246,12 @@ void EventLoop::loop() if (read_bytes != 1) throw std::logic_error("EventLoop wait_stream closed unexpectedly"); Lock lock(m_mutex); if (m_post_fn) { - Unlock(lock, *m_post_fn); + // m_post_fn throwing is never expected. If it does happen, the caller + // of EventLoop::post() will return without any indication of failure, + // which will likely cause other bugs. Log the error and continue. + KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() MP_REQUIRES(m_mutex) { Unlock(lock, *m_post_fn); })) { + MP_LOG(*this, Log::Error) << "EventLoop: m_post_fn threw: " << kj::str(*exception).cStr(); + } m_post_fn = nullptr; m_cv.notify_all(); } else if (done()) { diff --git a/test/mp/test/test.cpp b/test/mp/test/test.cpp index d91edb40..b259790b 100644 --- a/test/mp/test/test.cpp +++ b/test/mp/test/test.cpp @@ -427,6 +427,32 @@ KJ_TEST("Calling async IPC method, with server disconnect after cleanup") } } +KJ_TEST("Destroying ProxyClient<> with destroy method after peer disconnect") +{ + // Regression test for bitcoin-core/libmultiprocess#219 where + // ~ProxyClientBase would call std::terminate if the remote destroy RPC + // failed during teardown. + // + // Save a callback on the server so it holds a ProxyClient + // pointing back to this side, then disconnect. When the server is torn + // down, the ProxyClient destructor issues a destroy RPC over + // the now dead connection; without the bugfix the exception escapes the + // noexcept destructor and aborts the process. + + TestSetup setup{/*client_owns_connection=*/false}; + ProxyClient* foo = setup.client.get(); + foo->initThreadMap(); + + class Callback : public FooCallback + { + public: + int call(int arg) override { return arg; } + }; + + foo->saveCallback(std::make_shared()); + setup.client_disconnect(); +} + KJ_TEST("Make simultaneous IPC calls on single remote thread") { TestSetup setup; From d2f7bbe9715b457bc0d03f597b384afb75095eed Mon Sep 17 00:00:00 2001 From: Enoch Azariah Date: Mon, 4 May 2026 14:18:11 +0100 Subject: [PATCH 2/5] sv2-tp: reconnect to bitcoin node with backoff Add a reconnect loop around the initial Bitcoin Core IPC setup. When the IPC connection cannot be established, retry with exponential backoff instead of exiting immediately. This provides the basis for recovering sv2-tp after backend loss. --- src/sv2-tp.cpp | 83 +++++++++++++++----- src/sv2/template_provider.cpp | 138 +++++++++++++++++++--------------- src/sv2/template_provider.h | 7 ++ 3 files changed, 148 insertions(+), 80 deletions(-) diff --git a/src/sv2-tp.cpp b/src/sv2-tp.cpp index 419998f1..5c44eabd 100644 --- a/src/sv2-tp.cpp +++ b/src/sv2-tp.cpp @@ -125,6 +125,10 @@ static void AddArgs(ArgsManager& args) } static bool g_interrupt{false}; +namespace { +constexpr auto IPC_RECONNECT_INITIAL_DELAY{std::chrono::seconds{1}}; +constexpr auto IPC_RECONNECT_MAX_DELAY{std::chrono::seconds{32}}; +} #ifndef WIN32 static void registerSignalHandler(int signal, void(*handler)(int)) @@ -231,31 +235,44 @@ MAIN_FUNCTION options.template_interval = std::chrono::seconds(args.GetIntArg("-templateinterval", 0)); } - // Connect to bitcoin-node via IPC - // - // If the node is not available, keep retrying in a loop every 10 seconds. std::unique_ptr mine_init{interfaces::MakeBasicInit("sv2-tp", argc > 0 ? argv[0] : "")}; assert(mine_init); - std::unique_ptr node_init; std::string address{args.GetArg("-ipcconnect", "unix")}; LogPrintf("Attempting IPC connection to bitcoin-node at %s\n", address); LogPrintf("Ensure Bitcoin Core is running with '-ipcbind=unix' and the correct network (%s)\n", ChainTypeToString(chain_type)); - while (true) { - try { - node_init = mine_init->ipc()->connectAddress(address); - LogPrintf("Connected to bitcoin-node via IPC at: %s\n", address); - break; // Success: break out of the loop - } catch (const std::exception& exception) { - LogPrintf("IPC connection failed: %s\n", exception.what()); - LogPrintf("Retrying in 10 seconds... (Ensure Bitcoin Core is running with '-ipcbind=unix')\n"); - std::this_thread::sleep_for(std::chrono::seconds(10)); + const auto connect_to_node = [&]() -> std::pair, std::unique_ptr> { + auto retry_delay = IPC_RECONNECT_INITIAL_DELAY; + while (!g_interrupt) { + try { + std::unique_ptr node_init = mine_init->ipc()->connectAddress(address); + if (!node_init) { + throw std::runtime_error("IPC connection returned no remote init interface"); + } + std::unique_ptr mining = node_init->makeMining(); + if (!mining) { + throw std::runtime_error("IPC connection returned no mining interface"); + } + LogPrintf("Connected to bitcoin-node via IPC at: %s\n", address); + return {std::move(node_init), std::move(mining)}; + } catch (const std::exception& exception) { + LogPrintf("IPC connection failed: %s\n", exception.what()); + LogPrintf("Retrying in %d seconds... (Ensure Bitcoin Core is running with '-ipcbind=unix')\n", + retry_delay.count()); + std::this_thread::sleep_for(retry_delay); + retry_delay = std::min(retry_delay * 2, IPC_RECONNECT_MAX_DELAY); + } } - } + return {}; + }; + + auto connection = connect_to_node(); + auto node_init = std::move(connection.first); + auto mining = std::move(connection.second); + if (g_interrupt) return EXIT_SUCCESS; assert(node_init); - std::unique_ptr mining{node_init->makeMining()}; assert(mining); auto tp = std::make_unique(*mining); @@ -273,13 +290,41 @@ MAIN_FUNCTION registerSignalHandler(SIGINT, HandleSIGTERM); #endif - while(!g_interrupt) { + while (!g_interrupt && !tp->BackendDisconnected()) { UninterruptibleSleep(100ms); } - tp->Interrupt(); - tp->StopThreads(); - tp.reset(); + while (!g_interrupt) { + LogPrintf("Restarting sv2-tp after Bitcoin Core IPC disconnect\n"); + tp->Interrupt(); + tp->StopThreads(); + tp.reset(); + mining.reset(); + node_init.reset(); + + connection = connect_to_node(); + node_init = std::move(connection.first); + mining = std::move(connection.second); + if (g_interrupt) break; + assert(node_init); + assert(mining); + + tp = std::make_unique(*mining); + if (!tp->Start(options)) { + tfm::format(std::cerr, "Unable to start Stratum v2 Template Provider"); + return EXIT_FAILURE; + } + + while (!g_interrupt && !tp->BackendDisconnected()) { + UninterruptibleSleep(100ms); + } + } + + if (tp) { + tp->Interrupt(); + tp->StopThreads(); + tp.reset(); + } return EXIT_SUCCESS; } diff --git a/src/sv2/template_provider.cpp b/src/sv2/template_provider.cpp index a445bd46..3da220e7 100644 --- a/src/sv2/template_provider.cpp +++ b/src/sv2/template_provider.cpp @@ -20,6 +20,42 @@ // Allow a few seconds for clients to submit a block or to request transactions constexpr size_t STALE_TEMPLATE_GRACE_PERIOD{10}; +void Sv2TemplateProvider::DisconnectBackend(const char* operation, const std::exception& exception) +{ + const bool first_disconnect = !m_backend_disconnected.exchange(true); + if (first_disconnect) { + LogPrintLevel(BCLog::SV2, BCLog::Level::Error, + "Bitcoin Core IPC connection lost during %s: %s\n", + operation, exception.what()); + } else { + LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, + "Ignoring repeated Bitcoin Core IPC failure during %s: %s\n", + operation, exception.what()); + } + + m_flag_interrupt_sv2 = true; + if (m_connman) m_connman->Interrupt(); +} + +void Sv2TemplateProvider::InterruptMining() +{ + { + LOCK(m_tp_mutex); + for (auto& t : GetBlockTemplates()) { + try { + t.second.second->interruptWait(); + } catch (const ipc::Exception& e) { + DisconnectBackend("interruptWait", e); + } + } + } + try { + m_mining.interrupt(); + } catch (const ipc::Exception& e) { + DisconnectBackend("interrupt", e); + } +} + Sv2TemplateProvider::Sv2TemplateProvider(interfaces::Mining& mining) : m_mining{mining} { // TODO: persist static key @@ -129,15 +165,8 @@ void Sv2TemplateProvider::Interrupt() AssertLockNotHeld(m_tp_mutex); LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Interrupt pending mining waits..."); - { - LOCK(m_tp_mutex); - for (auto& t : GetBlockTemplates()) { - t.second.second->interruptWait(); - } - } - m_flag_interrupt_sv2 = true; - m_mining.interrupt(); + InterruptMining(); // Also interrupt network threads so client handlers can wind down quickly. if (m_connman) m_connman->Interrupt(); } @@ -176,70 +205,55 @@ class Timer { void Sv2TemplateProvider::ThreadSv2Handler() { - // Make sure it's initialized, doesn't need to be accurate. - { - LOCK(m_tp_mutex); - m_last_block_time = GetTime(); - } - - // Wait to come out of IBD, except on signet, where we might be the only miner. - size_t log_ibd{0}; - while (!m_flag_interrupt_sv2 && gArgs.GetChainType() != ChainType::SIGNET) { - // TODO: Wait until there's no headers-only branch with more work than our chaintip. - // The current check can still cause us to broadcast a few dozen useless templates - // at startup. - if (!m_mining.isInitialBlockDownload()) break; - if (log_ibd == 0) { - LogPrintf("Waiting for IBD to complete on %s network before serving templates (this may take a while)\n", - ChainTypeToString(gArgs.GetChainType())); - } else if (log_ibd % 10 == 0) { - LogPrintf(".\n"); + try { + // Make sure it's initialized, doesn't need to be accurate. + { + LOCK(m_tp_mutex); + m_last_block_time = GetTime(); } - log_ibd++; - std::this_thread::sleep_for(1000ms); - } - std::map client_threads; + // Wait to come out of IBD, except on signet, where we might be the only miner. + size_t log_ibd{0}; + while (!m_flag_interrupt_sv2 && gArgs.GetChainType() != ChainType::SIGNET) { + if (!m_mining.isInitialBlockDownload()) break; + if (log_ibd == 0) { + LogPrintf("Waiting for IBD to complete on %s network before serving templates (this may take a while)\n", + ChainTypeToString(gArgs.GetChainType())); + } else if (log_ibd % 10 == 0) { + LogPrintf(".\n"); + } + log_ibd++; + std::this_thread::sleep_for(1000ms); + } - while (!m_flag_interrupt_sv2) { - // We start with one template per client, which has an interface through - // which we monitor for better templates. + std::map client_threads; - m_connman->ForEachClient([this, &client_threads](Sv2Client& client) { - /** - * The initial handshake is handled on the Sv2Connman thread. This - * consists of the noise protocol handshake and the initial Stratum - * v2 messages SetupConnection and CoinbaseOutputConstraints. - * - * A further refactor should make that part non-blocking. But for - * now we spin up a thread here. - */ - if (!client.m_coinbase_output_constraints_recv) return; + while (!m_flag_interrupt_sv2) { + m_connman->ForEachClient([this, &client_threads](Sv2Client& client) { + if (!client.m_coinbase_output_constraints_recv) return; - if (client_threads.contains(client.m_id)) return; + if (client_threads.contains(client.m_id)) return; - client_threads.emplace(client.m_id, - std::thread(&util::TraceThread, - strprintf("sv2-%zu", client.m_id), - [this, &client] { ThreadSv2ClientHandler(client.m_id); })); - }); + client_threads.emplace(client.m_id, + std::thread(&util::TraceThread, + strprintf("sv2-%zu", client.m_id), + [this, &client] { ThreadSv2ClientHandler(client.m_id); })); + }); - // Take a break (handling new connections is not urgent) - std::this_thread::sleep_for(100ms); + std::this_thread::sleep_for(100ms); - LOCK(m_tp_mutex); - PruneBlockTemplateCache(); - } + LOCK(m_tp_mutex); + PruneBlockTemplateCache(); + } - for (auto& thread : client_threads) { - if (thread.second.joinable()) { - // If the node is shutting down, then all pending waitNext() calls - // should return in under a second. - thread.second.join(); + for (auto& thread : client_threads) { + if (thread.second.joinable()) { + thread.second.join(); + } } + } catch (const ipc::Exception& e) { + DisconnectBackend("template provider main loop", e); } - - } void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) @@ -405,6 +419,8 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) std::this_thread::sleep_for(50ms); } } + } catch (const ipc::Exception& e) { + DisconnectBackend("template provider client loop", e); } catch (const std::exception& e) { LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Client thread for id=%zu exiting after exception: %s\n", diff --git a/src/sv2/template_provider.h b/src/sv2/template_provider.h index 5b53bcb6..fa91642b 100644 --- a/src/sv2/template_provider.h +++ b/src/sv2/template_provider.h @@ -11,6 +11,7 @@ #include #include #include +#include using interfaces::BlockTemplate; @@ -86,6 +87,7 @@ class Sv2TemplateProvider : public Sv2EventsInterface * Signal for handling interrupts and stopping the template provider event loop. */ std::atomic m_flag_interrupt_sv2{false}; + std::atomic m_backend_disconnected{false}; CThreadInterrupt m_interrupt_sv2; /** @@ -124,6 +126,8 @@ class Sv2TemplateProvider : public Sv2EventsInterface */ [[nodiscard]] bool Start(const Sv2TemplateProviderOptions& options = {}); + bool BackendDisconnected() const { return m_backend_disconnected.load(); } + /** * The main thread for the template provider, contains an event loop handling * all tasks for the template provider. @@ -189,6 +193,9 @@ class Sv2TemplateProvider : public Sv2EventsInterface */ [[nodiscard]] bool SendWork(Sv2Client& client, uint64_t template_id, BlockTemplate& block_template, bool future_template); + void DisconnectBackend(const char* operation, const std::exception& exception); + void InterruptMining(); + }; #endif // BITCOIN_SV2_TEMPLATE_PROVIDER_H From b25a1bd1c28c262cd8259b3e189dc3643bf5fe1a Mon Sep 17 00:00:00 2001 From: Enoch Azariah Date: Wed, 6 May 2026 10:56:00 +0100 Subject: [PATCH 3/5] sv2: keep template provider alive across reconnect Decouple the template provider lifetime from the Bitcoin Core IPC backend. Keep the Stratum v2 listener and connected clients alive when the backend disconnects, wait for a replacement backend, and resume serving templates once a new IPC connection is installed. --- src/sv2-tp.cpp | 37 ++---- src/sv2/template_provider.cpp | 225 ++++++++++++++++++++++++++-------- src/sv2/template_provider.h | 70 +++++++++-- 3 files changed, 246 insertions(+), 86 deletions(-) diff --git a/src/sv2-tp.cpp b/src/sv2-tp.cpp index 5c44eabd..2bfcf339 100644 --- a/src/sv2-tp.cpp +++ b/src/sv2-tp.cpp @@ -275,7 +275,8 @@ MAIN_FUNCTION assert(node_init); assert(mining); - auto tp = std::make_unique(*mining); + auto tp = std::make_unique(); + tp->ReplaceBackend(std::move(node_init), std::move(mining)); if (!tp->Start(options)) { tfm::format(std::cerr, "Unable to start Stratum v2 Template Provider"); @@ -290,41 +291,25 @@ MAIN_FUNCTION registerSignalHandler(SIGINT, HandleSIGTERM); #endif - while (!g_interrupt && !tp->BackendDisconnected()) { - UninterruptibleSleep(100ms); - } - while (!g_interrupt) { - LogPrintf("Restarting sv2-tp after Bitcoin Core IPC disconnect\n"); - tp->Interrupt(); - tp->StopThreads(); - tp.reset(); - mining.reset(); - node_init.reset(); + if (!tp->BackendDisconnected()) { + UninterruptibleSleep(100ms); + continue; + } + LogPrintf("Restarting sv2-tp after Bitcoin Core IPC disconnect\n"); connection = connect_to_node(); node_init = std::move(connection.first); mining = std::move(connection.second); if (g_interrupt) break; assert(node_init); assert(mining); - - tp = std::make_unique(*mining); - if (!tp->Start(options)) { - tfm::format(std::cerr, "Unable to start Stratum v2 Template Provider"); - return EXIT_FAILURE; - } - - while (!g_interrupt && !tp->BackendDisconnected()) { - UninterruptibleSleep(100ms); - } + tp->ReplaceBackend(std::move(node_init), std::move(mining)); } - if (tp) { - tp->Interrupt(); - tp->StopThreads(); - tp.reset(); - } + tp->Interrupt(); + tp->StopThreads(); + tp.reset(); return EXIT_SUCCESS; } diff --git a/src/sv2/template_provider.cpp b/src/sv2/template_provider.cpp index 3da220e7..f73f633a 100644 --- a/src/sv2/template_provider.cpp +++ b/src/sv2/template_provider.cpp @@ -20,9 +20,71 @@ // Allow a few seconds for clients to submit a block or to request transactions constexpr size_t STALE_TEMPLATE_GRACE_PERIOD{10}; -void Sv2TemplateProvider::DisconnectBackend(const char* operation, const std::exception& exception) +template +std::shared_ptr MakeSharedIpcProxy(std::unique_ptr proxy, + const std::shared_ptr>& disconnected) { - const bool first_disconnect = !m_backend_disconnected.exchange(true); + return std::shared_ptr(proxy.release(), [disconnected](T* ptr) { + if (!ptr) return; + if (disconnected->load()) return; + delete ptr; + }); +} + +Sv2TemplateProvider::BackendSession::BackendSession(std::unique_ptr init, + std::unique_ptr mining) : + m_init(init.release(), IpcProxyDeleter{m_disconnected}), + m_mining(mining.release(), IpcProxyDeleter{m_disconnected}) +{ +} + +void Sv2TemplateProvider::ReplaceBackend(std::unique_ptr node_init, + std::unique_ptr mining) +{ + auto backend = std::make_shared(std::move(node_init), std::move(mining)); + { + LOCK(m_backend_mutex); + m_backend = backend; + } + m_backend_disconnected = false; + { + LOCK(m_tp_mutex); + ClearTemplateCache(); + } + m_backend_cv.notify_all(); +} + +std::shared_ptr Sv2TemplateProvider::WaitForBackend() +{ + WAIT_LOCK(m_backend_mutex, lock); + while (!m_flag_interrupt_sv2 && !m_backend) { + m_backend_cv.wait(lock); + } + if (m_flag_interrupt_sv2) return nullptr; + return m_backend; +} + +void Sv2TemplateProvider::DisconnectBackend(const std::shared_ptr& backend, + const char* operation, + const std::exception& exception) +{ + if (!backend) return; + const bool first_disconnect = backend->MarkDisconnected(); + std::shared_ptr active_backend; + { + LOCK(m_backend_mutex); + if (m_backend == backend) { + active_backend = m_backend; + m_backend.reset(); + } + } + if (!active_backend) return; + + m_backend_disconnected = true; + { + LOCK(m_tp_mutex); + ClearTemplateCache(); + } if (first_disconnect) { LogPrintLevel(BCLog::SV2, BCLog::Level::Error, "Bitcoin Core IPC connection lost during %s: %s\n", @@ -33,30 +95,36 @@ void Sv2TemplateProvider::DisconnectBackend(const char* operation, const std::ex operation, exception.what()); } - m_flag_interrupt_sv2 = true; - if (m_connman) m_connman->Interrupt(); + m_backend_cv.notify_all(); } -void Sv2TemplateProvider::InterruptMining() +void Sv2TemplateProvider::InterruptBackend() { + std::shared_ptr backend; + { + LOCK(m_backend_mutex); + backend = m_backend; + } + if (!backend) return; + { LOCK(m_tp_mutex); for (auto& t : GetBlockTemplates()) { try { t.second.second->interruptWait(); } catch (const ipc::Exception& e) { - DisconnectBackend("interruptWait", e); + DisconnectBackend(backend, "interruptWait", e); } } } try { - m_mining.interrupt(); + backend->Mining().interrupt(); } catch (const ipc::Exception& e) { - DisconnectBackend("interrupt", e); + DisconnectBackend(backend, "interrupt", e); } } -Sv2TemplateProvider::Sv2TemplateProvider(interfaces::Mining& mining) : m_mining{mining} +Sv2TemplateProvider::Sv2TemplateProvider() { // TODO: persist static key CKey static_key; @@ -153,6 +221,19 @@ Sv2TemplateProvider::~Sv2TemplateProvider() { AssertLockNotHeld(m_tp_mutex); + { + LOCK(m_backend_mutex); + if (m_backend) { + m_backend->MarkDisconnected(); + m_backend.reset(); + } + } + { + LOCK(m_tp_mutex); + ClearTemplateCache(); + } + m_backend_cv.notify_all(); + m_connman->Interrupt(); m_connman->StopThreads(); @@ -166,7 +247,8 @@ void Sv2TemplateProvider::Interrupt() LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Interrupt pending mining waits..."); m_flag_interrupt_sv2 = true; - InterruptMining(); + m_backend_cv.notify_all(); + InterruptBackend(); // Also interrupt network threads so client handlers can wind down quickly. if (m_connman) m_connman->Interrupt(); } @@ -203,56 +285,64 @@ class Timer { } }; +void Sv2TemplateProvider::ClearTemplateCache() +{ + AssertLockHeld(m_tp_mutex); + m_block_template_cache.clear(); + m_best_prev_hash = uint256::ZERO; + m_last_block_time = GetTime(); +} + void Sv2TemplateProvider::ThreadSv2Handler() { - try { - // Make sure it's initialized, doesn't need to be accurate. + { + LOCK(m_tp_mutex); + m_last_block_time = GetTime(); + } + + std::map client_threads; + + while (!m_flag_interrupt_sv2) { + std::shared_ptr backend; { - LOCK(m_tp_mutex); - m_last_block_time = GetTime(); + LOCK(m_backend_mutex); + backend = m_backend; } - // Wait to come out of IBD, except on signet, where we might be the only miner. - size_t log_ibd{0}; - while (!m_flag_interrupt_sv2 && gArgs.GetChainType() != ChainType::SIGNET) { - if (!m_mining.isInitialBlockDownload()) break; - if (log_ibd == 0) { - LogPrintf("Waiting for IBD to complete on %s network before serving templates (this may take a while)\n", - ChainTypeToString(gArgs.GetChainType())); - } else if (log_ibd % 10 == 0) { - LogPrintf(".\n"); + if (backend && gArgs.GetChainType() != ChainType::SIGNET) { + try { + if (backend->Mining().isInitialBlockDownload()) { + std::this_thread::sleep_for(1000ms); + continue; + } + } catch (const ipc::Exception& e) { + DisconnectBackend(backend, "template provider main loop", e); + continue; } - log_ibd++; - std::this_thread::sleep_for(1000ms); } - std::map client_threads; - - while (!m_flag_interrupt_sv2) { - m_connman->ForEachClient([this, &client_threads](Sv2Client& client) { - if (!client.m_coinbase_output_constraints_recv) return; + m_connman->ForEachClient([this, &client_threads](Sv2Client& client) { + if (!client.m_coinbase_output_constraints_recv) return; - if (client_threads.contains(client.m_id)) return; + if (client_threads.contains(client.m_id)) return; - client_threads.emplace(client.m_id, - std::thread(&util::TraceThread, - strprintf("sv2-%zu", client.m_id), - [this, &client] { ThreadSv2ClientHandler(client.m_id); })); - }); + const size_t client_id = client.m_id; + client_threads.emplace(client_id, + std::thread(&util::TraceThread, + strprintf("sv2-%zu", client_id), + [this, client_id] { ThreadSv2ClientHandler(client_id); })); + }); - std::this_thread::sleep_for(100ms); + std::this_thread::sleep_for(100ms); - LOCK(m_tp_mutex); - PruneBlockTemplateCache(); - } + LOCK(m_tp_mutex); + PruneBlockTemplateCache(); + } - for (auto& thread : client_threads) { - if (thread.second.joinable()) { - thread.second.join(); - } + for (auto& thread : client_threads) { + if (thread.second.joinable()) { + thread.second.join(); } - } catch (const ipc::Exception& e) { - DisconnectBackend("template provider main loop", e); } } @@ -285,9 +375,28 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) }; std::shared_ptr block_template; + std::shared_ptr backend; + std::shared_ptr template_backend; // Cache most recent block_template->getBlockHeader().hashPrevBlock result. uint256 prev_hash; while (!m_flag_interrupt_sv2) { + if (!backend) { + backend = WaitForBackend(); + if (!backend) break; + } + + if (backend->Disconnected()) { + backend.reset(); + block_template.reset(); + template_backend.reset(); + continue; + } + + if (template_backend && template_backend != backend) { + block_template.reset(); + template_backend.reset(); + } + if (!block_template) { LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Generate initial block template for client id=%zu\n", client_id); @@ -300,12 +409,20 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) if (!prepare_block_create_options(block_create_options)) break; const auto time_start{SteadyClock::now()}; - block_template = m_mining.createNewBlock(block_create_options); + try { + block_template = MakeSharedIpcProxy(backend->Mining().createNewBlock(block_create_options), + backend->DisconnectState()); + } catch (const ipc::Exception& e) { + DisconnectBackend(backend, "createNewBlock", e); + backend.reset(); + continue; + } if (!block_template) { LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "No new template for client id=%zu, node is shutting down\n", client_id); break; } + template_backend = backend; LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Assemble template: %.2fms\n", Ticks(SteadyClock::now() - time_start)); @@ -367,7 +484,16 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) client_id); } - std::shared_ptr tmpl = block_template->waitNext(options); + std::shared_ptr tmpl; + try { + tmpl = MakeSharedIpcProxy(block_template->waitNext(options), template_backend->DisconnectState()); + } catch (const ipc::Exception& e) { + DisconnectBackend(template_backend, "template provider client loop", e); + backend.reset(); + block_template.reset(); + template_backend.reset(); + continue; + } // The client may have disconnected during the wait, check now to avoid // a spurious IPC call and confusing log statements. { @@ -378,6 +504,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) // After timeout and during node shutdown this is expect to not be set if (tmpl) { block_template = tmpl; + template_backend = backend; uint256 new_prev_hash{block_template->getBlockHeader().hashPrevBlock}; { @@ -419,8 +546,6 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) std::this_thread::sleep_for(50ms); } } - } catch (const ipc::Exception& e) { - DisconnectBackend("template provider client loop", e); } catch (const std::exception& e) { LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Client thread for id=%zu exiting after exception: %s\n", diff --git a/src/sv2/template_provider.h b/src/sv2/template_provider.h index fa91642b..e17ecd14 100644 --- a/src/sv2/template_provider.h +++ b/src/sv2/template_provider.h @@ -2,6 +2,7 @@ #define BITCOIN_SV2_TEMPLATE_PROVIDER_H #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include using interfaces::BlockTemplate; @@ -54,10 +56,50 @@ class Sv2TemplateProvider : public Sv2EventsInterface private: /** - * The Mining interface is used to build new valid blocks, get the best known - * block hash and to check whether the node is still in IBD. + * The active Bitcoin Core IPC backend generation. */ - interfaces::Mining& m_mining; + struct BackendSession { + template + struct IpcProxyDeleter { + std::shared_ptr> disconnected; + void operator()(T* ptr) const + { + if (!ptr) return; + if (disconnected->load()) return; + delete ptr; + } + }; + + using InitPtr = std::unique_ptr>; + using MiningPtr = std::unique_ptr>; + + explicit BackendSession(std::unique_ptr init, std::unique_ptr mining); + + bool MarkDisconnected() + { + return !m_disconnected->exchange(true); + } + + bool Disconnected() const + { + return m_disconnected->load(); + } + + interfaces::Mining& Mining() + { + return *m_mining; + } + + const std::shared_ptr>& DisconnectState() const + { + return m_disconnected; + } + + private: + std::shared_ptr> m_disconnected{std::make_shared>(false)}; + InitPtr m_init; + MiningPtr m_mining; + }; /* * The template provider subprotocol used in setup connection messages. The stratum v2 @@ -89,6 +131,9 @@ class Sv2TemplateProvider : public Sv2EventsInterface std::atomic m_flag_interrupt_sv2{false}; std::atomic m_backend_disconnected{false}; CThreadInterrupt m_interrupt_sv2; + Mutex m_backend_mutex; + std::condition_variable_any m_backend_cv; + std::shared_ptr m_backend GUARDED_BY(m_backend_mutex); /** * The most recent template id. This is incremented on creating new template, @@ -114,9 +159,9 @@ class Sv2TemplateProvider : public Sv2EventsInterface BlockTemplateCache m_block_template_cache GUARDED_BY(m_tp_mutex); public: - explicit Sv2TemplateProvider(interfaces::Mining& mining); + Sv2TemplateProvider(); - ~Sv2TemplateProvider() EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex); + ~Sv2TemplateProvider() EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex, !m_backend_mutex); Mutex m_tp_mutex; @@ -127,12 +172,14 @@ class Sv2TemplateProvider : public Sv2EventsInterface [[nodiscard]] bool Start(const Sv2TemplateProviderOptions& options = {}); bool BackendDisconnected() const { return m_backend_disconnected.load(); } + void ReplaceBackend(std::unique_ptr node_init, + std::unique_ptr mining) EXCLUSIVE_LOCKS_REQUIRED(!m_backend_mutex, !m_tp_mutex); /** * The main thread for the template provider, contains an event loop handling * all tasks for the template provider. */ - void ThreadSv2Handler() EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex); + void ThreadSv2Handler() EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex, !m_backend_mutex); /** * Give each client its own thread so they're treated equally @@ -144,13 +191,13 @@ class Sv2TemplateProvider : public Sv2EventsInterface * connection. For the use case of a public facing template provider, * further changes are needed anyway e.g. for DoS resistance. */ - void ThreadSv2ClientHandler(size_t client_id) EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex); + void ThreadSv2ClientHandler(size_t client_id) EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex, !m_backend_mutex); /** * Triggered on interrupt signals to stop the main event loop in ThreadSv2Handler(). * Interrupts pending waitNext() calls */ - void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex); + void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!m_tp_mutex, !m_backend_mutex); /** * Tear down of the template provider thread and any other necessary tear down. @@ -193,8 +240,11 @@ class Sv2TemplateProvider : public Sv2EventsInterface */ [[nodiscard]] bool SendWork(Sv2Client& client, uint64_t template_id, BlockTemplate& block_template, bool future_template); - void DisconnectBackend(const char* operation, const std::exception& exception); - void InterruptMining(); + void ClearTemplateCache() EXCLUSIVE_LOCKS_REQUIRED(m_tp_mutex); + std::shared_ptr WaitForBackend() EXCLUSIVE_LOCKS_REQUIRED(!m_backend_mutex); + void DisconnectBackend(const std::shared_ptr& backend, const char* operation, const std::exception& exception) + EXCLUSIVE_LOCKS_REQUIRED(!m_backend_mutex, !m_tp_mutex); + void InterruptBackend() EXCLUSIVE_LOCKS_REQUIRED(!m_backend_mutex, !m_tp_mutex); }; From 83fba4f86af9ff95f33f0f545a924d2c851a94c7 Mon Sep 17 00:00:00 2001 From: Enoch Azariah Date: Mon, 4 May 2026 14:18:27 +0100 Subject: [PATCH 4/5] test: update sv2 template provider tester Adapt the sv2 template provider tests to the reconnect lifecycle. Construct the provider without a fixed Mining reference and install the backend through the new reconnect path so the test harness matches the runtime behavior. --- src/test/sv2_tp_tester.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/test/sv2_tp_tester.cpp b/src/test/sv2_tp_tester.cpp index 777bdf74..92da49d9 100644 --- a/src/test/sv2_tp_tester.cpp +++ b/src/test/sv2_tp_tester.cpp @@ -75,7 +75,8 @@ TPTester::TPTester(Sv2TemplateProviderOptions opts) BOOST_REQUIRE(m_mining_proxy != nullptr); // Construct Template Provider with the IPC-backed Mining proxy - m_tp = std::make_unique(*m_mining_proxy); + m_tp = std::make_unique(); + m_tp->ReplaceBackend(std::move(m_client_init), std::move(m_mining_proxy)); CreateSock = [this](int, int, int) -> std::unique_ptr { // This will be the bind/listen socket from m_tp. It will @@ -93,14 +94,10 @@ TPTester::~TPTester() mp::EventLoopRef loop_ref{*m_loop}; // Destroy objects that may post work to the loop while the loop is guaranteed alive. m_tp.reset(); - m_mining_proxy.reset(); - m_client_init.reset(); // Server init can go after clients; it only owns exported capabilities. m_server_init.reset(); } else { m_tp.reset(); - m_mining_proxy.reset(); - m_client_init.reset(); m_server_init.reset(); } // Join loop thread (loop exits automatically when refs & connections reach zero). From 451b1445c17d3e7e9b83e6ba1a301dce417cc563 Mon Sep 17 00:00:00 2001 From: Enoch Azariah Date: Fri, 8 May 2026 15:28:43 +0100 Subject: [PATCH 5/5] sv2-tp: improve reconnect and shutdown behavior Simplify the reconnect implementation now that disconnected proxy teardown is handled in the IPC layer. Remove the local teardown workarounds, restore ordinary backend ownership, and harden the remaining shutdown path so reconnect and operator shutdown both complete cleanly. --- src/sv2-tp.cpp | 7 +- src/sv2/template_provider.cpp | 134 +++++++++++++++++++++++----------- src/sv2/template_provider.h | 29 ++------ 3 files changed, 101 insertions(+), 69 deletions(-) diff --git a/src/sv2-tp.cpp b/src/sv2-tp.cpp index 2bfcf339..96f01b59 100644 --- a/src/sv2-tp.cpp +++ b/src/sv2-tp.cpp @@ -128,6 +128,7 @@ static bool g_interrupt{false}; namespace { constexpr auto IPC_RECONNECT_INITIAL_DELAY{std::chrono::seconds{1}}; constexpr auto IPC_RECONNECT_MAX_DELAY{std::chrono::seconds{32}}; +constexpr auto IPC_RECONNECT_POLL_INTERVAL{100ms}; } #ifndef WIN32 @@ -261,7 +262,11 @@ MAIN_FUNCTION LogPrintf("IPC connection failed: %s\n", exception.what()); LogPrintf("Retrying in %d seconds... (Ensure Bitcoin Core is running with '-ipcbind=unix')\n", retry_delay.count()); - std::this_thread::sleep_for(retry_delay); + auto waited{0ms}; + while (!g_interrupt && waited < retry_delay) { + std::this_thread::sleep_for(IPC_RECONNECT_POLL_INTERVAL); + waited += IPC_RECONNECT_POLL_INTERVAL; + } retry_delay = std::min(retry_delay * 2, IPC_RECONNECT_MAX_DELAY); } } diff --git a/src/sv2/template_provider.cpp b/src/sv2/template_provider.cpp index f73f633a..694e6657 100644 --- a/src/sv2/template_provider.cpp +++ b/src/sv2/template_provider.cpp @@ -17,27 +17,16 @@ #include #include -// Allow a few seconds for clients to submit a block or to request transactions -constexpr size_t STALE_TEMPLATE_GRACE_PERIOD{10}; - -template -std::shared_ptr MakeSharedIpcProxy(std::unique_ptr proxy, - const std::shared_ptr>& disconnected) -{ - return std::shared_ptr(proxy.release(), [disconnected](T* ptr) { - if (!ptr) return; - if (disconnected->load()) return; - delete ptr; - }); -} - Sv2TemplateProvider::BackendSession::BackendSession(std::unique_ptr init, std::unique_ptr mining) : - m_init(init.release(), IpcProxyDeleter{m_disconnected}), - m_mining(mining.release(), IpcProxyDeleter{m_disconnected}) + m_init(std::move(init)), + m_mining(std::move(mining)) { } +// Allow a few seconds for clients to submit a block or to request transactions +constexpr size_t STALE_TEMPLATE_GRACE_PERIOD{10}; + void Sv2TemplateProvider::ReplaceBackend(std::unique_ptr node_init, std::unique_ptr mining) { @@ -221,6 +210,10 @@ Sv2TemplateProvider::~Sv2TemplateProvider() { AssertLockNotHeld(m_tp_mutex); + m_connman->Interrupt(); + m_connman->StopThreads(); + + Interrupt(); { LOCK(m_backend_mutex); if (m_backend) { @@ -233,11 +226,6 @@ Sv2TemplateProvider::~Sv2TemplateProvider() ClearTemplateCache(); } m_backend_cv.notify_all(); - - m_connman->Interrupt(); - m_connman->StopThreads(); - - Interrupt(); StopThreads(); } @@ -410,8 +398,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) const auto time_start{SteadyClock::now()}; try { - block_template = MakeSharedIpcProxy(backend->Mining().createNewBlock(block_create_options), - backend->DisconnectState()); + block_template = backend->Mining().createNewBlock(block_create_options); } catch (const ipc::Exception& e) { DisconnectBackend(backend, "createNewBlock", e); backend.reset(); @@ -427,7 +414,13 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Assemble template: %.2fms\n", Ticks(SteadyClock::now() - time_start)); - prev_hash = block_template->getBlockHeader().hashPrevBlock; + try { + prev_hash = block_template->getBlockHeader().hashPrevBlock; + } catch (const ipc::Exception& e) { + DisconnectBackend(backend, "getBlockHeader", e); + backend.reset(); + continue; + } { LOCK(m_tp_mutex); if (prev_hash != m_best_prev_hash) { @@ -446,11 +439,17 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) std::shared_ptr client = m_connman->GetClientById(client_id); if (!client) break; - if (!SendWork(*client, template_id, *block_template, /*future_template=*/true)) { - LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Disconnecting client id=%zu\n", - client_id); - LOCK(client->cs_status); - client->m_disconnect_flag = true; + try { + if (!SendWork(*client, template_id, *block_template, /*future_template=*/true)) { + LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Disconnecting client id=%zu\n", + client_id); + LOCK(client->cs_status); + client->m_disconnect_flag = true; + } + } catch (const ipc::Exception& e) { + DisconnectBackend(backend, "SendWork", e); + backend.reset(); + continue; } } @@ -486,7 +485,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) std::shared_ptr tmpl; try { - tmpl = MakeSharedIpcProxy(block_template->waitNext(options), template_backend->DisconnectState()); + tmpl = block_template->waitNext(options); } catch (const ipc::Exception& e) { DisconnectBackend(template_backend, "template provider client loop", e); backend.reset(); @@ -505,7 +504,16 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) if (tmpl) { block_template = tmpl; template_backend = backend; - uint256 new_prev_hash{block_template->getBlockHeader().hashPrevBlock}; + uint256 new_prev_hash; + try { + new_prev_hash = block_template->getBlockHeader().hashPrevBlock; + } catch (const ipc::Exception& e) { + DisconnectBackend(template_backend, "getBlockHeader", e); + backend.reset(); + block_template.reset(); + template_backend.reset(); + continue; + } { LOCK(m_tp_mutex); @@ -530,11 +538,19 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id) std::shared_ptr client = m_connman->GetClientById(client_id); if (!client) break; - if (!SendWork(*client, WITH_LOCK(m_tp_mutex, return m_template_id;), *block_template, future_template)) { - LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Disconnecting client id=%zu\n", - client_id); - LOCK(client->cs_status); - client->m_disconnect_flag = true; + try { + if (!SendWork(*client, WITH_LOCK(m_tp_mutex, return m_template_id;), *block_template, future_template)) { + LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Disconnecting client id=%zu\n", + client_id); + LOCK(client->cs_status); + client->m_disconnect_flag = true; + } + } catch (const ipc::Exception& e) { + DisconnectBackend(template_backend, "SendWork", e); + backend.reset(); + block_template.reset(); + template_backend.reset(); + continue; } } @@ -569,7 +585,17 @@ void Sv2TemplateProvider::RequestTransactionData(Sv2Client& client, node::Sv2Req return; } - block = (*cached_block->second.second).getBlock(); + try { + block = (*cached_block->second.second).getBlock(); + } catch (const ipc::Exception& e) { + std::shared_ptr backend; + { + LOCK(m_backend_mutex); + backend = m_backend; + } + DisconnectBackend(backend, "getBlock", e); + return; + } auto recent = GetTime() - std::chrono::seconds(STALE_TEMPLATE_GRACE_PERIOD); if (block.hashPrevBlock != m_best_prev_hash && m_last_block_time < recent) { @@ -645,11 +671,22 @@ void Sv2TemplateProvider::SubmitSolution(node::Sv2SubmitSolutionMsg solution) } // Submit the solution to construct and process the block - const bool submitted = block_template->submitSolution( - solution.m_version, - solution.m_header_timestamp, - solution.m_header_nonce, - MakeTransactionRef(solution.m_coinbase_tx)); + bool submitted{false}; + try { + submitted = block_template->submitSolution( + solution.m_version, + solution.m_header_timestamp, + solution.m_header_nonce, + MakeTransactionRef(solution.m_coinbase_tx)); + } catch (const ipc::Exception& e) { + std::shared_ptr backend; + { + LOCK(m_backend_mutex); + backend = m_backend; + } + DisconnectBackend(backend, "submitSolution", e); + return; + } SaveBlockAsync(block_template, submitted); } @@ -682,6 +719,9 @@ void Sv2TemplateProvider::SaveBlockAsync(std::shared_ptr block_te "Wrote block %s to %s (submitted=%d)\n", block_hash.ToString(), fs::PathToString(out_path), submitted); } + } catch (const ipc::Exception& e) { + LogPrintLevel(BCLog::SV2, BCLog::Level::Error, + "sv2-saveblk thread caught IPC exception: %s\n", e.what()); } catch (const std::exception& e) { LogPrintLevel(BCLog::SV2, BCLog::Level::Error, "sv2-saveblk thread caught exception: %s\n", e.what()); @@ -708,8 +748,14 @@ void Sv2TemplateProvider::PruneBlockTemplateCache() bool Sv2TemplateProvider::SendWork(Sv2Client& client, uint64_t template_id, BlockTemplate& block_template, bool future_template) { - CBlockHeader header{block_template.getBlockHeader()}; - node::CoinbaseTx coinbase{block_template.getCoinbaseTx()}; + CBlockHeader header; + node::CoinbaseTx coinbase; + try { + header = block_template.getBlockHeader(); + coinbase = block_template.getCoinbaseTx(); + } catch (const ipc::Exception& e) { + throw; + } node::Sv2NewTemplateMsg new_template{header, coinbase, diff --git a/src/sv2/template_provider.h b/src/sv2/template_provider.h index e17ecd14..ed16e22f 100644 --- a/src/sv2/template_provider.h +++ b/src/sv2/template_provider.h @@ -59,30 +59,16 @@ class Sv2TemplateProvider : public Sv2EventsInterface * The active Bitcoin Core IPC backend generation. */ struct BackendSession { - template - struct IpcProxyDeleter { - std::shared_ptr> disconnected; - void operator()(T* ptr) const - { - if (!ptr) return; - if (disconnected->load()) return; - delete ptr; - } - }; - - using InitPtr = std::unique_ptr>; - using MiningPtr = std::unique_ptr>; - explicit BackendSession(std::unique_ptr init, std::unique_ptr mining); bool MarkDisconnected() { - return !m_disconnected->exchange(true); + return !m_disconnected.exchange(true); } bool Disconnected() const { - return m_disconnected->load(); + return m_disconnected.load(); } interfaces::Mining& Mining() @@ -90,15 +76,10 @@ class Sv2TemplateProvider : public Sv2EventsInterface return *m_mining; } - const std::shared_ptr>& DisconnectState() const - { - return m_disconnected; - } - private: - std::shared_ptr> m_disconnected{std::make_shared>(false)}; - InitPtr m_init; - MiningPtr m_mining; + std::atomic m_disconnected{false}; + std::unique_ptr m_init; + std::unique_ptr m_mining; }; /*