Skip to content

Commit 9ad9d35

Browse files
committed
Add signal safety comments and changes
1 parent 097c70a commit 9ad9d35

File tree

5 files changed

+40
-12
lines changed

5 files changed

+40
-12
lines changed

analysis/runtime/src/handlers.rs

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ use crate::events::{Event, EventKind};
22
use crate::mir_loc::MirLocId;
33
use crate::runtime::global_runtime::RUNTIME;
44

5+
// WARNING! Most handlers in this file may be called from a signal handler,
6+
// so they and all their callees should be signal-safe.
7+
// Signal handlers are generally not supposed to call memory allocation
8+
// functions, so those do not need to be signal-safe.
9+
510
/// A hook function (see [`HOOK_FUNCTIONS`]).
611
///
712
/// Instruments 64-bit `c2rust transpile`d `malloc`, which is similar to `libc::malloc`.

analysis/runtime/src/runtime/backend.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ impl Backend {
8989
let event = match events.pop() {
9090
Some(event) => event,
9191
None => {
92-
// We can't use anything with a futex here since
93-
// the event sender might run inside a signal handler
92+
// We can't block on a lock/semaphore here since
93+
// it might not be safe for the event sender to wake
94+
// us from inside a signal handler
9495
backoff.snooze();
9596
continue;
9697
}

analysis/runtime/src/runtime/global_runtime.rs

+5
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ impl GlobalRuntime {
4040
///
4141
/// It also silently drops the [`Event`] if the [`ScopedRuntime`]
4242
/// has been [`ScopedRuntime::finalize`]d/[`GlobalRuntime::finalize`]d.
43+
///
44+
/// May be called from a signal handler, so it needs to be async-signal-safe.
4345
pub fn send_event(&self, event: Event) {
46+
// # Async-signal-safety: OnceCell::get() is just a dereference
4447
match self.runtime.get() {
4548
None => {
4649
// Silently drop the [`Event`] as the [`ScopedRuntime`] isn't ready/initialized yet.
50+
//
51+
// # Async-signal-safety: `skip_event(_, BeforeMain)` is async-signal-safe.
4752
skip_event(event, SkipReason::BeforeMain);
4853
}
4954
Some(runtime) => {

analysis/runtime/src/runtime/scoped_runtime.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ impl ExistingRuntime for MainThreadRuntime {
103103
self.backend.lock().unwrap().flush();
104104
}
105105

106+
// # Async-signal-safety: NOT SAFE!!!
107+
// Do not use this with programs that install signal handlers.
106108
fn send_event(&self, event: Event) {
107109
self.backend.lock().unwrap().write(event);
108110
}
@@ -122,6 +124,10 @@ pub struct BackgroundThreadRuntime {
122124

123125
impl BackgroundThreadRuntime {
124126
fn push_event(&self, mut event: Event, can_sleep: bool) {
127+
// # Async-signal-safety: This needs `can_sleep == false` if called from
128+
// a signal handler; in that case, it spins instead of sleeping
129+
// which should be safe. `ArrayQueue::push` is backed by a fixed-size
130+
// array so it does not allocate.
125131
let backoff = Backoff::new();
126132
while let Err(event_back) = self.tx.push(event) {
127133
if can_sleep {
@@ -162,10 +168,16 @@ impl ExistingRuntime for BackgroundThreadRuntime {
162168
fn send_event(&self, event: Event) {
163169
match self.finalized.get() {
164170
None => {
171+
// # Async-signal-safety: `push_event` is safe if `can_sleep == false`
165172
self.push_event(event, false);
166173
}
167174
Some(()) => {
168-
// Silently drop the [`Event`] as the [`BackgroundThreadRuntime`] has already been [`BackgroundThreadRuntime::finalize`]d.
175+
// Silently drop the [`Event`] as the [`BackgroundThreadRuntime`]
176+
// has already been [`BackgroundThreadRuntime::finalize`]d.
177+
//
178+
// # Async-signal-safety: `skip_event(_, AfterMain)` is NOT SAFE;
179+
// however, see the comment in `skip_event` for an explanation
180+
// of why this will probably be okay in practice.
169181
skip_event(event, SkipReason::AfterMain);
170182
}
171183
}

analysis/runtime/src/runtime/skip.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
use std::{
22
fmt::{self, Display, Formatter},
3-
sync::atomic::{AtomicU64, Ordering},
3+
sync::atomic::{AtomicBool, AtomicU64, Ordering},
44
};
55

6-
use once_cell::sync::OnceCell;
7-
86
use crate::events::Event;
97

108
#[derive(Debug)]
@@ -24,8 +22,7 @@ impl Display for SkipReason {
2422
}
2523

2624
static EVENTS_SKIPPED_BEFORE_MAIN: AtomicU64 = AtomicU64::new(0);
27-
28-
static WARNED_AFTER_MAIN: OnceCell<()> = OnceCell::new();
25+
static WARNED_AFTER_MAIN: AtomicBool = AtomicBool::new(false);
2926

3027
/// Notify the user if any [`Event`]s were skipped before `main`.
3128
///
@@ -45,14 +42,22 @@ pub(super) fn skip_event(event: Event, reason: SkipReason) {
4542
use SkipReason::*;
4643
match reason {
4744
BeforeMain => {
45+
// # Async-signal-safety: atomic increments are safe.
4846
EVENTS_SKIPPED_BEFORE_MAIN.fetch_add(1, Ordering::Relaxed);
4947
}
5048
AfterMain => {
51-
// This is after `main`, so it's safe to use things like `eprintln!`,
52-
// which uses `ReentrantMutex` internally, which may use `pthread` mutexes.
53-
WARNED_AFTER_MAIN.get_or_init(|| {
49+
// # Async-signal-safety: not really signal-safe, but if we
50+
// get a signal after `main` ends, we're probably fine.
51+
// The allocator should have enough free memory by now
52+
// to not need to call `mmap`.
53+
if !WARNED_AFTER_MAIN.swap(true, Ordering::Relaxed) {
54+
// WARNED_AFTER_MAIN was previously `false` but we swapped it,
55+
// which will happen exactly once per run so we can print now.
5456
eprintln!("skipping {reason}");
55-
});
57+
}
58+
// TODO: It would be nice to get rid of the two `eprintln`s here
59+
// so we can guarantee signal safety, but then we would get no
60+
// debugging output.
5661
eprintln!("skipped event after `main`: {:?}", event.kind);
5762
}
5863
};

0 commit comments

Comments
 (0)