Commit Β·
c85191b
1
Parent(s): 96f3213
Fix LD_PRELOAD deadlock: static .bss ring buffer, zero heap allocation
Browse filesLazyLock heap-allocated the ring buffer via malloc β which is hooked β
causing infinite recursion deadlock on startup.
Fix: pack events into AtomicU64 (8 bytes each), store in a static array
that lives in .bss (zero-initialized by the OS loader, no malloc needed).
8K slots Γ 8 bytes = 64KB ring, entirely in static memory.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- rust_core/src/membrane.rs +40 -52
rust_core/src/membrane.rs
CHANGED
|
@@ -410,37 +410,35 @@ const EVENT_ALLOC: u8 = 1;
|
|
| 410 |
const EVENT_FREE: u8 = 2;
|
| 411 |
const EVENT_EMPTY: u8 = 0;
|
| 412 |
|
| 413 |
-
///
|
| 414 |
-
///
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
}
|
| 424 |
|
| 425 |
-
///
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
v.push(RingEvent {
|
| 434 |
-
tag: std::sync::atomic::AtomicU8::new(EVENT_EMPTY),
|
| 435 |
-
_pad: 0,
|
| 436 |
-
size_kb: 0,
|
| 437 |
-
address: 0,
|
| 438 |
-
timestamp_ns: 0,
|
| 439 |
-
_reserved: [0; 16],
|
| 440 |
-
});
|
| 441 |
-
}
|
| 442 |
-
v.into_boxed_slice()
|
| 443 |
-
});
|
| 444 |
|
| 445 |
/// Write cursor β atomically incremented by malloc/free hooks
|
| 446 |
static WRITE_POS: AtomicUsize = AtomicUsize::new(0);
|
|
@@ -475,13 +473,12 @@ fn start_drain_thread() {
|
|
| 475 |
// Drain up to 1024 events per batch
|
| 476 |
for _ in 0..1024 {
|
| 477 |
let slot = &RING[read_pos & (RING_SIZE - 1)];
|
| 478 |
-
let
|
| 479 |
-
if
|
| 480 |
-
break;
|
| 481 |
}
|
| 482 |
|
| 483 |
-
let
|
| 484 |
-
let size = slot.size_kb as usize * 1024;
|
| 485 |
|
| 486 |
match tag {
|
| 487 |
EVENT_ALLOC => {
|
|
@@ -509,8 +506,8 @@ fn start_drain_thread() {
|
|
| 509 |
_ => {}
|
| 510 |
}
|
| 511 |
|
| 512 |
-
// Mark slot as consumed
|
| 513 |
-
slot.
|
| 514 |
read_pos += 1;
|
| 515 |
drained += 1;
|
| 516 |
}
|
|
@@ -524,27 +521,20 @@ fn start_drain_thread() {
|
|
| 524 |
.expect("Failed to spawn condensate drain thread");
|
| 525 |
}
|
| 526 |
|
| 527 |
-
/// Push an event to the ring buffer β lock-free, ~10ns
|
| 528 |
#[inline(always)]
|
| 529 |
fn push_event(tag: u8, address: usize, size: usize) {
|
| 530 |
let pos = WRITE_POS.fetch_add(1, Ordering::Relaxed);
|
| 531 |
let slot = &RING[pos & (RING_SIZE - 1)];
|
| 532 |
|
| 533 |
-
// If slot isn't empty, drain thread is behind β drop this event.
|
| 534 |
// Better to lose an event than to stall malloc.
|
| 535 |
-
if slot.
|
| 536 |
return;
|
| 537 |
}
|
| 538 |
|
| 539 |
-
//
|
| 540 |
-
|
| 541 |
-
// The drain thread won't read until tag is set.
|
| 542 |
-
let slot_ptr = slot as *const RingEvent as *mut RingEvent;
|
| 543 |
-
unsafe {
|
| 544 |
-
(*slot_ptr).address = address as u32;
|
| 545 |
-
(*slot_ptr).size_kb = (size / 1024).min(u16::MAX as usize) as u16;
|
| 546 |
-
}
|
| 547 |
-
slot.tag.store(tag, Ordering::Release);
|
| 548 |
}
|
| 549 |
|
| 550 |
/// Get the original malloc function
|
|
@@ -638,10 +628,8 @@ static INIT: extern "C" fn() = {
|
|
| 638 |
extern "C" fn init() {
|
| 639 |
INITIALIZED.store(true, Ordering::SeqCst);
|
| 640 |
|
| 641 |
-
//
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
// Start the background drain thread
|
| 645 |
start_drain_thread();
|
| 646 |
|
| 647 |
unsafe { libc::atexit(condensate_summary) };
|
|
|
|
| 410 |
const EVENT_FREE: u8 = 2;
|
| 411 |
const EVENT_EMPTY: u8 = 0;
|
| 412 |
|
| 413 |
+
/// Lock-free ring buffer capacity β must be power of 2.
|
| 414 |
+
/// 8K slots Γ 8 bytes = 64KB. Lives in .bss, zero heap allocation.
|
| 415 |
+
const RING_SIZE: usize = 8192;
|
| 416 |
+
|
| 417 |
+
/// Compact ring event β 8 bytes, packed into a single AtomicU64.
|
| 418 |
+
/// Layout: [tag:8][size_kb:16][address_low:32][_pad:8]
|
| 419 |
+
/// No heap allocation, no struct, no AtomicU8 issues.
|
| 420 |
+
/// The entire ring is a static array of AtomicU64 β lives in .bss.
|
| 421 |
+
static RING: [AtomicU64; RING_SIZE] = {
|
| 422 |
+
const ZERO: AtomicU64 = AtomicU64::new(0);
|
| 423 |
+
[ZERO; RING_SIZE]
|
| 424 |
+
};
|
| 425 |
+
|
| 426 |
+
/// Pack an event into a u64: tag in low byte, size_kb in bytes 1-2, address_low in bytes 3-6
|
| 427 |
+
#[inline(always)]
|
| 428 |
+
fn pack_event(tag: u8, address: usize, size: usize) -> u64 {
|
| 429 |
+
let size_kb = (size / 1024).min(0xFFFF) as u64;
|
| 430 |
+
let addr_low = (address as u32) as u64;
|
| 431 |
+
(tag as u64) | (size_kb << 8) | (addr_low << 24)
|
| 432 |
}
|
| 433 |
|
| 434 |
+
/// Unpack: returns (tag, address_low, size_kb)
|
| 435 |
+
#[inline(always)]
|
| 436 |
+
fn unpack_event(packed: u64) -> (u8, usize, usize) {
|
| 437 |
+
let tag = (packed & 0xFF) as u8;
|
| 438 |
+
let size_kb = ((packed >> 8) & 0xFFFF) as usize;
|
| 439 |
+
let addr_low = ((packed >> 24) & 0xFFFFFFFF) as usize;
|
| 440 |
+
(tag, addr_low, size_kb * 1024)
|
| 441 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
|
| 443 |
/// Write cursor β atomically incremented by malloc/free hooks
|
| 444 |
static WRITE_POS: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
| 473 |
// Drain up to 1024 events per batch
|
| 474 |
for _ in 0..1024 {
|
| 475 |
let slot = &RING[read_pos & (RING_SIZE - 1)];
|
| 476 |
+
let packed = slot.load(Ordering::Acquire);
|
| 477 |
+
if packed == 0 {
|
| 478 |
+
break; // empty slot
|
| 479 |
}
|
| 480 |
|
| 481 |
+
let (tag, address, size) = unpack_event(packed);
|
|
|
|
| 482 |
|
| 483 |
match tag {
|
| 484 |
EVENT_ALLOC => {
|
|
|
|
| 506 |
_ => {}
|
| 507 |
}
|
| 508 |
|
| 509 |
+
// Mark slot as consumed (zero = empty)
|
| 510 |
+
slot.store(0, Ordering::Release);
|
| 511 |
read_pos += 1;
|
| 512 |
drained += 1;
|
| 513 |
}
|
|
|
|
| 521 |
.expect("Failed to spawn condensate drain thread");
|
| 522 |
}
|
| 523 |
|
| 524 |
+
/// Push an event to the ring buffer β lock-free, ~10ns, zero heap allocation
|
| 525 |
#[inline(always)]
|
| 526 |
fn push_event(tag: u8, address: usize, size: usize) {
|
| 527 |
let pos = WRITE_POS.fetch_add(1, Ordering::Relaxed);
|
| 528 |
let slot = &RING[pos & (RING_SIZE - 1)];
|
| 529 |
|
| 530 |
+
// If slot isn't empty (non-zero), drain thread is behind β drop this event.
|
| 531 |
// Better to lose an event than to stall malloc.
|
| 532 |
+
if slot.load(Ordering::Relaxed) != 0 {
|
| 533 |
return;
|
| 534 |
}
|
| 535 |
|
| 536 |
+
// Single atomic store β the packed value IS the fence
|
| 537 |
+
slot.store(pack_event(tag, address, size), Ordering::Release);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
}
|
| 539 |
|
| 540 |
/// Get the original malloc function
|
|
|
|
| 628 |
extern "C" fn init() {
|
| 629 |
INITIALIZED.store(true, Ordering::SeqCst);
|
| 630 |
|
| 631 |
+
// Ring buffer is static .bss β no initialization needed.
|
| 632 |
+
// Start the background drain thread.
|
|
|
|
|
|
|
| 633 |
start_drain_thread();
|
| 634 |
|
| 635 |
unsafe { libc::atexit(condensate_summary) };
|