use std::cell::Cell;
use std::sync::atomic::{AtomicI32, Ordering};
thread_local! {
static SLOT: Slot = Slot {
is_shell: std::thread::current().name() == Some("main"),
val: Cell::new(0),
};
}
struct Slot {
is_shell: bool,
val: Cell<i32>,
}
pub struct ErrflagCell {
shell: AtomicI32,
}
impl ErrflagCell {
pub const fn new() -> Self {
ErrflagCell {
shell: AtomicI32::new(0),
}
}
#[inline]
pub fn load(&self, order: Ordering) -> i32 {
SLOT.with(|s| {
if s.is_shell {
self.shell.load(order)
} else {
s.val.get()
}
})
}
#[inline]
pub fn store(&self, val: i32, order: Ordering) {
SLOT.with(|s| {
if s.is_shell {
self.shell.store(val, order);
} else {
s.val.set(val);
}
})
}
#[inline]
pub fn fetch_or(&self, val: i32, order: Ordering) -> i32 {
SLOT.with(|s| {
if s.is_shell {
self.shell.fetch_or(val, order)
} else {
let prev = s.val.get();
s.val.set(prev | val);
prev
}
})
}
#[inline]
pub fn fetch_and(&self, val: i32, order: Ordering) -> i32 {
SLOT.with(|s| {
if s.is_shell {
self.shell.fetch_and(val, order)
} else {
let prev = s.val.get();
s.val.set(prev & val);
prev
}
})
}
}
impl Default for ErrflagCell {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::zsh_h::{ERRFLAG_ERROR, ERRFLAG_INT};
#[test]
fn off_thread_writes_do_not_disturb_the_shell_flag() {
static FLAG: ErrflagCell = ErrflagCell::new();
FLAG.store(0, Ordering::Relaxed);
FLAG.fetch_or(ERRFLAG_ERROR | ERRFLAG_INT, Ordering::Relaxed);
std::thread::spawn(|| {
let saved = FLAG.load(Ordering::Relaxed);
assert_eq!(saved, 0, "a non-shell thread starts with its own flag");
FLAG.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
FLAG.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); FLAG.store(saved, Ordering::Relaxed);
})
.join()
.unwrap();
assert_eq!(
FLAG.load(Ordering::Relaxed) & ERRFLAG_ERROR,
ERRFLAG_ERROR,
"the worker's restore cleared the editor's abort flag"
);
}
}