Skip to content

Commit 01cff82

Browse files
authored
framehop: remove the allocation in signal handle of framehop (#282)
* remove the allocation in signal handle of framehop Signed-off-by: Yang Keao <[email protected]> * add test for ALLOC test utilities Signed-off-by: Yang Keao <[email protected]> --------- Signed-off-by: Yang Keao <[email protected]>
1 parent 0c52c5f commit 01cff82

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

.github/workflows/rust.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,16 @@ jobs:
152152
uses: actions-rs/[email protected]
153153
with:
154154
command: test
155-
args: --features flamegraph,prost-codec --target ${{ matrix.target }}
155+
args: --features flamegraph,prost-codec --target ${{ matrix.target }} -- --test-threads 1
156156

157157
- name: Run cargo test protobuf
158158
uses: actions-rs/[email protected]
159159
with:
160160
command: test
161-
args: --features flamegraph,protobuf-codec --target ${{ matrix.target }}
161+
args: --features flamegraph,protobuf-codec --target ${{ matrix.target }} -- --test-threads 1
162+
163+
- name: Run cargo test framehop
164+
uses: actions-rs/[email protected]
165+
with:
166+
command: test
167+
args: --features flamegraph,protobuf-codec,framehop-unwinder --target ${{ matrix.target }} -- --test-threads 1

src/backtrace/framehop_unwinder.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use framehop::{
33
};
44
use libc::{c_void, ucontext_t};
55
use once_cell::sync::Lazy;
6+
use spin::RwLock;
67
mod shlib;
78

89
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
@@ -123,7 +124,8 @@ fn read_stack(addr: u64) -> Result<u64, ()> {
123124
}
124125
}
125126

126-
static mut UNWINDER: Lazy<FramehopUnwinder> = Lazy::new(|| FramehopUnwinder::new());
127+
static UNWINDER: Lazy<RwLock<FramehopUnwinder>> =
128+
Lazy::new(|| RwLock::new(FramehopUnwinder::new()));
127129
#[derive(Clone, Debug)]
128130
pub struct Frame {
129131
pub ip: usize,
@@ -158,10 +160,22 @@ pub struct Trace;
158160
impl super::Trace for Trace {
159161
type Frame = Frame;
160162

163+
fn init() {
164+
let _ = UNWINDER.read();
165+
}
166+
161167
fn trace<F: FnMut(&Self::Frame) -> bool>(ctx: *mut c_void, cb: F)
162168
where
163169
Self: Sized,
164170
{
165-
unsafe { UNWINDER.iter_frames(ctx, cb) };
171+
// For Linux, this `try_write` should always succeed, because `SIGPROF` will never be delivered to
172+
// another thread while the signal handler is running. However, I'm not sure about other OSes, so
173+
// we use `try_write` to be safe instead of using `static mut` and `unsafe` directly.
174+
match UNWINDER.try_write() {
175+
None => return,
176+
Some(mut unwinder) => {
177+
unwinder.iter_frames(ctx, cb);
178+
}
179+
}
166180
}
167181
}

src/backtrace/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub trait Frame: Sized + Clone {
4343
pub trait Trace {
4444
type Frame;
4545

46+
// init will be called before running the first trace in signal handler
47+
fn init() {}
48+
4649
fn trace<F: FnMut(&Self::Frame) -> bool>(_: *mut libc::c_void, cb: F)
4750
where
4851
Self: Sized;

src/profiler.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ pub struct ProfilerGuard<'a> {
188188
fn trigger_lazy() {
189189
let _ = backtrace::Backtrace::new();
190190
let _profiler = PROFILER.read();
191+
TraceImpl::init();
191192
}
192193

193194
impl ProfilerGuard<'_> {
@@ -524,3 +525,77 @@ impl Profiler {
524525
if let Ok(()) = self.data.add(frames, 1) {}
525526
}
526527
}
528+
529+
#[cfg(test)]
530+
pub mod tests {
531+
use super::*;
532+
533+
struct AllocDetector {
534+
should_count_alloc: std::sync::atomic::AtomicBool,
535+
alloc_count: std::sync::atomic::AtomicUsize,
536+
}
537+
538+
unsafe impl std::alloc::GlobalAlloc for AllocDetector {
539+
unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 {
540+
if self
541+
.should_count_alloc
542+
.load(std::sync::atomic::Ordering::SeqCst)
543+
{
544+
self.alloc_count
545+
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
546+
}
547+
548+
unsafe { std::alloc::System.alloc(layout) }
549+
}
550+
551+
unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) {
552+
unsafe { std::alloc::System.dealloc(ptr, layout) }
553+
}
554+
}
555+
impl AllocDetector {
556+
fn enable_count_alloc(&self) {
557+
self.should_count_alloc
558+
.store(true, std::sync::atomic::Ordering::SeqCst);
559+
}
560+
561+
fn disable_count_alloc(&self) {
562+
self.should_count_alloc
563+
.store(false, std::sync::atomic::Ordering::SeqCst);
564+
}
565+
566+
fn alloc_count(&self) -> usize {
567+
self.alloc_count.load(std::sync::atomic::Ordering::SeqCst)
568+
}
569+
}
570+
571+
#[global_allocator]
572+
static ALLOC: AllocDetector = AllocDetector {
573+
should_count_alloc: std::sync::atomic::AtomicBool::new(false),
574+
alloc_count: std::sync::atomic::AtomicUsize::new(0),
575+
};
576+
577+
#[test]
578+
fn test_no_alloc_during_unwind() {
579+
// This test cannot run parallelly because it requires the global allocator to
580+
// record the allocation count.
581+
582+
trigger_lazy();
583+
PROFILER.write().as_mut().unwrap().start().unwrap();
584+
let timer = Timer::new(999);
585+
let start = std::time::Instant::now();
586+
ALLOC.enable_count_alloc();
587+
588+
// alloc something to make sure the ALLOC works fine.
589+
let _alloc = Box::new(1usize);
590+
// busy loop for a while to trigger some samples
591+
while start.elapsed().as_millis() < 500 {
592+
std::hint::black_box(());
593+
}
594+
ALLOC.disable_count_alloc();
595+
596+
assert_eq!(ALLOC.alloc_count(), 1);
597+
598+
drop(timer);
599+
PROFILER.write().as_mut().unwrap().stop().unwrap();
600+
}
601+
}

0 commit comments

Comments
 (0)