use std::sync::LazyLock;
use wasm_lite_std::Mutex;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ThreadRecord {
pub name: String,
pub id: String,
pub note: Option<String>,
}
static REGISTRY: LazyLock<Mutex<Vec<ThreadRecord>>> = LazyLock::new(|| Mutex::new(Vec::new()));
thread_local! {
static DEREGISTER: Deregister = const { Deregister };
}
struct Deregister;
impl Drop for Deregister {
fn drop(&mut self) {
let id = current_id();
REGISTRY.with_mut_sync(|registry| registry.retain(|record| record.id != id));
}
}
fn current_id() -> String {
format!("{:?}", std::thread::current().id())
}
pub(crate) fn register(note: Option<String>) {
let id = current_id();
let name = std::thread::current()
.name()
.map(str::to_string)
.unwrap_or_else(|| "<unnamed>".to_string());
let record = ThreadRecord { name, id, note };
REGISTRY.with_mut_sync(|registry| {
match registry
.iter_mut()
.find(|existing| existing.id == record.id)
{
Some(existing) => *existing = record,
None => registry.push(record),
}
});
DEREGISTER.with(|_| {});
}
pub(crate) fn registered() -> Vec<ThreadRecord> {
let mut records = REGISTRY.with_sync(|registry| registry.clone());
records.sort_by(|a, b| (&a.name, &a.id).cmp(&(&b.name, &b.id)));
records
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_registered_thread_appears_and_leaves_when_it_exits() {
let handle = std::thread::Builder::new()
.name("threads_test_worker".to_string())
.spawn(|| {
register(Some("does the test's work".to_string()));
let seen = registered();
assert!(
seen.iter()
.any(|record| record.name == "threads_test_worker"),
"{seen:?}"
);
})
.unwrap();
handle.join().unwrap();
let after = registered();
assert!(
!after
.iter()
.any(|record| record.name == "threads_test_worker"),
"a finished thread must not linger in the registry: {after:?}"
);
}
#[test]
fn registering_twice_replaces_rather_than_duplicates() {
std::thread::Builder::new()
.name("threads_test_twice".to_string())
.spawn(|| {
register(Some("first".to_string()));
register(Some("second".to_string()));
let mine: Vec<ThreadRecord> = registered()
.into_iter()
.filter(|record| record.name == "threads_test_twice")
.collect();
assert_eq!(mine.len(), 1, "{mine:?}");
assert_eq!(mine[0].note.as_deref(), Some("second"));
})
.unwrap()
.join()
.unwrap();
}
}