[错误]:当主机卸载由 JUCE 构建的插件时,TimerThread 可以向正在由 shutdownJuce_GUI 破坏的 MessageManager 发送消息 — SIGSEGV
作者: jdf创建于 2026年8月6日更新于 2026年8月6日
详细步骤说明如何重现此错误 当 shutdownJuce_GUI() 在 Timer::TimerThread 仍然存在时运行时,定时器线程可以调用 MessageBase::post() 对一个同时正在被销毁的 MessageManager,然后在 MessageQueue::post() 中崩溃,并发出 SIGSEGV。窗口是由 shutdownJuce_GUI() 本身打开的:
DeletedAtShutdown::deleteAll()销毁ShutdownDetector,其析构函数调用TimerThread::applicationShuttingDown()→stopThreadAsync()。这仅发出信号给线程(signalThreadShouldExit()+callbackArrived.signal());它不会加入线程。加入操作位于~TimerThread中,该操作仅在最后一个SharedResourcePointer<TimerThread>(每个Timer对象中持有)被释放时运行 - 也就是说,只有在关闭前每个Timer都被销毁时。~TimerThread的自己的jassert确认定时器可以在事件系统结束后继续存在。MessageManager::deleteInstance()然后立即运行。~MessageManager调用doPlatformSpecificShutdown()(销毁平台MessageQueue) 之前 置为空instance("最后做这件事…"),因此在析构函数的整个期间,MessageBase::post()对MessageManager::instance的空检查仍然通过。- 与此同时,
TimerThread::run()可能已经通过了callbackArrived.wait(...)检查,并已提交到messageToSend->post()。有两个位置,我们已经在两个位置捕获了崩溃:
juce_Timer.cpp:128(第一次发送):线程在关闭信号到达之前通过了callbackArrived.wait(0)检查。juce_Timer.cpp:135(300 毫秒的"消息可能丢失,再次发送"重试):这个位置在主机关闭期间很容易在插件中触发,因为包装器的shutdownJuce_GUI()在主机的消息线程上运行,而主机不再服务插件的队列 - 发送的CallTimersMessage从未运行,callTimers()从未发出callbackArrived信号,因此定时器线程可靠地停留在 300 毫秒的超时中,然后再次发送到清除窗口中。post()读取MessageManager::instance,对其进行空检查,然后调用postMessageToSystemQueue()- 这是在另一个线程上调用deleteInstance()的时间检查/使用时间竞争,该窗口通过空检查的最后执行顺序将~MessageManager的持续时间扩展到整个时间。
内容来源: juce-framework/JUCE