From 6f52631a2ad8f2715cd86100bf4b2377f54c5ed5 Mon Sep 17 00:00:00 2001 From: Kevin Zakka Date: Mon, 2 Mar 2026 12:21:34 -0800 Subject: [PATCH 1/2] Fix process hang on viewer close in launch_passive WaitForAtomicNoGil spins until the render loop consumes a pending set_texts/set_figures/set_images request (atomic == expected). If the user closes the viewer window after a request is submitted but before the render loop consumes it, the render loop exits and the atomic is never updated. The calling thread spins forever, preventing the process from terminating. Check exitrequest inside the spin loop so the wait bails out once the viewer is shutting down. --- python/mujoco/simulate.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/mujoco/simulate.cc b/python/mujoco/simulate.cc index 22209510..bf412381 100644 --- a/python/mujoco/simulate.cc +++ b/python/mujoco/simulate.cc @@ -247,6 +247,11 @@ class SimulateWrapper { if (simulate_) { py::gil_scoped_release no_gil; while (atomic.load() != expected) { + // If the viewer is exiting, stop waiting. The render loop will + // never consume the pending request once it has set exitrequest. + if (simulate_->exitrequest.load()) { + return true; + } // TODO(robotics-simulation): replace with `atomic.wait(expected)` when // we migrate python bindings to C++20 (we may need to drop GCC 10). std::this_thread::yield(); From 327f05a567f5dc9c14b27ce8b32ac7610d4d90d6 Mon Sep 17 00:00:00 2001 From: Kevin Zakka Date: Mon, 2 Mar 2026 12:48:38 -0800 Subject: [PATCH 2/2] Fix macOS crash on viewer close (NSScreen main-thread assertion) Hiding the dock icon after the viewer window closes triggers an asynchronous screen/dock reconfiguration via SkyLight. Without draining the main run loop, the resulting NSScreen invalidation notification is dispatched to a GCD worker thread, which hits NSScreen's main-thread assertion and crashes the process. Drain the main NSRunLoop briefly after setActivationPolicy: so the reconfiguration is processed on the main thread. --- python/mujoco/mjpython/mjpython.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/mujoco/mjpython/mjpython.mm b/python/mujoco/mjpython/mjpython.mm index db857d04..12f1a6bd 100644 --- a/python/mujoco/mjpython/mjpython.mm +++ b/python/mujoco/mjpython/mjpython.mm @@ -36,6 +36,13 @@ extern char **environ; // for execve // so that we can dlsym and call them from Python via ctypes. __attribute__((used)) void mjpython_hide_dock_icon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; + + // Drain pending Cocoa events on the main thread. The activation policy + // change triggers an asynchronous dock/screen reconfiguration via + // SkyLight. If we return before it's processed, the notification lands + // on a GCD worker thread and hits NSScreen's main-thread assertion. + NSDate* soon = [NSDate dateWithTimeIntervalSinceNow:0.01]; + [[NSRunLoop mainRunLoop] runUntilDate:soon]; } __attribute__((used)) void mjpython_show_dock_icon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];