|WARNING| [v2, 13/13] examples/eventdev_pipeline: make signal handler safe
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Mon Sep 7 19:40:34 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/169162
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-09-07
# Review of DPDK Patch Series: Signal Handler Safety
## Overview
This patch series addresses async-signal-safety violations across multiple DPDK examples and applications. The changes remove unsafe operations (printf, library calls, exit) from signal handlers and replace them with proper patterns using volatile flags.
---
## Patch-by-Patch Review
### Patch 1/13: app/graph: do not call printf in signal
**Assessment: Clean**
Removes printf from signal handler. The `force_quit` flag should be `volatile sig_atomic_t` for correctness, but this appears to be existing code outside the patch scope.
---
### Patch 2/13: examples: remove printf from signal handler
**Assessment: Clean**
Multiple examples simplified to just set flags. Consistent pattern across all files.
---
### Patch 3/13: examples/vmdq: do not print from signal handler
**Assessment: Good**
Properly moves statistics printing out of signal context. The `stats_requested` variable is correctly declared `volatile sig_atomic_t`. The main loop polls it on the main lcore and clears it after printing.
---
### Patch 4/13: examples/symmetric_mp: do not print or exit in handler
**Findings:**
#### Error: Missing volatile qualifier on shared flag
The `quit` variable is written from signal context and read by worker threads, but is declared as `static volatile sig_atomic_t` only. While `sig_atomic_t` ensures atomic access for signal handlers, the `volatile` qualifier here does not provide the necessary semantics for multi-threaded visibility.
When worker threads on other cores check `while (!quit)`, the compiler/CPU can cache the value of `quit` and never observe the signal handler's write.
**Fix:** Use C11 atomic with appropriate memory ordering:
```c
/* Set by SIGTERM and SIGINT handler to stop the forwarding loops. */
static _Atomic bool quit;
static void
signal_handler(__rte_unused int signum)
{
rte_atomic_store_explicit(&quit, true, rte_memory_order_relaxed);
}
static int
lcore_main(void *arg __rte_unused)
{
/* ... */
while (!rte_atomic_load_explicit(&quit, rte_memory_order_relaxed)) {
/* ... */
}
return 0;
}
```
Alternatively, if targeting older C standards or avoiding atomics for simplicity, document that `volatile sig_atomic_t` is acceptable here because the flag is only ever written once (set to 1) and the race is benign (workers may process a few extra packets before observing the quit). However, the atomic approach is preferred for correctness.
---
### Patch 5/13: examples/vdpa: make signal handler safe
**Assessment: Clean**
Proper pattern: flag set in handler, teardown moved to main(). The `quit` variable is `volatile sig_atomic_t`.
---
### Patch 6/13: examples/vhost: make signal handler safe
**Assessment: Clean**
Worker loop checks `volatile sig_atomic_t quit`, unregister moved to main after join. Correct.
---
### Patch 7/13: examples/vhost_blk: do not tear down from signal handler
**Assessment: Clean**
Sleep loop breaks on `quit`, cleanup in main(). Correct pattern.
---
### Patch 8/13: examples/ntb: do not print and re-raise from signal handler
**Assessment: Clean**
Replaces the problematic `signal(signum, SIG_DFL); kill(getpid(), signum)` pattern with setting per-lcore `stopped` flags. The `stopped` field is correctly marked `volatile uint8_t` since it's written from signal context and read by worker loops.
---
### Patch 9/13: examples/ipsecgw: do not print from signal handler
**Assessment: Clean**
Simple printf removal. The `force_quit` flag is not shown in the diff, but the pattern is consistent with other patches.
---
### Patch 10/13: examples/l2fwd-macsec: remove print in signal handler
**Assessment: Clean**
Printf removal. Correct.
---
### Patch 11/13: examples/ethtool: fix exit flag and unchecked cmdline
**Findings:**
#### Warning: volatile used for inter-thread synchronization
The `exit_now` flag is changed to `volatile int`. This is written by main() and read by worker_main() on another lcore. While the comment correctly identifies the visibility issue, `volatile` is not the right solution for multi-threaded synchronization in C11.
**Recommendation:** Use atomic operations:
```c
struct app_config {
struct app_port ports[MAX_PORTS];
int cnt_ports;
_Atomic int exit_now;
};
/* In main(): */
rte_atomic_store_explicit(&app_cfg.exit_now, 1, rte_memory_order_relaxed);
/* In worker_main(): */
while (!rte_atomic_load_explicit(&app_cfg.exit_now, rte_memory_order_relaxed)) {
/* ... */
}
```
However, given that this is a simple flag set once and polled, and the race is benign (at worst the worker processes a few extra packets), the `volatile` approach is acceptable here. The patch improves the existing code.
#### Info: cmdline_stdin_new() check is good practice
The added NULL check on `cmdline_stdin_new()` is correct. The use of `rte_panic()` is appropriate for initialization failures in an example application.
---
### Patch 12/13: examples/vmdq_dcb: allow exit on signal
**Assessment: Clean**
Adds signal handlers for SIGINT/SIGTERM that set `volatile sig_atomic_t quit`, modifies the infinite loop to check the flag, and waits for workers before cleanup. Correct pattern throughout.
---
### Patch 13/13: examples/eventdev_pipeline: make signal handler safe
**Findings:**
#### Error: Use of sigaction without checking return value
The patch uses `sigaction()` three times but does not check the return values. While `sigaction()` failures are rare in practice (typically only on invalid signal numbers), robustness requires checking.
**Fix:**
```c
if (sigaction(SIGINT, &sa, NULL) < 0)
rte_exit(EXIT_FAILURE, "Failed to set SIGINT handler\n");
if (sigaction(SIGTERM, &sa, NULL) < 0)
rte_exit(EXIT_FAILURE, "Failed to set SIGTERM handler\n");
if (sigaction(SIGUSR1, &sa, NULL) < 0)
rte_exit(EXIT_FAILURE, "Failed to set SIGUSR1 handler\n");
```
#### Info: Signal handler design is sound
The split between quit (SIGINT/SIGTERM) and dump (SIGUSR1) handlers is good design. The `SA_RESETHAND` flag on the quit signals ensures a second SIGINT/SIGTERM gets default behavior (process termination), which is standard practice. The dump signal correctly sets `volatile sig_atomic_t dump_dev_signal` and the main loop clears it after dumping.
#### Info: Comment update needed
The commit message says "SIGTSTP (^Z) to use SIGUSR1" but should note that SIGUSR1 has no keyboard shortcut--users must send it via `kill -USR1 <pid>`. Consider adding a note in the example's documentation or startup message.
---
## Summary
### Errors (Must Fix)
1. **Patch 4 (symmetric_mp):** `quit` flag needs proper multi-threaded synchronization (atomic or documented rationale for `volatile sig_atomic_t`)
2. **Patch 13 (eventdev_pipeline):** `sigaction()` calls lack error checking
### Warnings (Should Fix)
1. **Patch 11 (ethtool):** `volatile int exit_now` for multi-threaded flag should use atomics (or document why `volatile` is acceptable here)
### Info (Consider)
1. **Patch 13:** Document that SIGUSR1 has no keyboard shortcut, unlike the old SIGTSTP (^Z)
### Positive Observations
- Consistent pattern across all patches: set flag in handler, act on it in normal context
- Proper use of `volatile sig_atomic_t` for signal-set flags in single-threaded or signal-only scenarios
- Good cleanup discipline: wait for workers, then teardown resources
- `SA_RESETHAND` usage in patch 13 is excellent practice
### General Recommendation
This series makes substantial improvements to signal safety. The two errors above should be addressed before merging. The overall approach is sound and follows best practices for async-signal-safety.
More information about the test-report
mailing list