Skip to content

Fix UB in signal handler - #38

Merged
tpressure merged 3 commits into
cobaltcore-dev:mainfrom
olivereanderson:fix-ub-in-terminate-flag
May 7, 2026
Merged

tpressure merged 3 commits into
cobaltcore-dev:mainfrom
olivereanderson:fix-ub-in-terminate-flag

Conversation

@olivereanderson

@olivereanderson olivereanderson commented May 7, 2026 •

Copy link
Copy Markdown
Contributor

The way the signal handler is currently implemented we end up writing to bool's read by other threads without the use of atomics or synchronization. This is undefined behavior: It is subject to data races and/or some compilers (either existing or to appear in the future) may end up optimizing the reads out entirely.

We fix this by using a single static std::atomic<bool> that the worker and statistics threads read from and the signal handler writes to.

The less invasive solution would be to replace each individual thread's terminate bool with an std::atomic<bool>, but I could not get the constructors to accept that. Moreover the compiler messages were rather arcane and hard to understand for someone who just started writing C++. There is probably something fancy going on with copy constructors and/or constexpr or what have you.

Overall I personally prefer this simpler solution, while admitting that it is less flexible if individual thread termination should be desired in the future. I am going with the YAGNI principle here.

This was first brought up in this previous discussion.

Summary by CodeRabbit

  • Refactor
    • Unified process-wide shutdown so all worker and background threads stop reliably when interrupted.
    • Signal-triggered shutdown made safer and more robust.
    • Threads and related objects made non-copyable/movable to prevent misuse during teardown.
    • Resource cleanup hardened to avoid double-free or null/dangling memory operations during shutdown.

@coderabbitai

coderabbitai Bot commented May 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR centralizes shutdown around a lock-free process-wide std::atomic<bool> global_terminate, updates worker warmup/run loops and the statistics thread to poll it (relaxed loads), makes Statistics and WorkerThread non-copyable and movable, adds a WorkerThread destructor and memory cleanup guard, and updates the SIGINT handler to set the atomic.

Changes

Global Atomic Thread Termination & Thread Cleanup

Layer / File(s) Summary
Dependencies & Global State
memtouch.cpp
Adds <atomic> and declares std::atomic<bool> global_terminate{false}.
Statistics move semantics
memtouch.cpp
Statistics deletes copy ctor/assign and defines move ctor/assign that copy atomic rate values.
WorkerThread move & destructor
memtouch.cpp
WorkerThread deletes copy operations, implements move ctor/assign transferring state and resetting moved-from mem base, and adds a destructor for memory cleanup.
Worker loop polling
memtouch.cpp
Worker warmup and main loops poll global_terminate (relaxed loads) to exit; per-thread terminate removed.
Memory cleanup guard
memtouch.cpp
cleanup_memory() returns early when mem_base == nullptr or MAP_FAILED and nulls mem_base after unmapping.
StatisticsThread polling
memtouch.cpp
StatisticsThread::run() now exits when global_terminate is set (relaxed loads) instead of using an internal boolean.
Signal handler & init
memtouch.cpp
sigint_handler asserts the atomic is lock-free and stores true into global_terminate (relaxed); main relies on default false initialization.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nudged one flag and quieted the fray,
Threads tiptoed home at the end of the day.
Movers carry state, copies left behind,
Memory swept neat, no races to find.
A calm shutdown hop — carrot and rhyme. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix UB in signal handler' directly and clearly describes the main change: addressing undefined behavior in the signal handler by introducing a process-wide atomic boolean instead of unsafe plain bool writes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Microsoft Presidio Analyzer (2.2.362)
memtouch.cpp

Microsoft Presidio Analyzer failed to scan this file


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@olivereanderson
olivereanderson force-pushed the fix-ub-in-terminate-flag branch from f2f6191 to a58b1ca Compare May 7, 2026 07:02
@olivereanderson
olivereanderson marked this pull request as ready for review May 7, 2026 07:03
@olivereanderson

Copy link
Copy Markdown
Contributor Author

I've added Pascal and Philipp as reviewer's, but @tpressure might want to comment on whether the decision to use a single shared atomic termination flag is compatible with the longer term goals of this project.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
memtouch.cpp (2)

