From d8e313eae9a6881a6872b2c254a826081e477c1c Mon Sep 17 00:00:00 2001 From: Cliff Burdick Date: Tue, 7 Jul 2026 18:51:32 -0700 Subject: [PATCH 1/2] #216 - Fix engine lifecycle cleanup Signed-off-by: Cliff Burdick --- docs/api-reference/cpp.md | 5 ++ docs/api-reference/python.md | 4 ++ include/daqiri/common.h | 4 +- src/common.cpp | 24 ++++++- src/engine.cpp | 64 ++++++++++++++++++- src/engine.h | 24 ++++++- src/engine_dpdk.cpp | 1 + src/engines/dpdk/daqiri_dpdk_engine.cpp | 7 +- src/engines/dpdk/daqiri_dpdk_engine.h | 2 +- src/engines/dpdk/daqiri_dpdk_stats.cpp | 1 + src/engines/ibverbs/daqiri_ibverbs_engine.cpp | 37 +++++++++-- src/engines/ibverbs/daqiri_ibverbs_engine.h | 3 + src/engines/rdma/daqiri_rdma_engine.cpp | 26 ++++++++ 13 files changed, 183 insertions(+), 19 deletions(-) diff --git a/docs/api-reference/cpp.md b/docs/api-reference/cpp.md index 92f1afa4..49f26cbe 100644 --- a/docs/api-reference/cpp.md +++ b/docs/api-reference/cpp.md @@ -33,6 +33,11 @@ auto status = daqiri::daqiri_init(config); After `daqiri_init()` returns `Status::SUCCESS`, all memory regions are allocated, NIC queues are configured, and worker threads are running. +Only one engine may be active in a process. Calling `daqiri_init()` again before +`shutdown()` returns `Status::INTERNAL_ERROR` and leaves the running engine unchanged. +After `shutdown()` completes, a later `daqiri_init()` creates a fresh engine instance and +allocates/registers its resources again. + If GPU RX `reorder_configs` are configured for Raw Ethernet (`stream_type: "raw"`), set one CUDA stream per GPU reorder plan before pulling reordered bursts. CPU reorder configs do not use a CUDA stream. See the [Configuration YAML Reference](configuration.md#rx-reorder-configs) diff --git a/docs/api-reference/python.md b/docs/api-reference/python.md index 38bf0509..74a4eb2d 100644 --- a/docs/api-reference/python.md +++ b/docs/api-reference/python.md @@ -85,6 +85,10 @@ finally: daqiri.shutdown() ``` +Only one engine may be active at a time. A second `daqiri_init()` before `shutdown()` +returns `Status.INTERNAL_ERROR` without changing the live engine. After `shutdown()`, a +later initialization starts with a fresh engine instance and fresh resources. + Initialize from a Python dictionary: ```python diff --git a/include/daqiri/common.h b/include/daqiri/common.h index 5b49699e..a893d20d 100644 --- a/include/daqiri/common.h +++ b/include/daqiri/common.h @@ -858,8 +858,8 @@ void set_header(BurstParams *burst, uint16_t port, uint16_t q, int64_t num, void format_eth_addr(char *dst, std::string addr); /** - * @brief Shut down the daqiri and do any cleanup necessary. Freeing memory is - * done in the engine's destructor. + * @brief Shut down DAQIRI and release the active engine and its resources. + * A subsequent daqiri_init() creates a fresh engine instance. * */ void shutdown(); diff --git a/src/common.cpp b/src/common.cpp index a7cdaf56..26ea3d92 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -47,6 +47,15 @@ static Engine* g_daqiri_engine = nullptr; namespace { +void reset_active_engine() { + Engine* engine = g_daqiri_engine; + g_daqiri_engine = nullptr; + if (engine != nullptr) { + engine->shutdown(); + } + EngineFactory::reset(); +} + constexpr size_t kEthAddrLength = 6; constexpr size_t kEthAddrOctetLength = 2; @@ -523,7 +532,7 @@ void* get_packet_ptr(BurstParams* burst, int idx) { void shutdown() { ASSERT_DAQIRI_ENGINE_INITIALIZED(); - g_daqiri_engine->shutdown(); + reset_active_engine(); metrics::shutdown(); } @@ -580,6 +589,11 @@ void print_stats() { } Status daqiri_init(NetworkConfig& config) { + if (g_daqiri_engine != nullptr) { + DAQIRI_LOG_ERROR("DAQIRI is already initialized; call shutdown() before daqiri_init()"); + return Status::INTERNAL_ERROR; + } + if (config.common_.engine_type == EngineType::UNKNOWN) { if (is_explicit_engine_type(config.common_.engine)) { config.common_.engine_type = config.common_.engine; @@ -653,13 +667,19 @@ Status daqiri_init(NetworkConfig& config) { auto engine = &(EngineFactory::get_active_engine()); - if (!engine->set_config_and_initialize(config)) { return Status::INTERNAL_ERROR; } + if (!engine->set_config_and_initialize(config)) { + reset_active_engine(); + metrics::shutdown(); + return Status::INTERNAL_ERROR; + } for (const auto& intf : config.ifs_) { const auto& rx = intf.rx_; auto port = engine->get_port_id(intf.address_); if (port < 0) { DAQIRI_LOG_ERROR("Failed to get port from name {}", intf.address_); + reset_active_engine(); + metrics::shutdown(); return Status::INVALID_PARAMETER; } } diff --git a/src/engine.cpp b/src/engine.cpp index d4f4b785..6fa58e22 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -157,6 +157,50 @@ bool validate_flow_action_config(const FlowAction& action, const std::string& fl } // namespace +Engine::~Engine() { + free_memory_regions(); +} + +void Engine::free_memory_regions() noexcept { + // DPDK's external-memory descriptors must be released before their backing + // allocations. EAL-backed HUGE allocations themselves are released by + // rte_eal_cleanup(), which runs in DpdkEngine before this base destructor. + ext_pktmbufs_.clear(); + + for (auto& [name, region] : ar_) { + (void)name; + if (region.ptr_ == nullptr) { + continue; + } + + switch (region.deallocator_) { + case AllocRegion::Deallocator::FREE: + std::free(region.ptr_); + break; + case AllocRegion::Deallocator::CUDA_HOST: + if (region.affinity_ >= 0) { + cudaSetDevice(region.affinity_); + } + cudaFreeHost(region.ptr_); + break; + case AllocRegion::Deallocator::CUDA_DEVICE: + if (region.affinity_ >= 0) { + cudaSetDevice(region.affinity_); + } + cuMemFree(reinterpret_cast(region.ptr_)); + break; + case AllocRegion::Deallocator::MUNMAP: + munmap(region.ptr_, region.size_); + break; + case AllocRegion::Deallocator::EAL: + case AllocRegion::Deallocator::NONE: + break; + } + region.ptr_ = nullptr; + } + ar_.clear(); +} + std::string Engine::generate_random_string(int len) { constexpr char tokens[] = "abcdefghijklmnopqrstuvwxyz"; if (len <= 0) { return {}; } @@ -329,16 +373,20 @@ static void bind_region_numa(void* p, size_t bytes, int numa) { (void)numa; } -void* Engine::alloc_huge(size_t bytes, int numa) { +void* Engine::alloc_huge(size_t bytes, int numa, AllocRegion::Deallocator* deallocator) { // Base (non-DPDK) implementation: try a real hugepage-backed mapping, falling // back to ordinary page-aligned host memory if MAP_HUGETLB is unavailable. // The DPDK engine overrides this to use rte_malloc_socket (EAL hugepages, // IOVA-contiguous for the NIC) -- see DpdkEngine::alloc_huge. + if (deallocator == nullptr) { + return nullptr; + } #if defined(MAP_HUGETLB) void* p = mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); if (p != MAP_FAILED) { bind_region_numa(p, bytes, numa); + *deallocator = AllocRegion::Deallocator::MUNMAP; return p; } DAQIRI_LOG_WARN( @@ -351,6 +399,7 @@ void* Engine::alloc_huge(size_t bytes, int numa) { return nullptr; } bind_region_numa(ptr, bytes, numa); + *deallocator = AllocRegion::Deallocator::FREE; return ptr; } @@ -359,7 +408,10 @@ Status Engine::allocate_memory_regions() { for (auto& mr : cfg_.mrs_) { void* ptr = nullptr; AllocRegion ar; + ar.mr_name_ = mr.second.name_; + ar.affinity_ = mr.second.affinity_; mr.second.ttl_size_ = align_ceil(mr.second.adj_size_ * mr.second.num_bufs_, GPU_PAGE_SIZE); + ar.size_ = mr.second.ttl_size_; if (mr.second.owned_) { switch (mr.second.kind_) { @@ -368,6 +420,7 @@ Status Engine::allocate_memory_regions() { DAQIRI_LOG_CRITICAL("Failed to allocate aligned host memory!"); return Status::NULL_PTR; } + ar.deallocator_ = AllocRegion::Deallocator::FREE; break; case MemoryKind::HOST_PINNED: cudaSetDevice(mr.second.affinity_); @@ -375,9 +428,10 @@ Status Engine::allocate_memory_regions() { DAQIRI_LOG_CRITICAL("Failed to allocate CUDA pinned host memory!"); return Status::NULL_PTR; } + ar.deallocator_ = AllocRegion::Deallocator::CUDA_HOST; break; case MemoryKind::HUGE: - ptr = alloc_huge(mr.second.ttl_size_, mr.second.affinity_); + ptr = alloc_huge(mr.second.ttl_size_, mr.second.affinity_, &ar.deallocator_); break; case MemoryKind::DEVICE: { unsigned int flag = 1; @@ -402,8 +456,10 @@ Status Engine::allocate_memory_regions() { cuPointerSetAttribute(&flag, CU_POINTER_ATTRIBUTE_SYNC_MEMOPS, cuptr); if (attr_res != CUDA_SUCCESS) { DAQIRI_LOG_CRITICAL("Could not set pointer attributes"); + cuMemFree(cuptr); return Status::NULL_PTR; } + ar.deallocator_ = AllocRegion::Deallocator::CUDA_DEVICE; break; } default: @@ -419,6 +475,8 @@ Status Engine::allocate_memory_regions() { } } + ar.ptr_ = ptr; + DAQIRI_LOG_INFO( "Successfully allocated memory region {} at {} type {} with {} bytes " "({} elements @ {} bytes total {})", @@ -429,7 +487,7 @@ Status Engine::allocate_memory_regions() { mr.second.num_bufs_, mr.second.adj_size_, mr.second.ttl_size_); - ar_[mr.second.name_] = {mr.second.name_, ptr}; + ar_[mr.second.name_] = ar; } DAQIRI_LOG_INFO("Finished allocating memory regions"); return Status::SUCCESS; diff --git a/src/engine.h b/src/engine.h index 585b9d52..b599a529 100644 --- a/src/engine.h +++ b/src/engine.h @@ -33,8 +33,20 @@ struct rte_pktmbuf_extmem; namespace daqiri { struct AllocRegion { + enum class Deallocator { + NONE, + FREE, + CUDA_HOST, + CUDA_DEVICE, + MUNMAP, + EAL, + }; + std::string mr_name_; - void* ptr_; + void* ptr_ = nullptr; + size_t size_ = 0; + int affinity_ = -1; + Deallocator deallocator_ = Deallocator::NONE; }; /** @@ -137,7 +149,7 @@ class Engine { struct rte_mempool* create_pktmbuf_pool(const std::string& name, const MemoryRegionConfig& mr); struct rte_mempool* create_generic_pool(const std::string& name, const MemoryRegionConfig& mr); - virtual ~Engine() = default; + virtual ~Engine(); protected: static constexpr int MAX_IFS = 4; @@ -177,7 +189,8 @@ class Engine { // a libdpdk-free implementation (MAP_HUGETLB with a regular-page fallback); // DpdkEngine overrides it with rte_malloc_socket so HUGE regions come from EAL // hugepages that are IOVA-contiguous for the NIC. - virtual void* alloc_huge(size_t bytes, int numa); + virtual void* alloc_huge(size_t bytes, int numa, AllocRegion::Deallocator* deallocator); + void free_memory_regions() noexcept; void init_rx_core_q_map(); static std::string generate_random_string(int len); size_t get_alignment(MemoryKind kind); @@ -256,6 +269,11 @@ class EngineFactory { return *EngineInstance_; } + static void reset() { + EngineInstance_.reset(); + EngineType_ = EngineType::UNKNOWN; + } + private: EngineFactory() = default; ~EngineFactory() = default; diff --git a/src/engine_dpdk.cpp b/src/engine_dpdk.cpp index f90feabd..2827db45 100644 --- a/src/engine_dpdk.cpp +++ b/src/engine_dpdk.cpp @@ -317,6 +317,7 @@ Status Engine::register_memory_regions() { #if RTE_VERSION >= RTE_VERSION_NUM(24, 11, 0, 0) ret = rte_extmem_register_dmabuf(ext_mem->buf_ptr, ext_mem->buf_len, dmabuf_fd, offset, NULL, 0, GPU_PAGE_SIZE); + close(dmabuf_fd); #else DAQIRI_LOG_WARN( "rte_extmem_register_dmabuf unavailable in DPDK {}; falling back to peermem " diff --git a/src/engines/dpdk/daqiri_dpdk_engine.cpp b/src/engines/dpdk/daqiri_dpdk_engine.cpp index 0c4e406e..db65bf5c 100644 --- a/src/engines/dpdk/daqiri_dpdk_engine.cpp +++ b/src/engines/dpdk/daqiri_dpdk_engine.cpp @@ -1721,6 +1721,7 @@ Status DpdkEngine::set_reorder_cuda_stream(const std::string& interface_name, //////////////////////////////////////////////////////////////////////////////// bool DpdkEngine::set_config_and_initialize(const NetworkConfig& cfg) { if (!this->initialized_) { + force_quit.store(false, std::memory_order_relaxed); cfg_ = cfg; if (!validate_config()) { @@ -1775,8 +1776,12 @@ Status DpdkEngine::get_mac_addr(int port, char* mac) { return Status::SUCCESS; } -void* DpdkEngine::alloc_huge(size_t bytes, int numa) { +void* DpdkEngine::alloc_huge(size_t bytes, int numa, AllocRegion::Deallocator* deallocator) { // EAL hugepage allocation: IOVA-contiguous and registrable with the NIC. + if (deallocator == nullptr) { + return nullptr; + } + *deallocator = AllocRegion::Deallocator::EAL; return rte_malloc_socket(nullptr, bytes, 0, numa); } diff --git a/src/engines/dpdk/daqiri_dpdk_engine.h b/src/engines/dpdk/daqiri_dpdk_engine.h index 639ed342..b88cb3e9 100644 --- a/src/engines/dpdk/daqiri_dpdk_engine.h +++ b/src/engines/dpdk/daqiri_dpdk_engine.h @@ -170,7 +170,7 @@ class DpdkEngine : public Engine { void print_stats() override; void adjust_memory_regions() override; // kind: HUGE regions come from EAL hugepages (IOVA-contiguous for the NIC). - void* alloc_huge(size_t bytes, int numa) override; + void* alloc_huge(size_t bytes, int numa, AllocRegion::Deallocator* deallocator) override; uint64_t get_burst_tot_byte(BurstParams* burst) override; BurstParams* create_tx_burst_params() override; bool validate_config() const override; diff --git a/src/engines/dpdk/daqiri_dpdk_stats.cpp b/src/engines/dpdk/daqiri_dpdk_stats.cpp index e9d4c67d..f12066ac 100644 --- a/src/engines/dpdk/daqiri_dpdk_stats.cpp +++ b/src/engines/dpdk/daqiri_dpdk_stats.cpp @@ -23,6 +23,7 @@ namespace daqiri { void DpdkStats::Init(const NetworkConfig &cfg) { + force_quit_.store(false, std::memory_order_relaxed); cfg_ = cfg; init_ = true; port_metrics_.clear(); diff --git a/src/engines/ibverbs/daqiri_ibverbs_engine.cpp b/src/engines/ibverbs/daqiri_ibverbs_engine.cpp index ac19815e..e719b4f8 100644 --- a/src/engines/ibverbs/daqiri_ibverbs_engine.cpp +++ b/src/engines/ibverbs/daqiri_ibverbs_engine.cpp @@ -330,6 +330,13 @@ IbverbsEngine::~IbverbsEngine() { // Bring-up // --------------------------------------------------------------------------- bool IbverbsEngine::set_config_and_initialize(const NetworkConfig& cfg) { + if (initialized_) { + DAQIRI_LOG_ERROR("ibverbs engine is already initialized; call shutdown() first"); + return false; + } + + force_quit_.store(false, std::memory_order_relaxed); + max_batch_ = 0; cfg_ = cfg; if (!reserve_static_flow_ids()) { return false; } initialize(); @@ -478,12 +485,14 @@ Status IbverbsEngine::register_mr(struct ibv_pd* pd, const std::string& mr_name, return Status::GENERIC_FAILURE; } struct ibv_mr* gmr = ibv_reg_dmabuf_mr(pd, offset, mr.ttl_size_, va, dmabuf_fd, access); + const int reg_errno = errno; + close(dmabuf_fd); if (gmr == nullptr) { DAQIRI_LOG_CRITICAL("ibv_reg_dmabuf_mr failed for MR {} ({} bytes): {}", mr_name, - mr.ttl_size_, strerror(errno)); - close(dmabuf_fd); + mr.ttl_size_, strerror(reg_errno)); return Status::NULL_PTR; } + registered_mrs_.push_back(gmr); *out_base = static_cast(base); *out_lkey = gmr->lkey; DAQIRI_LOG_INFO("Registered GPU MR {} (dmabuf) base {} size {} lkey {}", mr_name, base, @@ -496,6 +505,7 @@ Status IbverbsEngine::register_mr(struct ibv_pd* pd, const std::string& mr_name, DAQIRI_LOG_CRITICAL("ibv_reg_mr failed for {} ({} bytes)", mr_name, mr.ttl_size_); return Status::NULL_PTR; } + registered_mrs_.push_back(ib_mr); *out_base = static_cast(base); *out_lkey = ib_mr->lkey; DAQIRI_LOG_INFO("Registered MR {} base {} size {} lkey {}", mr_name, base, mr.ttl_size_, @@ -3580,7 +3590,7 @@ void IbverbsEngine::print_stats() { } void IbverbsEngine::shutdown() { - force_quit_ = true; + force_quit_.store(true, std::memory_order_relaxed); for (auto& q : rx_queues_) { q->running = false; if (q->worker.joinable()) { @@ -3698,15 +3708,24 @@ void IbverbsEngine::shutdown() { } } tx_queues_.clear(); + + for (auto it = registered_mrs_.rbegin(); it != registered_mrs_.rend(); ++it) { + if (*it != nullptr && ibv_dereg_mr(*it) != 0) { + DAQIRI_LOG_ERROR("ibv_dereg_mr failed during ibverbs shutdown: {}", strerror(errno)); + } + } + registered_mrs_.clear(); + for (auto& pd : pd_map_) { - if (pd.second) { - ibv_dealloc_pd(pd.second); + if (pd.second && ibv_dealloc_pd(pd.second) != 0) { + DAQIRI_LOG_ERROR("ibv_dealloc_pd failed during ibverbs shutdown: {}", strerror(errno)); } } pd_map_.clear(); + clock_cache_.clear(); for (auto& c : ctx_map_) { - if (c.second) { - ibv_close_device(c.second); + if (c.second && ibv_close_device(c.second) != 0) { + DAQIRI_LOG_ERROR("ibv_close_device failed during ibverbs shutdown: {}", strerror(errno)); } } ctx_map_.clear(); @@ -3718,6 +3737,10 @@ void IbverbsEngine::shutdown() { daqiri::ObjectPool::free(tx_meta_pool_); tx_meta_pool_ = nullptr; } + initialized_ = false; + force_quit_.store(false, std::memory_order_relaxed); + max_batch_ = 0; + rx_meta_pool_size_ = 0; } // --------------------------------------------------------------------------- diff --git a/src/engines/ibverbs/daqiri_ibverbs_engine.h b/src/engines/ibverbs/daqiri_ibverbs_engine.h index e3d2ec2c..a030ba42 100644 --- a/src/engines/ibverbs/daqiri_ibverbs_engine.h +++ b/src/engines/ibverbs/daqiri_ibverbs_engine.h @@ -509,6 +509,9 @@ class IbverbsEngine : public Engine { // One ibv_context + PD per opened device, keyed by device name. std::unordered_map ctx_map_; std::unordered_map pd_map_; + // Registrations may be shared by queue setup only through their backing + // allocation, so retain every verbs object and deregister it before its PD. + std::vector registered_mrs_; // Cached mlx5 clock-info per device for converting the CQE's free-running HW // timestamp to nanoseconds (mlx5dv_ts_to_ns). Refreshed lazily (the HW clock diff --git a/src/engines/rdma/daqiri_rdma_engine.cpp b/src/engines/rdma/daqiri_rdma_engine.cpp index e9d76743..e664f72c 100644 --- a/src/engines/rdma/daqiri_rdma_engine.cpp +++ b/src/engines/rdma/daqiri_rdma_engine.cpp @@ -91,7 +91,13 @@ std::shared_ptr get_rdma_metrics_for_connection( } // namespace bool RdmaEngine::set_config_and_initialize(const NetworkConfig& cfg) { + if (initialized_) { + DAQIRI_LOG_ERROR("RDMA engine is already initialized; call shutdown() first"); + return false; + } + DAQIRI_LOG_INFO("Setting up RDMA engine"); + rdma_force_quit.store(false, std::memory_order_relaxed); cfg_ = cfg; initialize(); @@ -260,6 +266,12 @@ int RdmaEngine::rdma_register_mr(const MemoryRegionConfig& mr, void* ptr) { #endif if (params.ctx_mr_map_[pd.second] == nullptr) { DAQIRI_LOG_CRITICAL("Failed to register MR {} on PD {}", mr.name_, (void*)pd.second); + for (auto& [registered_pd, registered_mr] : params.ctx_mr_map_) { + (void)registered_pd; + if (registered_mr != nullptr) { + ibv_dereg_mr(registered_mr); + } + } return -1; } else { DAQIRI_LOG_INFO( @@ -1922,6 +1934,20 @@ RdmaEngine::~RdmaEngine() { cm_event_channel_ = nullptr; } + // Registrations hold references to their PDs and backing allocations. Drop + // them before deallocating PDs; Engine's base destructor frees the buffers. + for (auto& [name, params] : mrs_) { + (void)name; + for (auto& [pd, mr] : params.ctx_mr_map_) { + (void)pd; + if (mr != nullptr) { + ibv_dereg_mr(mr); + } + } + params.ctx_mr_map_.clear(); + } + mrs_.clear(); + // Deallocate PDs for (auto& entry : pd_map_) { if (entry.second != nullptr) { From f090a8760d73cca7f1ba1414b17fdbfbeea883f3 Mon Sep 17 00:00:00 2001 From: Cliff Burdick Date: Tue, 7 Jul 2026 19:00:38 -0700 Subject: [PATCH 2/2] #216 - Retain DPDK dma-buf descriptors Signed-off-by: Cliff Burdick --- src/engine.cpp | 8 ++++++++ src/engine.h | 3 +++ src/engine_dpdk.cpp | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/engine.cpp b/src/engine.cpp index 6fa58e22..e4345b14 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -166,6 +166,14 @@ void Engine::free_memory_regions() noexcept { // allocations. EAL-backed HUGE allocations themselves are released by // rte_eal_cleanup(), which runs in DpdkEngine before this base destructor. ext_pktmbufs_.clear(); + // Safety net for partial teardown. Normal DPDK shutdown closes these after + // rte_eal_cleanup(), then clears the vector before reaching this destructor. + for (const int fd : ext_dmabuf_fds_) { + if (fd >= 0) { + close(fd); + } + } + ext_dmabuf_fds_.clear(); for (auto& [name, region] : ar_) { (void)name; diff --git a/src/engine.h b/src/engine.h index b599a529..6bcc7080 100644 --- a/src/engine.h +++ b/src/engine.h @@ -171,6 +171,9 @@ class Engine { // shared_ptr to an incomplete type -- only populated by the DPDK engine // (engine_dpdk.cpp). Layout is identical in every build. std::unordered_map> ext_pktmbufs_; + // rte_extmem_register_dmabuf() retains the caller's numeric fd for deferred + // driver mapping. Keep successful exports open until EAL teardown completes. + std::vector ext_dmabuf_fds_; std::unordered_map>> rx_core_q_map; // EAL lifecycle state, used only by the DPDK engine. Mirrors the diff --git a/src/engine_dpdk.cpp b/src/engine_dpdk.cpp index 2827db45..3196c555 100644 --- a/src/engine_dpdk.cpp +++ b/src/engine_dpdk.cpp @@ -194,6 +194,16 @@ void Engine::cleanup_eal() { // behind, so we also do a best-effort unlink targeted at our --file-prefix. rte_eal_cleanup(); + // The patched rte_extmem_register_dmabuf() stores caller-provided fd values + // for deferred driver DMA mapping; it does not duplicate them. They must + // remain valid through EAL teardown and can be closed only afterward. + for (const int fd : ext_dmabuf_fds_) { + if (fd >= 0) { + close(fd); + } + } + ext_dmabuf_fds_.clear(); + if (!eal_file_prefix_.empty()) { static const char* kHugepageMounts[] = {"/dev/hugepages", "/mnt/huge"}; for (const char* mount : kHugepageMounts) { @@ -317,7 +327,11 @@ Status Engine::register_memory_regions() { #if RTE_VERSION >= RTE_VERSION_NUM(24, 11, 0, 0) ret = rte_extmem_register_dmabuf(ext_mem->buf_ptr, ext_mem->buf_len, dmabuf_fd, offset, NULL, 0, GPU_PAGE_SIZE); - close(dmabuf_fd); + if (ret == 0) { + ext_dmabuf_fds_.push_back(dmabuf_fd); + } else { + close(dmabuf_fd); + } #else DAQIRI_LOG_WARN( "rte_extmem_register_dmabuf unavailable in DPDK {}; falling back to peermem "