|WARNING| dev: fix hotplug notification to secondary
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Mon Jul 6 17:30:29 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/166578
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-07-06
# DPDK Patch Review
## Patch: dev: fix hotplug notification to secondary
### Summary
This patch fixes a logic error in multi-process hotplug notification and adds comprehensive unit tests for device hotplug functionality in both single-process and multi-process scenarios.
---
## ERRORS
### 1. Resource leak in `run_primary_hotplug_test()` - strdup() allocations never freed
In the child process path, multiple `strdup()` calls allocate memory that is never freed before `execv()` or `exit()`:
```c
argv[0] = strdup(prgname);
argv[1] = strdup("-l");
argv[2] = strdup(core_str);
argv[3] = strdup("--proc-type=secondary");
argv[4] = strdup(prefix);
argv[5] = NULL;
if (setenv(RECURSIVE_ENV_VAR, "run_hotplug_secondary", 1) == 0)
execv(argv[0], argv);
exit(TEST_FAILED);
```
While `execv()` success replaces the process image (making the leak irrelevant), the `exit(TEST_FAILED)` path leaks all five allocated strings if `execv()` fails.
**Fix:** Free the strings before `exit()`, or use static stack buffers instead of `strdup()`:
```c
/* Free on execv failure path */
if (setenv(RECURSIVE_ENV_VAR, "run_hotplug_secondary", 1) == 0)
execv(argv[0], argv);
/* execv failed - free allocations */
for (int i = 0; i < 5; i++)
free(argv[i]);
exit(TEST_FAILED);
```
Or use stack buffers:
```c
char argv0[256], argv1[8], argv2[16], argv3[32], argv4[256];
snprintf(argv0, sizeof(argv0), "%s", prgname);
snprintf(argv1, sizeof(argv1), "-l");
snprintf(argv2, sizeof(argv2), "%u", rte_get_main_lcore());
snprintf(argv3, sizeof(argv3), "--proc-type=secondary");
snprintf(argv4, sizeof(argv4), "%s", file_prefix_arg());
char *argv[] = {argv0, argv1, argv2, argv3, argv4, NULL};
```
### 2. Memzone not freed on error paths in `run_primary_hotplug_test()`
When `fork()` fails or `rte_dev_probe()` fails, the function jumps to `out:` where `rte_memzone_free(mz)` is called. However, if `mz == NULL` (memzone allocation failed), this is fine. But if `pid` is still -1 (fork failed before assignment), or if probe fails, the memzone remains allocated and is never freed by any secondary because it never ran. The memzone will persist until DPDK shutdown.
This is acceptable for a test that should cause process termination on fork failure, but if the test is retried, the memzone name will conflict.
**Suggested improvement:** Use `rte_memzone_free()` unconditionally at `out:` (it handles NULL gracefully per DPDK convention), but consider adding a cleanup attempt before retrying or document that test must not be re-run in the same primary process after failure.
---
## WARNINGS
### 1. Missing NULL check on `strdup()` return values
The `strdup()` calls can fail and return NULL, which would cause `execv()` to receive a NULL argument. While unlikely in a test environment, this could cause undefined behavior.
**Suggested fix:** Check each `strdup()` or use stack buffers (see Error #1).
### 2. `setenv()` return value inverted
```c
if (setenv(RECURSIVE_ENV_VAR, "run_hotplug_secondary", 1) == 0)
execv(argv[0], argv);
exit(TEST_FAILED);
```
`setenv()` returns 0 on success and -1 on error. The code correctly calls `execv()` only if `setenv()` succeeds (returns 0). However, if `setenv()` fails, the code falls through to `exit(TEST_FAILED)` without any diagnostic message. Consider logging the failure:
```c
if (setenv(RECURSIVE_ENV_VAR, "run_hotplug_secondary", 1) != 0) {
printf("Child: setenv failed: %s\n", strerror(errno));
exit(TEST_FAILED);
}
execv(argv[0], argv);
printf("Child: execv failed: %s\n", strerror(errno));
exit(TEST_FAILED);
```
### 3. `waitpid()` error handling incomplete
```c
if (pid > 0) {
int wstatus;
waitpid(pid, &wstatus, 0);
if (WIFEXITED(wstatus))
printf("Primary: secondary exited with status %d\n", WEXITSTATUS(wstatus));
}
```
`waitpid()` can fail (return -1) or the child can be terminated by a signal. The code should check the return value and handle `WIFSIGNALED`:
```c
if (pid > 0) {
int wstatus;
pid_t w = waitpid(pid, &wstatus, 0);
if (w == -1) {
printf("Primary: waitpid failed: %s\n", strerror(errno));
} else if (WIFEXITED(wstatus)) {
printf("Primary: secondary exited with status %d\n", WEXITSTATUS(wstatus));
} else if (WIFSIGNALED(wstatus)) {
printf("Primary: secondary killed by signal %d\n", WTERMSIG(wstatus));
}
}
```
### 4. Race condition in device removal at cleanup
```c
dev = find_device_by_name(HOTPLUG_TEST_DEV);
if (dev != NULL) {
printf("Primary: removing device %s\n", HOTPLUG_TEST_DEV);
rte_dev_remove(dev);
}
```
Between `find_device_by_name()` and `rte_dev_remove()`, the device pointer could become stale if another thread/process removes the device. While unlikely in a single-threaded test, the pattern is fragile. Consider using the device name directly in error paths, or accept that cleanup is best-effort.
Suggested (best-effort cleanup):
```c
printf("Primary: attempting device cleanup\n");
rte_dev_remove_by_name(HOTPLUG_TEST_DEV); /* if such API exists */
/* or just accept the TOCTOU and note in comment */
```
### 5. Test registration with `NOHUGE_SKIP` and `ASAN_SKIP` for MP test
```c
REGISTER_FAST_TEST(dev_hotplug_mp_autotest, NOHUGE_SKIP, ASAN_SKIP, test_dev_hotplug_mp);
```
Multi-process tests often cannot run without hugepages (secondary cannot attach to primary's shared memory). The `NOHUGE_SKIP` is appropriate. However, `ASAN_SKIP` may be overly conservative if the test itself does not trigger known ASan issues. Consider testing with ASan and only skip if necessary, or document why ASan is incompatible.
### 6. Missing devargs cleanup in `find_device_by_name()`
```c
memset(&da, 0, sizeof(da));
if (rte_devargs_parse(&da, name) != 0)
return NULL;
snprintf(filter, sizeof(filter), "bus=%s", rte_bus_name(da.bus));
rte_devargs_reset(&da);
```
The `rte_devargs_reset()` call is present and correct. However, if `rte_devargs_parse()` returns NULL for `da.bus`, `rte_bus_name(da.bus)` could dereference a NULL pointer.
**Suggested fix:**
```c
if (rte_devargs_parse(&da, name) != 0)
return NULL;
if (da.bus == NULL) {
rte_devargs_reset(&da);
return NULL;
}
snprintf(filter, sizeof(filter), "bus=%s", rte_bus_name(da.bus));
rte_devargs_reset(&da);
```
---
## INFO / NOTES
### 1. Code style - comparison style is correct
The patch correctly uses explicit comparisons throughout:
- `if (mz == NULL)`
- `if (dev != NULL)`
- `rte_atomic_load_explicit(...) != expected_state`
No style issues.
### 2. Atomic usage is correct
The use of `RTE_ATOMIC(int)` with `rte_atomic_load_explicit()` and `rte_atomic_store_explicit()` follows DPDK guidelines:
- `rte_memory_order_acquire` for loads
- `rte_memory_order_release` for stores
- `rte_memory_order_relaxed` for result write (acceptable as it's synchronized by the state flag)
Well done.
### 3. Bugfix correctness
The core fix in `eal_common_dev.c` is correct:
```c
- bool do_mp = !!internal_conf->no_shconf;
+ bool do_mp = internal_conf->no_shconf == 0;
```
`no_shconf == 0` means shared config is enabled, so multi-process notification should be sent. The original `!!no_shconf` inverted the logic (MP notification sent when shared config disabled). This fix resolves the reported bug.
### 4. Test coverage valuable but limited
The test provides basic validation of MP notification. It does NOT cover:
- Multiple secondaries simultaneously
- Device removal notification
- Error paths in MP messaging
- Bus-specific behaviors
These are acceptable limitations for a first-gate test. Consider documenting what the test does NOT cover in a comment or commit message.
---
## FINAL RECOMMENDATION
The patch fixes a critical logic bug and adds valuable test coverage. However, **resource leaks in error paths** (strdup allocations, potential memzone leak) must be fixed before merging.
**Required fixes:**
- Address Error #1 (strdup leaks in child process)
- Address Error #2 or document memzone cleanup expectation
**Suggested improvements:**
- Add NULL checks for strdup() (Warning #1)
- Improve error diagnostics in child process (Warning #2)
- Improve waitpid() handling (Warning #3)
- Add NULL check for da.bus (Warning #6)
With these corrections, the patch will be suitable for merging.
More information about the test-report
mailing list