25-26: ⚡ Quick win

Add a static_assert to guarantee lock-free signal-handler safety.

The C++ standard (via [support.signal]) only permits plain lock-free atomic operations inside an async signal handler. Functions in <stdatomic.h> (i.e., <atomic>) are signal-safe only when "the atomic arguments are lock-free." std::atomic_flag is the only type guaranteed to be lock-free; std::atomic<bool> carries no such guarantee. If TERMINATE happens to use a lock internally on some target, sigint_handler's store could deadlock with a thread that was interrupted while holding that lock.

std::atomic<bool>::is_always_lock_free is a constexpr that lets you encode the requirement as a hard compile-time failure:

🛡️ Proposed fix
 static std::atomic<bool> TERMINATE;
+static_assert(std::atomic<bool>::is_always_lock_free,
+              "TERMINATE must be lock-free for async-signal-handler safety");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memtouch.cpp` around lines 25 - 26, Add a compile-time check to ensure the
TERMINATE atomic is lock-free for signal-handler safety: add a static_assert
referencing std::atomic<bool>::is_always_lock_free near the declaration of
TERMINATE (or replace TERMINATE with a lock-free type such as std::atomic_flag
and assert its lock-freedom) and update the sigint_handler usage accordingly so
the assertion guarantees the atomic store in sigint_handler is allowed in async
signal context.

77-80: 💤 Low value

Inconsistent memory order — line 78 silently defaults to seq_cst.

Every other access to TERMINATE explicitly uses std::memory_order_relaxed (lines 71, 214, and 277), but the load guarding the outer run-loop omits the order parameter, silently using memory_order_seq_cst. On weakly-ordered architectures this emits a superfluous fence, and the inconsistency can mislead a reader into thinking stronger ordering is semantically required here.

♻️ Proposed fix
-        while (not TERMINATE.load()) {
+        while (not TERMINATE.load(std::memory_order_relaxed)) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memtouch.cpp` around lines 77 - 80, The load guarding the outer loop uses the
default seq_cst ordering while other TERMINATE accesses use
std::memory_order_relaxed, introducing an unnecessary fence and inconsistency;
change the condition in the run-once branch to use
TERMINATE.load(std::memory_order_relaxed) so it matches the other accesses and
avoid the superfluous memory barrier (update the check around
run_loop(num_pages) that references TERMINATE and ensure semantics remain the
same).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@memtouch.cpp`:
- Around line 25-26: Add a compile-time check to ensure the TERMINATE atomic is
lock-free for signal-handler safety: add a static_assert referencing
std::atomic<bool>::is_always_lock_free near the declaration of TERMINATE (or
replace TERMINATE with a lock-free type such as std::atomic_flag and assert its
lock-freedom) and update the sigint_handler usage accordingly so the assertion
guarantees the atomic store in sigint_handler is allowed in async signal
context.
- Around line 77-80: The load guarding the outer loop uses the default seq_cst
ordering while other TERMINATE accesses use std::memory_order_relaxed,
introducing an unnecessary fence and inconsistency; change the condition in the
run-once branch to use TERMINATE.load(std::memory_order_relaxed) so it matches
the other accesses and avoid the superfluous memory barrier (update the check
around run_loop(num_pages) that references TERMINATE and ensure semantics remain
the same).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c418d57a-e700-4093-afdf-7e548d4357dd

📥 Commits

Reviewing files that changed from the base of the PR and between d7fc9fd and a58b1ca.

📒 Files selected for processing (1)
  • memtouch.cpp

@olivereanderson
olivereanderson force-pushed the fix-ub-in-terminate-flag branch 2 times, most recently from faa5661 to 4e111ca Compare May 7, 2026 07:13
scholzp
scholzp previously approved these changes May 7, 2026

@scholzp scholzp left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

I think as long as we don't want to terminate threads on an individual basis, this solution is a good step forward. Well done! :)

Comment thread memtouch.cpp Resolved
Comment thread memtouch.cpp Resolved
Comment thread memtouch.cpp Outdated
static constexpr int PATTERN(0xff);
static constexpr int DEFAULT_STAT_IVAL(1000);

static std::atomic<bool> TERMINATE;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't use upper case for variables, even if they are global. Just call it terminate or global_terminate if you want to make it more obvious.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can auto initialize this to false with:

static std::atomic global_terminate {false};`

