|WARNING| net/virtio-user: fix eventfd sharing in secondary process
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Wed Jun 24 19:14:36 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/166167
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-06-24
# DPDK Patch Review
## Summary
This patch adds multiprocess support for virtio-user by sharing kick/call eventfds between primary and secondary processes. The implementation includes an MP channel for fd transfer, per-process storage via `eth_dev->process_private`, and proper synchronization.
---
## Errors
### 1. Resource leak on error path in `virtio_user_sync_eventfds()`
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_sync_eventfds()`
When `rte_mp_request_sync()` succeeds but `mp_reply.nb_received != 1`, the code frees `mp_reply.msgs` and jumps to `err_free`. However, if `mp_reply.nb_received` is greater than 1, the file descriptors in the extra messages (`mp_reply.msgs[1..n]`) are not closed before freeing the message array.
```c
if (rte_mp_request_sync(&mp_req, &mp_reply, &ts) < 0 ||
mp_reply.nb_received != 1) {
PMD_INIT_LOG(ERR, "Failed to request eventfds from primary");
free(mp_reply.msgs); /* BUG: fds in msgs[1..n] leaked */
ret = -EIO;
goto err_free;
}
```
**Fix:** Close all fds in all received messages before freeing:
```c
if (rte_mp_request_sync(&mp_req, &mp_reply, &ts) < 0 ||
mp_reply.nb_received != 1) {
PMD_INIT_LOG(ERR, "Failed to request eventfds from primary");
for (i = 0; i < mp_reply.nb_received; i++) {
for (int j = 0; j < mp_reply.msgs[i].num_fds; j++)
close(mp_reply.msgs[i].fds[j]);
}
free(mp_reply.msgs);
ret = -EIO;
goto err_free;
}
```
---
### 2. Missing unlock on error path in `virtio_user_pmd_probe()`
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_pmd_probe()`
When `rte_mp_action_register()` fails with `rte_errno == ENOTSUP`, the code logs a warning, finishes probing, and jumps to `end` with the spinlock still held.
```c
rte_spinlock_lock(&virtio_user_mp_lock);
if (!virtio_user_mp_registered) {
ret = rte_mp_action_register(VIRTIO_USER_MP_NAME,
virtio_user_mp_primary_handler);
if (ret < 0 && rte_errno != EEXIST) {
rte_spinlock_unlock(&virtio_user_mp_lock); /* unlock here */
if (rte_errno == ENOTSUP) {
PMD_INIT_LOG(WARNING, "...");
rte_eth_dev_probing_finish(eth_dev);
ret = 0;
goto end; /* BUG: lock not released above */
}
...
}
...
}
rte_spinlock_unlock(&virtio_user_mp_lock);
```
**Fix:** Move the unlock before the `ENOTSUP` check or add another unlock in that branch:
```c
if (ret < 0 && rte_errno != EEXIST) {
rte_spinlock_unlock(&virtio_user_mp_lock);
if (rte_errno == ENOTSUP) {
PMD_INIT_LOG(WARNING, "...");
rte_eth_dev_probing_finish(eth_dev);
ret = 0;
goto end;
}
...
}
```
---
## Warnings
### 1. `kickfd < 0` check insufficient without initialized value
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_notify_queue()`
The code checks `if (kickfd < 0 || write(...))` but does not verify `kickfd` was initialized. The initialization in `virtio_user_sync_eventfds()` sets all fds to -1 before populating them, so this is actually safe. However, consider documenting this in a comment or adding an assertion to make the invariant explicit.
```c
int kickfd = virtio_user_get_kickfd(hw, dev, vq->vq_queue_index);
if (kickfd < 0 || write(kickfd, ¬ify_data,
sizeof(notify_data)) < 0)
PMD_DRV_LOG(ERR, "failed to kick backend: %s",
strerror(errno));
```
**Suggestion:** Add a comment documenting that -1 is the sentinel for invalid fds, or use an assertion:
```c
int kickfd = virtio_user_get_kickfd(hw, dev, vq->vq_queue_index);
/* kickfd == -1 if queue not initialized or process_private missing */
if (kickfd < 0 || write(kickfd, ¬ify_data,
sizeof(notify_data)) < 0)
```
---
### 2. Missing release notes
**File:** N/A
This patch fixes a significant bug (secondary processes unable to communicate with vhost backend) and adds new MP functionality. The `Fixes:` tag targets stable branches. Release notes should be updated to document both the bug fix and the new MP channel.
**Suggestion:** Add an entry to `doc/guides/rel_notes/release_<version>.rst` under "Fixed Issues":
```rst
* **net/virtio-user: Fixed secondary process eventfd sharing.**
Secondary processes can now communicate with the vhost backend.
Kick/call eventfds are shared from the primary via a dedicated
multiprocess channel at probe time.
```
---
### 3. `rte_malloc()` used where standard `malloc()` is appropriate
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_sync_eventfds()`
The `pp->kickfds` and `pp->callfds` arrays are allocated with `rte_malloc()` but do not require hugepage backing (not DMA'd, not shared with secondary, not NUMA-specific). Standard `malloc()` is preferred for control-path metadata.
```c
pp->kickfds = rte_malloc("virtio_user_proc_priv",
total_queues * sizeof(int), 0);
pp->callfds = rte_malloc("virtio_user_proc_priv",
total_queues * sizeof(int), 0);
```
**Suggestion:** Use `malloc()` or calloc for these allocations:
```c
pp->kickfds = calloc(total_queues, sizeof(int));
pp->callfds = calloc(total_queues, sizeof(int));
```
(Also update `rte_free()` to `free()` in `virtio_user_free_proc_priv()`.)
---
### 4. Inconsistent alignment in struct initialization
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_sync_eventfds()`
The `timespec` initializer uses designated initializers but the style is not aligned with DPDK norms (initializer list style in struct declarations). This is minor and not a blocker.
```c
struct timespec ts = {.tv_sec = 5, .tv_nsec = 0};
```
**Suggestion (optional):** Use zero-initialization + assignment for clarity in non-constant contexts:
```c
struct timespec ts;
ts.tv_sec = 5;
ts.tv_nsec = 0;
```
---
### 5. Missing bounds check in `virtio_user_get_kickfd()` secondary path
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_get_kickfd()`
The code checks `queue_idx >= pp->nr_queues` but does not verify `pp->kickfds != NULL`. If `virtio_user_sync_eventfds()` fails partway through initialization, `pp` could be allocated but `pp->kickfds` NULL.
```c
struct virtio_user_proc_priv *pp = eth_dev->process_private;
if (pp == NULL || queue_idx >= pp->nr_queues)
return -1;
return pp->kickfds[queue_idx]; /* potential NULL deref */
```
**Fix:** Add NULL check for the array:
```c
if (pp == NULL || pp->kickfds == NULL || queue_idx >= pp->nr_queues)
return -1;
return pp->kickfds[queue_idx];
```
---
## Info
### 1. Consider adding NUMA-aware socket parameter to `rte_zmalloc()`
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_sync_eventfds()`
The `pp` allocation uses `rte_zmalloc(..., 0)` (socket ID = 0). For consistency with other DPDK components, consider using `rte_socket_id()` or `SOCKET_ID_ANY` to allow NUMA-local allocation in the secondary process.
---
### 2. MP handler could verify port type
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_mp_primary_handler()`
The handler assumes that any port with the requested name is a virtio-user device. If another driver uses the same name, the cast `dev = eth_dev->data->dev_private` could access unrelated memory. Consider verifying the port is a virtio-user device (e.g., check `eth_dev->device->driver->name`).
---
### 3. Error log could be more specific about MP registration failure
**File:** `drivers/net/virtio/virtio_user_ethdev.c`
**Function:** `virtio_user_pmd_probe()`
The error message "Failed to register MP handler" does not distinguish between `EEXIST` (already registered, which is handled) and other errors. Consider adding the `rte_errno` value to the log.
```c
PMD_INIT_LOG(ERR, "Failed to register MP handler: %s (errno=%d)",
strerror(rte_errno), rte_errno);
```
---
## Overall Assessment
The patch correctly implements the multiprocess eventfd sharing mechanism. The two **Errors** (fd leak, missing unlock) must be fixed. The **Warnings** should be addressed, particularly the missing release notes and the NULL check in `virtio_user_get_kickfd()`. The **Info** items are optional improvements.
The synchronization design (dev->mutex protecting the fd arrays in the primary, per-process storage in the secondary) is sound. The MP channel usage follows DPDK conventions.
More information about the test-report
mailing list