#1696·JUCE

[错误]:当主机卸载由 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() 本身打开的:

  1. DeletedAtShutdown::deleteAll() 销毁 ShutdownDetector,其析构函数调用 TimerThread::applicationShuttingDown()stopThreadAsync()。这仅发出信号给线程(signalThreadShouldExit() + callbackArrived.signal());它不会加入线程。加入操作位于 ~TimerThread 中,该操作仅在最后一个 SharedResourcePointer<TimerThread> (每个 Timer 对象中持有)被释放时运行 - 也就是说,只有在关闭前每个 Timer 都被销毁时。~TimerThread 的自己的 jassert 确认定时器可以在事件系统结束后继续存在。
  2. MessageManager::deleteInstance() 然后立即运行。~MessageManager 调用 doPlatformSpecificShutdown() (销毁平台 MessageQueue) 之前 置为空 instance ("最后做这件事…"),因此在析构函数的整个期间,MessageBase::post()MessageManager::instance 的空检查仍然通过。
  3. 与此同时,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