This saves you from initializing it in main()

@phip1611 phip1611 May 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw @olivereanderson: Since you are new to C++ (yes?). There are at least four ways of initializing variables in C++. Although ugly IMHO, the {} variant shown by thomas is the recommended and safe way. I can also recommend the book Effective Modern C++

Comment thread memtouch.cpp Outdated
while (not terminate) {
run_loop(num_pages);
if (not run_once) {
while (not TERMINATE.load(std::memory_order_relaxed)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any special reason why you picked memory_order_relaxed here? On x86, it does not really make a difference, but I would have went with memory_order_seq_cst, we don't need to micro optimize here

@phip1611 phip1611 May 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? On x86, it does not really make a difference,

I think this is the wrong way of thinking, both in C++ and also Rust btw. You specify the memory ordering logically for your program and the abstract c++/rust machine implemented by the compiler - it is then the compilers job to map this to imperfect implementations of this abstract machine: specifically x86, arm or riscv systems.

Although practically irrelevant in many cases and likely also here, I expect that there are ways where the compiler can optimize things with a relaxed ordering even if it eventually emits x86 - for the reason mentioned above.'

I don't have any data but what I've learned from the past: Always think about the abstract machine the compiler implements first.

In other words: to my limited understanding: The C++ memory order is not "the instruction ordering for the CPU." It is a source-level synchronization contract.

@olivereanderson olivereanderson May 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any special reason why you picked memory_order_relaxed here?

Because I do not think that seq_cst is a good default.

While it is true that if a weaker ordering works then that is also the case for seq_cst and the converse statement is not true. I maintain that it is better to demonstrate that you understand why the ordering you picked worked. If you are able to do that then you might as well pick the weakest one that works.

On the other hand choosing seq_cst does not mean that the program will work as intended.

@olivereanderson olivereanderson May 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me quote the common misconceptions section in Chapter 3 of Rust Atomics and Locks by Mara Bos here (bold emphasis mine):

Myth: Sequentially consistent memory ordering is a great default and is always correct.

Putting aside performance concerns, sequentially consistent memory ordering is often seen as the perfect type of memory ordering to default to, because of its strong guarantees. It’s true that if any other memory ordering is correct, SeqCst is also correct. This might make it sound like SeqCst is always correct. However, it’s entirely possible that a concurrent algorithm is simply incorrect, regardless of memory ordering.

More importantly, when reading code, SeqCst basically tells the reader: "this operation depends on the total order of every single SeqCst operation in the program," which is an incredibly far-reaching claim. The same code would likely be easier to review and verify if it used weaker memory ordering instead, if possible. For example, Release effectively tells the reader: "this relates to an acquire operation on the same variable," which involves far fewer considerations when forming an understanding of the code.

It is advisable to see SeqCst as a warning sign. Seeing it in the wild often means that either something complicated is going on, or simply that the author did not take the time to analyze their memory ordering related assumptions, both of which are reasons for extra scrutiny.

@olivereanderson olivereanderson May 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to emphasize that I am 100% sure that you do not think that SeqCst always leads to correct behavior.

What I want to get across here is that I don't think SeqCst makes reasoning about what is going on any easier. In fact I would rather say the opposite!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to lecture me about memory consistency models ;) My question was why you explicitly went for relaxed semantics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the short answer is that I do not like the complex semantics of seq_cst so I pick another ordering whenever I can. In this case it is easy enough to see that relaxed ordering does the job.

phip1611
phip1611 previously approved these changes May 7, 2026

@phip1611 phip1611 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please do not forget to initialize TERMINATE

@olivereanderson
olivereanderson dismissed stale reviews from phip1611 and scholzp via ffbd7d3 May 7, 2026 13:44
@olivereanderson
olivereanderson force-pushed the fix-ub-in-terminate-flag branch from 4e111ca to ffbd7d3 Compare May 7, 2026 13:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memtouch.cpp`:
- Around line 218-223: The cleanup_memory() function currently only checks
mem_base == nullptr before calling munmap, but mmap failure leaves mem_base ==
MAP_FAILED; update cleanup_memory() to guard munmap by verifying mem_base is
neither nullptr nor MAP_FAILED (use the MAP_FAILED symbol) and only call
munmap(mem_base, uint64_t(mem_size_mib) * 1024 * 1024) in that case; after a
successful munmap set mem_base = nullptr (and optionally mem_size_mib = 0) to
make cleanup idempotent; apply the same guard/update pattern to the other
similar cleanup block around the code referenced at lines ~228-231.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fe35dba-81b6-4448-8b4c-4c7d4fd6def4

📥 Commits

Reviewing files that changed from the base of the PR and between a58b1ca and ffbd7d3.

📒 Files selected for processing (1)
  • memtouch.cpp

Comment thread memtouch.cpp Resolved
@olivereanderson
olivereanderson force-pushed the fix-ub-in-terminate-flag branch 4 times, most recently from 5d82b94 to df76fd5 Compare May 7, 2026 14:11
@olivereanderson

Copy link
Copy Markdown
Contributor Author

We don't have sticky approvals for some reason.

@tpressure could you please resolve the discussions you started? Or let me know if you don't want to merge this as it is now.

@olivereanderson
olivereanderson force-pushed the fix-ub-in-terminate-flag branch from df76fd5 to d577ea8 Compare May 7, 2026 14:16
@tpressure

Copy link
Copy Markdown
Collaborator

We don't have sticky approvals for some reason.

@tpressure could you please resolve the discussions you started? Or let me know if you don't want to merge this as it is now.

Acually, I think in this case it was a good thing because too much people have merge permissions here. I will do another round of review later today.

We do not want cleanup_memory to call munmap on a null pointer or if
mmap failed.

Also set mem_base to be the null pointer before returning if the munmap
call succeeded.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We move cleanup_memory into WorkerThread's destructor in order to
enable early return in the `run` method in our next commit.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
The way the signal handler is currently implemented we end up
writing to bool's read by other threads without the use of atomics
or synchronization.

This is undefined behavior: It is subject to data races and/or some
compilers (either existing or to appear in the future) may end up
optimizing the reads out entirely.

We fix this by using a single static `std::atomic<bool>` that the
worker and statistics threads read from and the signal handler writes
to.

The less invasive solution would be to replace each individual thread's
`terminate` bool with an `std::atomic<bool>`, but I could not get the
constructors to accept that. Moreover the compiler messages were rather
arcane and hard to understand for someone who just started writing
C++. There is probably something fancy going on with copy constructors
and/or constexpr or what have you.

Overall I personally prefer this simpler solution, while admitting that
it is less flexible if individual thread termination should be desired
in the future. I am going with the YAGNI principle here.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP <oliver.anderson@sap.com>
@olivereanderson
olivereanderson force-pushed the fix-ub-in-terminate-flag branch from d577ea8 to e21e653 Compare May 7, 2026 14:48
@tpressure
tpressure added this pull request to the merge queue May 7, 2026
@phip1611
phip1611 removed this pull request from the merge queue due to a manual request May 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memtouch.cpp`:
- Around line 326-333: Remove the non-async-signal-safe printf call from the
sigint_handler and only perform the lock-free atomic update: delete the
printf("Terminating...\n"); line and keep the static_assert and the
global_terminate.store(true, std::memory_order_relaxed); so the handler only
uses async-signal-safe operations (atomic store) to signal termination.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8e54dc7-0291-42a6-a499-180059f5ebb8

📥 Commits

Reviewing files that changed from the base of the PR and between ffbd7d3 and e21e653.

📒 Files selected for processing (1)
  • memtouch.cpp

Comment thread memtouch.cpp
Comment on lines 326 to 333
void sigint_handler([[maybe_unused]] int s) {
printf("Terminating...\n");
for (auto& worker : worker_storage) {
worker.kill();
}

stat_thread.kill();
static_assert(std::atomic<bool>::is_always_lock_free,
"Unable to ensure async-signal-handler safety: std::atomic<bool> should always "
"be lock free, but the assertion failed.");
global_terminate.store(true, std::memory_order_relaxed);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify signal handlers do not call non-async-signal-safe I/O.
rg -n -C3 'void\s+sigint_handler|printf\s*\(' memtouch.cpp

Repository: cobaltcore-dev/memtouch

Length of output: 1746


Remove printf from the signal handler; it is not async-signal-safe.

Line 327 calls printf inside sigint_handler. The printf function uses internal stdio locks and is not in the POSIX list of async-signal-safe functions. Calling it in a signal handler can deadlock if the interrupted code held the stdio lock. Remove the printf call and rely on the lock-free atomic store to signal termination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memtouch.cpp` around lines 326 - 333, Remove the non-async-signal-safe printf
call from the sigint_handler and only perform the lock-free atomic update:
delete the printf("Terminating...\n"); line and keep the static_assert and the
global_terminate.store(true, std::memory_order_relaxed); so the handler only
uses async-signal-safe operations (atomic store) to signal termination.

@tpressure
tpressure added this pull request to the merge queue May 7, 2026
Merged via the queue into cobaltcore-dev:main with commit df3a8bc May 7, 2026
9 checks passed
@blitz

blitz commented May 21, 2026

Copy link
Copy Markdown

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memtouch.cpp`:
- Around line 326-333: Remove the non-async-signal-safe printf call from the
sigint_handler and only perform the lock-free atomic update: delete the
printf("Terminating...\n"); line and keep the static_assert and the
global_terminate.store(true, std::memory_order_relaxed); so the handler only
uses async-signal-safe operations (atomic store) to signal termination.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8e54dc7-0291-42a6-a499-180059f5ebb8

📥 Commits

Reviewing files that changed from the base of the PR and between ffbd7d3 and e21e653.

📒 Files selected for processing (1)
  • memtouch.cpp

Random comment from the side's What's the reason to dismiss this issue if the goal is fixing UB in signal handlers?

@olivereanderson

Copy link
Copy Markdown
Contributor Author

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memtouch.cpp`:
- Around line 326-333: Remove the non-async-signal-safe printf call from the
sigint_handler and only perform the lock-free atomic update: delete the
printf("Terminating...\n"); line and keep the static_assert and the
global_terminate.store(true, std::memory_order_relaxed); so the handler only
uses async-signal-safe operations (atomic store) to signal termination.

🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info

⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f8e54dc7-0291-42a6-a499-180059f5ebb8

📥 Commits
Reviewing files that changed from the base of the PR and between ffbd7d3 and e21e653.

📒 Files selected for processing (1)

  • memtouch.cpp

Random comment from the side's What's the reason to dismiss this issue if the goal is fixing UB in signal handlers?

Thanks for the question.

The answer is simple: I missed the issue (or thought that it was an older thing that had already been resolved).

Related: Is merely the existence of non-async-signal-safe code in a signal handler UB, or does it depend on its usage?
IIRC if the same (kind of) signal is sent twice, then the signal handler runs to completion before the second signal is handled (or the second signal is simply blocked by the OS, I don't quite recall the details).

I am aware of us using an RwLock in a signal handler on our Cloud hypervisor fork. That is definitely not async signal safe and I've wanted to fix the code for a while, but would prefer to do it after rust-vmm/rust-vmm#165 has been resolved.

@blitz

blitz commented May 22, 2026

Copy link
Copy Markdown

Related: Is merely the existence of non-async-signal-safe code in a signal handler UB, or does it depend on its usage?

Just executing it is enough unless stated otherwise in the documentation.

IIRC if the same (kind of) signal is sent twice, then the signal handler runs to completion before the second signal is handled (or the second signal is simply blocked by the OS, I don't quite recall the details).

The manpage of sigaction says:

sa_mask specifies a mask of signals which should be blocked (i.e., added to the
signal mask of the thread in which the signal handler is invoked) during execution
of the signal handler. In addition, the signal which triggered the handler will be
blocked, unless the SA_NODEFER flag is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants