|SUCCESS| [v2,12/12] interrupts: warn on leaked event FDs

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Sat Oct 24 16:09:00 CEST 2026


Test-Label: ai-code-review
Test-Status: SUCCESS
https://dpdk.org/patch/172050

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-10-24

# DPDK Patch Series Review

## Patch 1/12: eal/linux: fix FD leak in alarm subsystem

**Analysis:**
The patch adds `rte_intr_fd_close(intr_handle)` before `rte_intr_instance_free(intr_handle)` in the alarm cleanup path. The timerfd is created during alarm initialization and registered with the interrupt subsystem, so closing it on cleanup is correct.

**Findings:** None.

---

## Patch 2/12: eal/linux: reset FD in interrupt handle for uevent

**Analysis:**
The patch replaces `close(rte_intr_fd_get(intr_handle))` with `rte_intr_fd_close(intr_handle)` in two places. `rte_intr_fd_close()` both closes the fd and resets the handle's fd field to -1, preventing the spurious leak warning when the handle is later freed.

**Findings:** None.

---

## Patch 3/12: interrupts: do not duplicate interrupt handles

**Analysis:**
This is a significant refactor of the interrupt subsystem's handle management:

1. **Removal of `rte_intr_instance_dup()`:** The patch removes the duplication of interrupt handles in the interrupt subsystem. Previously, `rte_intr_callback_register()` cloned the caller's handle; now it stores a const pointer to the caller's handle directly.

2. **Change to pointer-based matching:** The interrupt source lookup switches from FD-based matching (`rte_intr_fd_get(src->intr_handle) == rte_intr_fd_get(intr_handle)`) to pointer-based matching (`src->intr_handle == intr_handle`). The commit message explains that the old FD-based matching was problematic when multiple handles shared the same FD.

3. **API change to unregister callback signature:** The `rte_intr_unregister_callback_fn` signature changes to take `const struct rte_intr_handle *` instead of `struct rte_intr_handle *`, documented in the release notes.

4. **Update to one caller:** The memif driver's `memif_intr_unregister_handler()` is updated to match the new signature, adding `RTE_SET_USED(intr_handle)` and using `cc->intr_handle` instead of the parameter.

**Correctness check:**

The evidence rule requires checking whether code you have not read supports the claim that "DPDK components use one intr_handle per FD". The patch context shows the changes to FreeBSD and Linux interrupt code, and the memif driver update. The claim about existing usage is stated in the commit message but not proven by code visible in the patch. However, this is design rationale, not a finding about the code being changed.

The actual code changes are:
- Removal of `rte_intr_instance_dup()` and its call sites
- Change from FD-based to pointer-based lookup
- Removal of `rte_intr_instance_free(src->intr_handle)` calls in unregister paths, since the handle is now owned by the caller
- API signature change to const pointer

These changes are internally consistent. The memif driver is updated to call `rte_intr_fd_close(cc->intr_handle)` on the handle it owns, rather than on the callback parameter.

**Potential issue:**

In `drivers/net/memif/memif_socket.c`, line 581 (new code):
```c
rte_intr_fd_close(cc->intr_handle);
```

This is in `memif_intr_unregister_handler()`, which is an unregister callback. The callback is invoked while the interrupt subsystem holds a pointer to `cc->intr_handle`. After the callback returns, if the interrupt source still exists and callbacks remain registered, the source continues to reference the same handle pointer. Closing the fd in the handle at this point is correct *only if* this is the last callback for this source, or if the caller guarantees the handle will not be used again.

Looking at the wider context: this callback is unregistering itself, and `cc` is the control channel structure. If there are no other callbacks for this interrupt source, the source will be freed immediately after the callback returns (per the interrupt subsystem logic visible in patch 3's context). If there are other callbacks, they presumably also own their own handles, since each callback has its own `cb_arg`.

However, the AGENTS.md evidence rule requires that any claim about behavior outside the visible code must be verified by reading it. The claim "other callbacks have their own handles" rests on code not shown in the patch. Without reading the memif driver's registration path, I cannot confirm that multiple callbacks never share a handle.

But: the change to `rte_intr_fd_close(cc->intr_handle)` is *replacing* `rte_intr_fd_close(intr_handle)` (the parameter). Before patch 3, `intr_handle` was a clone created by the interrupt subsystem, and closing its fd had no effect on other callbacks' clones. After patch 3, `intr_handle` is the caller's original handle. The change to `cc->intr_handle` is correct because `cc->intr_handle` *is* the same pointer the interrupt subsystem now holds (`intr_handle == cc->intr_handle` after the API change).

So the memif change is correct. If closing the fd at this point is unsafe because other callbacks reference it, that would have been a pre-existing bug (closing the fd in the clone affected the original), and patch 3 does not introduce it.

**Findings:** None.

---

## Patch 4/12: bus/pci: reset VFIO device FD after release

**Analysis:**
The patch calls `rte_intr_dev_fd_set(dev->intr_handle, -1)` after `rte_vfio_release_device()`, which closes the VFIO device fd. It also removes the set of `dev_fd` for `dev->vfio_req_intr_handle` in the secondary process, with a comment explaining the request handle is unused.

**Findings:** None.

---

## Patch 5/12: net/mana: reset FD in interrupt handle

**Analysis:**
The patch adds comments and calls to `rte_intr_fd_set(priv->intr_handle, -1)` before freeing the interrupt handle in two places. The comments state "fd is owned by ibverbs, only clear reference here." The fd is the ibverbs `async_fd`, confirmed by context showing `rte_intr_fd_set(intr_handle, ib_ctx->async_fd)` during initialization (not shown in patch but implied by the comment).

**Findings:** None.

---

## Patch 6/12: net/memif: fix some interrupt FD leaks

**Analysis:**
The patch fixes three resource leak issues in the memif driver:

1. **In `memif_socket_create()`:** When `rte_intr_callback_register()` fails, the code previously returned directly, leaking `sockfd`, `sock`, and `intr_handle`. The patch changes this to `goto error`, which cleans up these resources.

   **Check:** The `error:` label closes `sockfd` (if >= 0), frees `intr_handle`, and frees `sock`. This cleanup is correct for the failure case.

2. **In `memif_socket_init()`:** When `rte_hash_add_key_data()` fails after creating a listener socket, the newly created socket (including its callback, fd, and interrupt handle) was leaked. The patch adds cleanup code unregistering the callback, closing the fd, freeing the interrupt handle, and freeing the socket.

   **Check:** The cleanup matches the initialization: `rte_intr_callback_register()` was called, so unregister is needed; `sockfd` was set and passed to the interrupt handle, so close is needed; `intr_handle` was allocated, so free is needed; `socket` was allocated, so free is needed.

3. **In `memif_socket_remove_device()`:** For listener sockets (`pmd->role != MEMIF_ROLE_CLIENT`), the callback was not unregistered and the socket fd was not closed before freeing the interrupt handle. The patch adds these calls.

   **Check:** The socket was created by `memif_socket_create()`, which registered a callback and set the fd in the interrupt handle. So unregister and close are needed before freeing the handle.

**Findings:** None.

---

## Patch 7/12: net/memif: reset FD in interrupt handle

**Analysis:**
The patch adds calls to clear fd references in the memif interrupt handle before freeing it, or uses `rte_intr_fd_close()` to close and clear in one call:

1. In error paths where the fd is closed separately, `rte_intr_fd_set(..., -1)` is added before `rte_intr_instance_free()`.
2. In cleanup paths, `close()` followed by `rte_intr_fd_set(..., -1)` is replaced with `rte_intr_fd_close()`.

**Check:** All changes follow the same pattern: clear the fd reference so the subsequent `rte_intr_instance_free()` does not report a spurious leak. The fds are either closed explicitly (and then cleared) or closed via `rte_intr_fd_close()`.

**Findings:** None.

---

## Patch 8/12: net/sxe2: reset FD in interrupt handle

**Analysis:**
The patch adds a comment and `rte_intr_fd_set(tmp_intr_handle, -1)` in the error path of `sxe2_intr_handler_create()`. The fd passed in is not owned by this function (it's the caller's VFIO device fd or similar), so clearing the reference before freeing the handle is correct.

**Findings:** None.

---

## Patch 9/12: raw/ifpga: reset FD in interrupt handle

**Analysis:**
The patch adds a comment and `rte_intr_fd_set(*intr_handle, -1)` before freeing the interrupt handle in `ifpga_unregister_msix_irq()`. The comment states the fd was pointing to `efd[0]`, which is closed by `rte_intr_efd_disable()`. Clearing the reference prevents a double-close or spurious leak warning.

**Findings:** None.

---

## Patch 10/12: eal/linux: fix closing of event FDs not owned by EAL

**Analysis:**
The patch changes `rte_intr_efd_disable()` to only close eventfds when the handle type is `RTE_INTR_HANDLE_VFIO_MSIX`. The old code used `max_intr > nb_efd` as a heuristic, which was a side effect of VFIO MSI-X allocation reserving an extra slot. This heuristic incorrectly closed eventfds owned by VDEV and EXT handles.

**Check:** The patch replaces the heuristic with an explicit type check. The commit message explains that `rte_intr_efd_enable()` creates eventfds only for `RTE_INTR_HANDLE_VFIO_MSIX`, so only that type's eventfds should be closed by `rte_intr_efd_disable()`.

Looking at patch 11 (which modifies `rte_intr_efd_enable()`), I can confirm that eventfds are created only in the `RTE_INTR_HANDLE_VFIO_MSIX` branch. For `RTE_INTR_HANDLE_VDEV`, the code only validates the size; the driver is responsible for filling the array. For `EXT`, nothing is done.

The fix is correct: only VFIO MSI-X eventfds are owned by EAL and should be closed by EAL.

**Findings:** None.

---

## Patch 11/12: eal/linux: fix eventfd leaks when enabling interrupts

**Analysis:**
The patch fixes two issues in `rte_intr_efd_enable()`:

1. **Eventfd leak on partial allocation failure:** If eventfd creation fails partway through the loop, the previously created eventfds are left open. The patch updates `nb_efd` as each eventfd is created, so the error path can close exactly those that were opened.

2. **Eventfd leak on repeated enable:** If `rte_intr_efd_enable()` is called again without `rte_intr_efd_disable()`, the existing eventfds are overwritten and leaked. The patch adds a check at the start of the VFIO_MSIX branch: if `nb_efd` is already non-zero, return `-EBUSY`.

**Check:**

The loop now updates `nb_efd` immediately after creating each eventfd:
```c
if (rte_intr_efds_index_set(intr_handle, i, fd) ||
        rte_intr_nb_efd_set(intr_handle, i + 1)) {
    ret = -rte_errno;
    close(fd);
    rte_intr_efds_index_set(intr_handle, i, -1);
    goto error;
}
```

The error path closes all eventfds from `0` to `nb_efd - 1`:
```c
error:
    for (int i = 0; i < rte_intr_nb_efd_get(intr_handle); i++) {
        close(rte_intr_efds_index_get(intr_handle, i));
        rte_intr_efds_index_set(intr_handle, i, -1);
    }
    rte_intr_nb_efd_set(intr_handle, 0);
```

This correctly cleans up a partial allocation. The check for `nb_efd != 0` at the start prevents repeated calls from leaking the old eventfds.

**Findings:** None.

---

## Patch 12/12: interrupts: warn on leaked event FDs

**Analysis:**
The patch extends the leak warning in `rte_intr_instance_free()` to check the event fd array, and updates `rte_intr_efd_disable()` to clear all event fd entries to -1 for all types except VDEV.

**Check:**

In `rte_intr_instance_free()`:
```c
for (int i = 0; i < intr_handle->nb_intr; i++) {
    if (intr_handle->efds[i] >= 0)
        EAL_LOG(NOTICE, "Some interrupt handle is leaking an event FD: %d (index %d)",
            intr_handle->efds[i], i);
}
```

In `rte_intr_efd_disable()`:
```c
if (rte_intr_type_get(intr_handle) == RTE_INTR_HANDLE_VFIO_MSIX) {
    for (int i = 0; i < rte_intr_nb_efd_get(intr_handle); i++)
        close(rte_intr_efds_index_get(intr_handle, i));
}
/* VDEV is responsible for efds, see rte_intr_efd_enable(). */
if (rte_intr_type_get(intr_handle) != RTE_INTR_HANDLE_VDEV) {
    for (int i = 0; i < rte_intr_nb_efd_get(intr_handle); i++)
        rte_intr_efds_index_set(intr_handle, i, -1);
}
```

For VFIO_MSIX, eventfds are closed then cleared. For other types except VDEV, the entries are just cleared (the driver closed them). For VDEV, entries are left as-is because the driver owns them and must clear them manually per the updated API documentation.

The updated `rte_intr_efds_index_set()` documentation in `rte_interrupts.h` states:
> Entries still holding a valid fd when the instance is freed are reported
> as leaks, so callers managing the array themselves must reset entries
> to -1 once the fd is closed or no longer referenced.

This is a reasonable API contract.

**Findings:** None.

---

## Final Check: Evidence Rule and Severity Compliance

Re-reading each finding to ensure it quotes code, rests on code actually read, and does not contradict itself:

- No findings were generated.

No style/process items were flagged.

---

Review-Result: CLEAN


More information about the test-report mailing list