From 9f62fe280de01680dd7dd3b5bf8ea25a2e8513c5 Mon Sep 17 00:00:00 2001 From: Juanca-Lobolanja Date: Tue, 30 Jun 2026 10:24:29 -0700 Subject: [PATCH 1/3] #207 - Fix TX mempool exhaustion via descriptor_status polling Poll NIC TX completions via rte_eth_tx_descriptor_status() in the tx_core_worker idle path (empty ring). This triggers mlx5_tx_handle_completion() as a side effect, reclaiming mbufs so is_tx_burst_available() unblocks. Signed-off-by: Juanca-Lobolanja --- src/engines/dpdk/daqiri_dpdk_engine.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/engines/dpdk/daqiri_dpdk_engine.cpp b/src/engines/dpdk/daqiri_dpdk_engine.cpp index 1d1bd7b..5c5e982 100644 --- a/src/engines/dpdk/daqiri_dpdk_engine.cpp +++ b/src/engines/dpdk/daqiri_dpdk_engine.cpp @@ -6110,7 +6110,16 @@ int DpdkEngine::tx_core_worker(void* arg) { (void*)tparams->ring); while (!force_quit.load()) { - if (rte_ring_dequeue(tparams->ring, reinterpret_cast(&msg)) != 0) { continue; } + if (rte_ring_dequeue(tparams->ring, reinterpret_cast(&msg)) != 0) { + // Poll TX completions while idle so the PMD reclaims mbufs back to the + // mempool. Without this, is_tx_burst_available() can permanently gate + // new submissions once the pool drops below the 2×batch threshold, + // because rte_eth_tx_burst() — the only other reclaim path — is never + // called when the ring is empty. On mlx5 this dispatches to + // mlx5_tx_descriptor_status() → mlx5_tx_handle_completion(). + (void)rte_eth_tx_descriptor_status(tparams->port, tparams->queue, 0); + continue; + } // Scatter mode needs to chain all the buffers if (msg->hdr.hdr.num_segs > 1) { From 19f53293c784328179c5367cf35004f01f25f3f3 Mon Sep 17 00:00:00 2001 From: Juanca-Lobolanja Date: Tue, 30 Jun 2026 10:27:39 -0700 Subject: [PATCH 2/3] #207 - Add mlx5 tx_done_cleanup DPDK patch Add mlx5_tx_done_cleanup() to the mlx5 PMD (DPDK 25.11) as a thin wrapper around mlx5_tx_handle_completion(). Registers the callback in mlx5_dev_ops and mlx5_dev_ops_isolate so rte_eth_tx_done_cleanup() no longer returns -ENOTSUP. Signed-off-by: Juanca-Lobolanja --- dpdk_patches/mlx5_tx_done_cleanup.patch | 59 +++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 dpdk_patches/mlx5_tx_done_cleanup.patch diff --git a/dpdk_patches/mlx5_tx_done_cleanup.patch b/dpdk_patches/mlx5_tx_done_cleanup.patch new file mode 100644 index 0000000..bb7bfe4 --- /dev/null +++ b/dpdk_patches/mlx5_tx_done_cleanup.patch @@ -0,0 +1,59 @@ +diff --git a/drivers/net/mlx5/mlx5_tx.h b/drivers/net/mlx5/mlx5_tx.h +--- a/drivers/net/mlx5/mlx5_tx.h ++++ b/drivers/net/mlx5/mlx5_tx.h +@@ -237,6 +237,7 @@ + void mlx5_tx_handle_completion(struct mlx5_txq_data *__rte_restrict txq, + unsigned int olx __rte_unused); + int mlx5_tx_descriptor_status(void *tx_queue, uint16_t offset); ++int mlx5_tx_done_cleanup(void *tx_queue, uint32_t free_cnt); + void mlx5_txq_info_get(struct rte_eth_dev *dev, uint16_t queue_id, + struct rte_eth_txq_info *qinfo); + int mlx5_tx_burst_mode_get(struct rte_eth_dev *dev, uint16_t tx_queue_id, +diff --git a/drivers/net/mlx5/mlx5_tx.c b/drivers/net/mlx5/mlx5_tx.c +--- a/drivers/net/mlx5/mlx5_tx.c ++++ b/drivers/net/mlx5/mlx5_tx.c +@@ -288,5 +288,27 @@ mlx5_tx_descriptor_status(void *tx_queue, uint16_t offset) + } + ++/** ++ * DPDK callback to free consumed TX mbufs. ++ * ++ * @param tx_queue ++ * The TX queue. ++ * @param[in] free_cnt ++ * Maximum number of packets to free. Use 0 to indicate all possible packets ++ * should be freed. Note that a packet may be using multiple mbufs. ++ * ++ * @return ++ * Number of packets freed, or negative errno on error. ++ */ ++int ++mlx5_tx_done_cleanup(void *tx_queue, uint32_t free_cnt __rte_unused) ++{ ++ struct mlx5_txq_data *__rte_restrict txq = tx_queue; ++ uint16_t tail_before = txq->elts_tail; ++ ++ mlx5_tx_handle_completion(txq, 0); ++ return (uint16_t)(txq->elts_tail - tail_before); ++} ++ + /* + * Array of declared and compiled Tx burst function and corresponding + * supported offloads set. The array is used to select the Tx burst +diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c +--- a/drivers/net/mlx5/mlx5.c ++++ b/drivers/net/mlx5/mlx5.c +@@ -2585,5 +2585,6 @@ const struct eth_dev_ops mlx5_dev_ops = { + .rxq_info_get = mlx5_rxq_info_get, + .txq_info_get = mlx5_txq_info_get, ++ .tx_done_cleanup = mlx5_tx_done_cleanup, + .rx_burst_mode_get = mlx5_rx_burst_mode_get, + .tx_burst_mode_get = mlx5_tx_burst_mode_get, + .rx_queue_intr_enable = mlx5_rx_intr_enable, +@@ -2680,5 +2681,6 @@ const struct eth_dev_ops mlx5_dev_ops_isolate = { + .rxq_info_get = mlx5_rxq_info_get, + .txq_info_get = mlx5_txq_info_get, ++ .tx_done_cleanup = mlx5_tx_done_cleanup, + .rx_burst_mode_get = mlx5_rx_burst_mode_get, + .tx_burst_mode_get = mlx5_tx_burst_mode_get, + .rx_queue_intr_enable = mlx5_rx_intr_enable, From a7354dc0f36e9976e3fc776e6c6bd8b2164981f0 Mon Sep 17 00:00:00 2001 From: Juanca-Lobolanja Date: Tue, 30 Jun 2026 10:35:43 -0700 Subject: [PATCH 3/3] #207 - Use rte_eth_tx_done_cleanup for idle-path completion Replace the rte_eth_tx_descriptor_status() side-effect workaround with the proper rte_eth_tx_done_cleanup() API, now that the mlx5 patch provides the callback. Signed-off-by: Juanca-Lobolanja --- src/engines/dpdk/daqiri_dpdk_engine.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engines/dpdk/daqiri_dpdk_engine.cpp b/src/engines/dpdk/daqiri_dpdk_engine.cpp index 5c5e982..036b330 100644 --- a/src/engines/dpdk/daqiri_dpdk_engine.cpp +++ b/src/engines/dpdk/daqiri_dpdk_engine.cpp @@ -6115,9 +6115,8 @@ int DpdkEngine::tx_core_worker(void* arg) { // mempool. Without this, is_tx_burst_available() can permanently gate // new submissions once the pool drops below the 2×batch threshold, // because rte_eth_tx_burst() — the only other reclaim path — is never - // called when the ring is empty. On mlx5 this dispatches to - // mlx5_tx_descriptor_status() → mlx5_tx_handle_completion(). - (void)rte_eth_tx_descriptor_status(tparams->port, tparams->queue, 0); + // called when the ring is empty. + (void)rte_eth_tx_done_cleanup(tparams->port, tparams->queue, 0); continue; }