diff --git a/src/managers/dpdk/daqiri_dpdk_mgr.cpp b/src/managers/dpdk/daqiri_dpdk_mgr.cpp index de695dc..fc6151a 100644 --- a/src/managers/dpdk/daqiri_dpdk_mgr.cpp +++ b/src/managers/dpdk/daqiri_dpdk_mgr.cpp @@ -4403,6 +4403,11 @@ Status DpdkMgr::send_tx_burst(BurstParams* burst) { } void DpdkMgr::shutdown() { + // Idempotency guard: shutdown() may be invoked a second time via ~DpdkMgr + // during C++ __cxa_finalize, by which point the spdlog default logger has + // already been destroyed and any DAQIRI_LOG_INFO here crashes inside + // spdlog::sink_it_. Skip the body (and the log calls) if already torn down. + if (!initialized_) { return; } DAQIRI_LOG_INFO("daqiri DPDK manager shutdown called {}", num_init); if (--num_init == 0) { diff --git a/src/managers/rdma/daqiri_rdma_mgr.cpp b/src/managers/rdma/daqiri_rdma_mgr.cpp index df88152..e525eeb 100644 --- a/src/managers/rdma/daqiri_rdma_mgr.cpp +++ b/src/managers/rdma/daqiri_rdma_mgr.cpp @@ -1508,6 +1508,12 @@ void RdmaMgr::initialize() { } void RdmaMgr::shutdown() { + // Idempotency guard: shutdown() runs explicitly from the caller AND again + // from ~RdmaMgr / ~SocketMgr during C++ __cxa_finalize. By the second call + // the spdlog default logger (a function-local static created lazily on the + // first DAQIRI_LOG_INFO) has already been destroyed, so any logging here + // crashes inside spdlog::sink_it_. Skip the whole body on subsequent calls. + if (!initialized_) { return; } DAQIRI_LOG_INFO("RDMA manager shutting down"); rdma_force_quit.store(true); diff --git a/src/managers/socket/daqiri_socket_mgr.cpp b/src/managers/socket/daqiri_socket_mgr.cpp index 3ccec89..9f16a66 100644 --- a/src/managers/socket/daqiri_socket_mgr.cpp +++ b/src/managers/socket/daqiri_socket_mgr.cpp @@ -466,6 +466,21 @@ void SocketMgr::clear_rx_queues() { } void SocketMgr::shutdown() { + // Idempotency guard: shutdown() may be invoked a second time via + // ~SocketMgr during C++ __cxa_finalize, after the spdlog default logger + // has been destroyed. Any DAQIRI_LOG_INFO from the cascade (here, the + // RoCE branch, or the manager method this delegates to) would then crash + // inside spdlog::sink_it_. Skip the whole body on subsequent calls. + // + // The guard checks BOTH flags because initialize() sets initialized_=false + // and running_=true before running setup, then sets initialized_=true on + // success. If setup throws, the catch block calls shutdown() with + // initialized_=false and running_=true to clean up any threads/sockets + // that were spawned partway. Guarding on initialized_ alone would skip + // that cleanup. Both flags are only cleared together after a successful + // shutdown() body, so the post-shutdown re-entry from __cxa_finalize is + // the only case where the guard fires. + if (!initialized_ && !running_.load()) { return; } if (is_roce_protocol()) { #if DAQIRI_MGR_RDMA if (roce_mgr_ != nullptr) { @@ -476,8 +491,6 @@ void SocketMgr::shutdown() { return; } - if (!initialized_ && !running_.load()) { return; } - running_.store(false); for (auto& ep : endpoints_) {