atagen

monolith

public
Star0
Watch0

Commit d2817bc6c3eb

tarball

atagen <boss@atagen.co>2026-10-19 06:13 UTC

1 file changed+15-1
import: join the worker before exiting

The buffer-import worker could drop the last Arc<Shared> after main returned, calling vkDestroyDevice concurrently with NVIDIA's exit handlers. glibc then aborted in pthread_cond_destroy with free(): invalid size on most nested quits.
Modified file: crates/monolith/src/import.rs+15 additions−1 deletions
@@ -27,6 +27,7 @@ const OFFLOAD_BYTES: i64 = 512 * 1024;
2727
2828 pub struct Imports {
2929 jobs: mpsc::Sender<Job>,
30+ worker: Option<std::thread::JoinHandle<()>>,
3031 dmabufs: HashMap<WeakDmabuf, Waiters>,
3132 shm: HashMap<ObjectId, Waiters>,
3233 }
@@ -50,6 +51,18 @@ impl Blocker for ImportBlocker {
5051 }
5152 }
5253
54+impl Drop for Imports {
55+ // The worker may hold the last reference to the Vulkan device. Left
56+ // running, it destroys the device while NVIDIA's exit handlers tear down
57+ // the same driver state on the main thread, and glibc aborts.
58+ fn drop(&mut self) {
59+ self.jobs = mpsc::channel().0;
60+ if let Some(worker) = self.worker.take() {
61+ let _ = worker.join();
62+ }
63+ }
64+}
65+
5366 enum Job {
5467 Dmabuf(Dmabuf),
5568 Shm(WlBuffer, Option<Rectangle<i32, Buffer>>),
@@ -68,7 +81,7 @@ impl Imports {
6881 ) -> std::io::Result<Self> {
6982 let (done, results) = channel::<Done>();
7083 let (jobs, queue) = mpsc::channel::<Job>();
71- std::thread::Builder::new()
84+ let worker = std::thread::Builder::new()
7285 .name("buffer-import".into())
7386 .spawn(move || {
7487 for job in queue {
@@ -96,6 +109,7 @@ impl Imports {
96109 .map_err(|e| std::io::Error::other(e.to_string()))?;
97110 Ok(Self {
98111 jobs,
112+ worker: Some(worker),
99113 dmabufs: HashMap::new(),
100114 shm: HashMap::new(),
101115 })