#![cfg(loom)]
use loom::sync::atomic::{AtomicUsize, Ordering};
use loom::sync::Arc;
use serial_test::serial;
struct SlotSemaforo {
contador: Arc<AtomicUsize>,
max: usize,
}
impl SlotSemaforo {
fn novo(max: usize) -> Self {
Self {
contador: Arc::new(AtomicUsize::new(0)),
max,
}
}
fn clonar(&self) -> Self {
Self {
contador: Arc::clone(&self.contador),
max: self.max,
}
}
fn try_acquire(&self) -> bool {
let mut atual = self.contador.load(Ordering::Acquire);
loop {
if atual >= self.max {
return false;
}
match self.contador.compare_exchange_weak(
atual,
atual + 1,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return true,
Err(novo) => atual = novo,
}
}
}
fn release(&self) {
let anterior = self.contador.fetch_sub(1, Ordering::AcqRel);
assert!(
anterior > 0,
"release sem acquire correspondente — double-free detectado"
);
}
fn ocupados(&self) -> usize {
self.contador.load(Ordering::Acquire)
}
}
#[serial(loom_model)]
#[test]
fn quatro_threads_invariante_maximo_tres_slots() {
const NUM_THREADS: usize = 4;
const MAX_SLOTS: usize = 3;
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.max_branches = 500;
builder.check(|| {
let sem = Arc::new(SlotSemaforo::novo(MAX_SLOTS));
let contador_holds = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for _ in 0..NUM_THREADS {
let sem_t = Arc::new(sem.clonar());
let holds_t = Arc::clone(&contador_holds);
let h = loom::thread::spawn(move || {
if sem_t.try_acquire() {
let agora = holds_t.fetch_add(1, Ordering::AcqRel) + 1;
assert!(
agora <= MAX_SLOTS,
"violação: {agora} holds simultâneos ultrapassam o limite {MAX_SLOTS}"
);
loom::thread::yield_now();
holds_t.fetch_sub(1, Ordering::AcqRel);
sem_t.release();
}
});
handles.push(h);
}
for h in handles {
h.join().expect("thread terminou com pânico");
}
assert_eq!(
sem.ocupados(),
0,
"slots ainda ocupados após todas as threads terminarem"
);
});
}
#[serial(loom_model)]
#[test]
fn release_libera_slot_para_proxima_thread() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.max_branches = 500;
builder.check(|| {
let sem = Arc::new(SlotSemaforo::novo(1));
let sem_a = Arc::new(sem.clonar());
let sem_b = Arc::new(sem.clonar());
let liberado = Arc::new(AtomicUsize::new(0));
let liberado_b = Arc::clone(&liberado);
let ha = loom::thread::spawn(move || {
let adquiriu = sem_a.try_acquire();
assert!(adquiriu, "thread A deve adquirir o único slot disponível");
loom::thread::yield_now();
sem_a.release();
liberado.store(1, Ordering::Release);
});
let hb = loom::thread::spawn(move || {
loop {
if sem_b.try_acquire() {
sem_b.release();
break;
}
if liberado_b.load(Ordering::Acquire) == 1 {
if sem_b.try_acquire() {
sem_b.release();
}
break;
}
loom::thread::yield_now();
}
});
ha.join().expect("thread A terminou com pânico");
hb.join().expect("thread B terminou com pânico");
assert_eq!(
sem.ocupados(),
0,
"slot deve estar livre após ambas as threads terminarem"
);
});
}
#[serial(loom_model)]
#[test]
fn shutdown_limpo_todos_slots_liberados() {
const NUM_THREADS: usize = 4;
const MAX_SLOTS: usize = 4;
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.max_branches = 500;
builder.check(|| {
let sem = Arc::new(SlotSemaforo::novo(MAX_SLOTS));
let adquiridos_total = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for _ in 0..NUM_THREADS {
let sem_t = Arc::new(sem.clonar());
let total_t = Arc::clone(&adquiridos_total);
let h = loom::thread::spawn(move || {
if sem_t.try_acquire() {
total_t.fetch_add(1, Ordering::AcqRel);
loom::thread::yield_now();
sem_t.release();
}
});
handles.push(h);
}
for h in handles {
h.join()
.expect("thread terminou com pânico durante shutdown");
}
assert_eq!(
sem.ocupados(),
0,
"shutdown sujo: {n} slots ainda ocupados",
n = sem.ocupados()
);
let total = adquiridos_total.load(Ordering::Acquire);
assert!(
total > 0,
"nenhuma thread adquiriu slot — possível deadlock no modelo"
);
});
}