|WARNING| [v2] eal: fix core_index for non-EAL registered threads

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Wed Jun 17 21:20:49 CEST 2026


Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/165612

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-06-17

# DPDK Patch Review

## Summary
This patch fixes a correctness bug where non-EAL threads registered via `rte_thread_register()` have their `core_index` left at -1, causing libraries that use `rte_lcore_index()` for per-lcore caching to fall back to a shared path with severe contention.

## Errors

### 1. Bitset declared as first member of `struct rte_config` causes ABI break
**Location:** `lib/eal/common/eal_private.h:48-49`

```c
struct rte_config {
	RTE_BITSET_DECLARE(core_indices,
			   RTE_MAX_LCORE); /**< bitset of currently allocated core_indices */
	uint32_t main_lcore;
```

Adding a new member at the **beginning** of `struct rte_config` shifts the offset of all existing fields (`main_lcore`, `lcore_count`, `numa_node_count`, etc.), breaking the ABI. Any code compiled against the old layout will access wrong offsets.

**Fix:** Add the new field at the **end** of the structure to preserve existing member offsets:

```c
struct rte_config {
	uint32_t main_lcore;         /**< Id of the main lcore */
	uint32_t lcore_count;        /**< Number of available logical cores. */
	uint32_t numa_node_count;    /**< Number of detected NUMA nodes. */
	/* ... existing fields ... */
	RTE_BITSET_DECLARE(core_indices,
			   RTE_MAX_LCORE); /**< bitset of currently allocated core_indices */
};
```

### 2. Resource leak on early error return in `eal_lcore_non_eal_allocate`
**Location:** `lib/eal/common/eal_common_lcore.c:383-387`

```c
	core_index = rte_bitset_find_first_clear(cfg->core_indices, RTE_MAX_LCORE);
	if (core_index == -1) {
		EAL_LOG(DEBUG, "No core_index available.");
		lcore_id = RTE_MAX_LCORE;
		goto out;
	}
```

The `rte_rwlock_write_lock(&lcore_lock)` at line 382 is never released if this error path is taken. The lock remains held, causing deadlock on the next call to any function that acquires `lcore_lock`.

**Fix:** Release the lock before returning:

```c
	core_index = rte_bitset_find_first_clear(cfg->core_indices, RTE_MAX_LCORE);
	if (core_index == -1) {
		EAL_LOG(DEBUG, "No core_index available.");
		lcore_id = RTE_MAX_LCORE;
		rte_rwlock_write_unlock(&lcore_lock);
		goto out;
	}
```

Alternatively, move the `rte_bitset_find_first_clear()` call before acquiring the lock if it does not require lock protection.

## Warnings

### 1. Missing explicit comparison in conditional
**Location:** `lib/eal/common/eal_common_lcore.c:383`

```c
	if (core_index == -1) {
```

This is correct per DPDK style (explicit comparison against -1). No issue.

### 2. Bitset initialization on first discovery
**Location:** `lib/eal/common/eal_common_lcore.c:188`

```c
	/* This is the first time we discover the lcores, so the bitset should be zeroed */
	rte_bitset_set(config->core_indices, count);
```

The comment states the bitset "should be zeroed," implying an assumption that `rte_config` is zero-initialized before `rte_eal_cpu_init()` runs. Verify that `rte_config` (whether static or dynamically allocated) is guaranteed to be zeroed at the point this function is called. If not, add an explicit `rte_bitset_clear_all()` before the loop.

### 3. Release notes missing
The patch fixes a significant performance regression (5x throughput drop) in a scenario where non-EAL threads use per-lcore caching. This is a **bug fix** that restores expected performance and should be documented in the release notes under the "Fixed Issues" section for the EAL subsystem.

**Suggested entry:**
```
* **eal: Fixed per-lcore cache contention for registered non-EAL threads**

  Non-EAL threads registered via ``rte_thread_register()`` now receive a valid
  ``core_index``, allowing libraries that use ``rte_lcore_index()`` for per-lcore
  caching (e.g., mlx5 indexed memory pool) to avoid fallback to a shared global
  path. This eliminates severe spinlock contention that caused up to 5x throughput
  regression under concurrent access from multiple registered threads.
```

### 4. Functional test or app/testpmd hook recommended
While not strictly required for an internal bug fix, consider adding a test case in `app/test` that:
- Registers multiple non-EAL threads via `rte_thread_register()`
- Verifies each gets a unique `rte_lcore_index()` >= 0
- Verifies indices are reused after `rte_thread_unregister()`

This would prevent regression and validate the fix.

## Info

### Code style observations (no action required)

1. Include order change in `eal_common_lcore.c` (adding `rte_bitset.h` before `rte_branch_prediction.h`) is acceptable per DPDK style (alphabetical within the DPDK misc library group).

2. The logic correctly handles:
   - Initial EAL lcore discovery setting the bitset
   - Lcore remapping on `-l`/`-c` options clearing and repopulating the bitset
   - Non-EAL allocation setting the bit and rollback clearing it on callback failure
   - Non-EAL release clearing the bit

3. Bitset size `RTE_MAX_LCORE` is appropriate -- matches the maximum possible `core_index` values.

---

## Summary of Required Changes

**Must fix (Errors):**
1. Move `core_indices` bitset to the end of `struct rte_config` to avoid ABI break
2. Release `lcore_lock` on the error path where no `core_index` is available

**Should fix (Warnings):**
3. Add release notes entry documenting the performance fix
4. Verify or ensure `rte_config` is zero-initialized before `rte_eal_cpu_init()`, or add explicit `rte_bitset_clear_all()` at function entry

**Consider (Info):**
5. Add a functional test for non-EAL thread `core_index` allocation/reuse


More information about the test-report mailing list