Skip to content

Commit 6cbdde6

Browse files
committed
no-mistakes(review): share writable glibc_single_threaded shim with test target
1 parent 6ffc83d commit 6cbdde6

3 files changed

Lines changed: 63 additions & 39 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2025-2026 Andrey Vasilevsky <anvanster@gmail.com>
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! glibc 2.31 compatibility storage for `__libc_single_threaded`.
5+
//!
6+
//! `__libc_single_threaded` was added in glibc 2.32 but ONNX Runtime
7+
//! references it, so a build for SLES 15 SP4 and similar needs a definition to
8+
//! link against. Every target that links ONNX Runtime - the binary and the
9+
//! test executables, which do not include `main.rs` - has to supply one.
10+
//!
11+
//! The storage must be WRITABLE. It was previously `pub static ...: u8 = 0`,
12+
//! which lands in .rodata, under a comment claiming "on newer glibc the real
13+
//! symbol shadows this at runtime" - the opposite of how ELF resolves it. A
14+
//! definition in the executable takes precedence over the one in libc, and on
15+
//! aarch64 this symbol is also emitted into .dynsym, so glibc bound its own
16+
//! startup write of the flag to the read-only byte and took SIGSEGV before
17+
//! `main()`: every invocation died, including `--version` and `--help`
18+
//! (issue #15). x86_64 escaped only because the symbol is not dynamically
19+
//! exported there, so glibc kept using its own copy.
20+
//!
21+
//! glibc owns the value: it sets the flag at startup and clears it when a
22+
//! thread is created. We only supply the storage, and never read it. On a
23+
//! glibc too old to maintain it, the byte stays 0, which is the conservative
24+
//! "not single threaded" answer.
25+
//!
26+
//! Every definition of the symbol must use [`SingleThreaded`] so the writable
27+
//! storage cannot drift back to a plain `u8` in one target and not another.
28+
29+
use std::cell::UnsafeCell;
30+
31+
/// Writable single-byte storage for glibc's `__libc_single_threaded` flag.
32+
#[repr(transparent)]
33+
pub struct SingleThreaded(UnsafeCell<u8>);
34+
35+
impl SingleThreaded {
36+
/// The initial value of the flag: "not single threaded".
37+
pub const ZERO: Self = Self(UnsafeCell::new(0));
38+
}
39+
40+
// SAFETY: glibc is the only writer, from its own startup and thread-creation
41+
// paths, and this process never reads the byte. The UnsafeCell is what places
42+
// it in writable memory rather than .rodata.
43+
unsafe impl Sync for SingleThreaded {}

crates/codegraph-server/src/lib.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@
1212
//! - **LSP** (default): Standard Language Server Protocol for IDE integration
1313
//! - **MCP** (`--mcp` flag): Model Context Protocol for AI client integration
1414
15-
// glibc 2.31 compat (test builds): the production shim lives in main.rs
16-
// for the binary target. `cargo test --lib` builds a separate test
17-
// executable that doesn't include main.rs, so ONNX Runtime's reference
18-
// to `__libc_single_threaded` (added in glibc 2.32) goes unresolved
19-
// when linking tests on SLES 15-SP4. This duplicate is gated on
20-
// `cfg(test)` so the binary target never sees two definitions.
15+
// glibc 2.31 compat (test builds): the production shim lives in main.rs for
16+
// the binary target. `cargo test --lib` builds a separate test executable that
17+
// doesn't include main.rs, so ONNX Runtime's reference to
18+
// `__libc_single_threaded` (added in glibc 2.32) goes unresolved when linking
19+
// tests on SLES 15-SP4. This definition is gated on `cfg(test)` so the binary
20+
// target never sees two of them; see `glibc_compat` for why the storage has to
21+
// be writable.
2122
#[cfg(all(target_os = "linux", test))]
2223
#[no_mangle]
23-
pub static __libc_single_threaded: u8 = 0;
24+
#[allow(non_upper_case_globals)]
25+
pub static __libc_single_threaded: glibc_compat::SingleThreaded =
26+
glibc_compat::SingleThreaded::ZERO;
2427

2528
pub mod ai_query;
2629
pub mod backend;
@@ -33,6 +36,7 @@ pub mod domain;
3336
pub mod embed_queue;
3437
pub mod error;
3538
pub mod git_mining;
39+
pub mod glibc_compat;
3640
pub mod handlers;
3741
pub mod index;
3842
pub mod index_state;

crates/codegraph-server/src/main.rs

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,16 @@ use std::path::PathBuf;
1313
use std::sync::atomic::{AtomicUsize, Ordering};
1414
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
1515

16-
// glibc 2.31 compat: __libc_single_threaded was added in glibc 2.32 but ONNX
17-
// Runtime references it, so a build for SLES 15 SP4 and similar needs a
18-
// definition to link against.
19-
//
20-
// The storage must be WRITABLE. This was previously `pub static ... : u8 = 0`,
21-
// which lands in .rodata, and the comment claimed "on newer glibc the real
22-
// symbol shadows this at runtime" - the opposite of how ELF resolves it. A
23-
// definition in the executable takes precedence over the one in libc, and on
24-
// aarch64 this symbol is also emitted into .dynsym, so glibc bound its own
25-
// startup write of the flag to our read-only byte and took SIGSEGV before
26-
// main(): every invocation died, including `--version` and `--help`
27-
// (issue #15). x86_64 escaped only because the symbol is not dynamically
28-
// exported there, so glibc kept using its own copy.
29-
//
30-
// glibc owns the value: it sets the flag at startup and clears it when a
31-
// thread is created. We only supply the storage, and never read it. On a glibc
32-
// too old to maintain it, the byte stays 0, which is the conservative
33-
// "not single threaded" answer.
16+
// glibc 2.31 compat: ONNX Runtime references `__libc_single_threaded`, which
17+
// glibc only defines from 2.32 on, so a build for SLES 15 SP4 and similar
18+
// needs a definition to link against. The storage has to be writable - glibc
19+
// writes the flag at startup - which is what `SingleThreaded` provides; see
20+
// `codegraph_server::glibc_compat` for the full story (issue #15).
3421
#[cfg(target_os = "linux")]
35-
mod glibc_compat {
36-
use std::cell::UnsafeCell;
37-
38-
#[repr(transparent)]
39-
pub struct SingleThreaded(UnsafeCell<u8>);
40-
41-
// SAFETY: glibc is the only writer, from its own startup and
42-
// thread-creation paths, and this process never reads the byte. The
43-
// UnsafeCell is what places it in writable memory rather than .rodata.
44-
unsafe impl Sync for SingleThreaded {}
45-
46-
#[no_mangle]
47-
pub static __libc_single_threaded: SingleThreaded = SingleThreaded(UnsafeCell::new(0));
48-
}
22+
#[no_mangle]
23+
#[allow(non_upper_case_globals)]
24+
pub static __libc_single_threaded: codegraph_server::glibc_compat::SingleThreaded =
25+
codegraph_server::glibc_compat::SingleThreaded::ZERO;
4926

5027
#[derive(Parser)]
5128
#[command(name = "codegraph-server")]

0 commit comments

Comments
 (0)