Skip to content

Commit 87bcfca

Browse files
author
James Gober
committed
Enhance CI workflow by verifying toolchain installation before and after cache restoration
1 parent 8b134ac commit 87bcfca

2 files changed

Lines changed: 85 additions & 55 deletions

File tree

.github/workflows/ci.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,22 @@ jobs:
2929
# `cargo --version` fails the job loudly with a clear diagnostic
3030
# if anything is still wrong, instead of leaving the failure
3131
# buried inside a downstream `cargo build`.
32-
- name: Verify toolchain is fully installed
32+
- name: Verify toolchain before cache
3333
run: |
34-
rustup show active-toolchain || rustup default stable
34+
rustup default stable
35+
rustup show
3536
cargo --version
37+
rustc --version
3638
- uses: Swatinem/rust-cache@v2
39+
# The cache restore on macos-latest has been observed to
40+
# leave `cargo` resolving to `rustup-init` on the very next
41+
# step. Re-running `rustup default stable` after the cache
42+
# restore is the workaround mod-tempdir adopted; we mirror
43+
# it here.
44+
- name: Re-verify toolchain after cache
45+
run: |
46+
rustup default stable
47+
cargo --version
3748
3849
- name: Build (default features)
3950
run: cargo build --verbose

src/symbolicate/unix.rs

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
//! Unix (Linux / macOS / *BSD) symbolicator using `addr2line` and
22
//! `object`.
33
//!
4-
//! `Context` borrows from the parsed `object::File`, which borrows
5-
//! from the raw binary bytes. We resolve the lifetime puzzle by
6-
//! reading the binary once and leaking the bytes via `Box::leak`,
7-
//! upgrading them to `'static`. The leaked allocation lives for
8-
//! the rest of the process; for a profiler this is fine, since the
9-
//! same process that opened the binary uses it for the duration.
4+
//! `addr2line::Context<EndianRcSlice<_>>` (the type returned by
5+
//! the default `Context::new` constructor) contains `Rc<[u8]>` in
6+
//! its readers plus internal `UnsafeCell`-backed memoisation, so
7+
//! it is fundamentally `!Send + !Sync`. We sidestep the cross-
8+
//! thread sharing requirement by holding the symbolicator in a
9+
//! `thread_local!` cell. Each thread that calls
10+
//! `symbolicated_report()` builds its own context on first use;
11+
//! the per-address cache in `super::report` then deduplicates the
12+
//! per-address resolution work across threads.
13+
//!
14+
//! Memory cost: roughly the binary's on-disk size per thread that
15+
//! ever symbolicated. Bytes are leaked once per thread via
16+
//! `Box::leak` so the context's `'static` references remain valid
17+
//! for the rest of the process.
1018
11-
use std::sync::OnceLock;
19+
use std::cell::RefCell;
1220

1321
use addr2line::Context;
1422
use object::read::File as ObjectFile;
@@ -21,14 +29,22 @@ struct UnixSymbolicator {
2129
ctx: Ctx,
2230
}
2331

24-
static SYMBOLICATOR: OnceLock<Option<UnixSymbolicator>> = OnceLock::new();
32+
thread_local! {
33+
// Outer `Option` distinguishes "not yet tried" (None) from
34+
// "tried and failed" (Some(None)). After the first call, the
35+
// outer is always Some, so we never retry the failed-build
36+
// path on subsequent calls.
37+
static SYMBOLICATOR: RefCell<Option<Option<UnixSymbolicator>>> =
38+
const { RefCell::new(None) };
39+
}
2540

2641
fn build() -> Option<UnixSymbolicator> {
2742
let path = super::self_binary::self_exe()?;
2843
let bytes = std::fs::read(path).ok()?;
2944
// Leak the bytes so the parsed `File` and the `Context` can
3045
// safely hold references for the rest of the process. Memory
31-
// cost: roughly the binary's on-disk size, one-time.
46+
// cost: roughly the binary's on-disk size, one-time per
47+
// thread that ever symbolicates.
3248
let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice());
3349
let obj = ObjectFile::parse(leaked).ok()?;
3450
let ctx = Context::new(&obj).ok()?;
@@ -38,60 +54,63 @@ fn build() -> Option<UnixSymbolicator> {
3854
/// Resolve one address. Returns one or more frames; multiple
3955
/// frames mean inlined call-site expansion at this address.
4056
pub(crate) fn resolve(address: u64) -> Vec<SymbolicatedFrame> {
41-
let sym = SYMBOLICATOR.get_or_init(build);
42-
let Some(sym) = sym.as_ref() else {
43-
return vec![SymbolicatedFrame {
44-
address,
45-
function: None,
46-
file: None,
47-
line: None,
48-
inlined: false,
49-
}];
50-
};
51-
52-
let mut out = Vec::new();
53-
let Ok(mut frames) = sym.ctx.find_frames(address).skip_all_loads() else {
54-
return vec![SymbolicatedFrame {
57+
let unresolved = || {
58+
vec![SymbolicatedFrame {
5559
address,
5660
function: None,
5761
file: None,
5862
line: None,
5963
inlined: false,
60-
}];
64+
}]
6165
};
6266

63-
let mut idx = 0usize;
64-
while let Ok(Some(frame)) = frames.next() {
65-
let function = frame
66-
.function
67-
.as_ref()
68-
.and_then(|f| f.raw_name().ok())
69-
.map(|name| rustc_demangle::demangle(&name).to_string());
67+
SYMBOLICATOR.with(|cell| {
68+
let mut guard = match cell.try_borrow_mut() {
69+
Ok(g) => g,
70+
Err(_) => return unresolved(),
71+
};
72+
let sym = guard.get_or_insert_with(build);
73+
let Some(sym) = sym.as_ref() else {
74+
return unresolved();
75+
};
7076

71-
let (file, line) = match frame.location {
72-
Some(loc) => (loc.file.map(std::path::PathBuf::from), loc.line),
73-
None => (None, None),
77+
let mut out = Vec::new();
78+
let Ok(mut frames) = sym.ctx.find_frames(address).skip_all_loads() else {
79+
return unresolved();
7480
};
7581

76-
out.push(SymbolicatedFrame {
77-
address,
78-
function,
79-
file,
80-
line,
81-
inlined: idx > 0,
82-
});
83-
idx += 1;
84-
}
82+
let mut idx = 0usize;
83+
while let Ok(Some(frame)) = frames.next() {
84+
let function = frame
85+
.function
86+
.as_ref()
87+
.and_then(|f| f.raw_name().ok())
88+
.map(|name| rustc_demangle::demangle(&name).to_string());
8589

86-
if out.is_empty() {
87-
out.push(SymbolicatedFrame {
88-
address,
89-
function: None,
90-
file: None,
91-
line: None,
92-
inlined: false,
93-
});
94-
}
90+
let (file, line) = match frame.location {
91+
Some(loc) => (loc.file.map(std::path::PathBuf::from), loc.line),
92+
None => (None, None),
93+
};
94+
95+
out.push(SymbolicatedFrame {
96+
address,
97+
function,
98+
file,
99+
line,
100+
inlined: idx > 0,
101+
});
102+
idx += 1;
103+
}
95104

96-
out
105+
if out.is_empty() {
106+
out.push(SymbolicatedFrame {
107+
address,
108+
function: None,
109+
file: None,
110+
line: None,
111+
inlined: false,
112+
});
113+
}
114+
out
115+
})
97116
}

0 commit comments

Comments
 (0)