On Mac, it is possible to crash when shutting down, it is not clear if this is due to shutting down the app on another thread during logout or something that can happen during normal shutdown, because it is so difficult to reproduce. However, from looking at the stack traces it is possible for a [NSApplication terminate:] event to get processed while Appliction::aboutToQuit() is calling ScriptEngine::waitTillDoneRunning() This causes AppKit to invoke the static destructors too early. Which in turn, causes the DependencyManager destructor to fire while there are still many dependencies running. Unfortunatly, the order of destruction is not determinstic, causing them to get shutdown in an incorrect order. To workaround this, we delay the call to QCoreApplication::processEvents() as late as possible, in the Application destructor. Theoretically, this will be a safe time for the static destructors to be invoked, because it is after all of the DependencyManager's dependencies have been manually destroyed. However, this is only a speculative fix, because this is so difficult to reproduce.
Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 8 files). Show all
Unified · Split
| @@ -224,7 +224,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const · 223 unmodified lines | |||
| 224 | if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) { | 224 | if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) { |
| 225 | QObject::connect(this, &ScriptEngine::update, this, [this]() { | 225 | QObject::connect(this, &ScriptEngine::update, this, [this]() { |
| 226 | // process pending entity script content | 226 | // process pending entity script content |
| 227 | if (!_contentAvailableQueue.empty()) { | 227 | if (!_contentAvailableQueue.empty() && !(_isFinished || _isStopping)) { |
| 228 | EntityScriptContentAvailableMap pending; | 228 | EntityScriptContentAvailableMap pending; |
| 229 | std::swap(_contentAvailableQueue, pending); | 229 | std::swap(_contentAvailableQueue, pending); |
| 230 | for (auto& pair : pending) { | 230 | for (auto& pair : pending) { |
| @@ -343,7 +343,7 @@ void ScriptEngine::runDebuggable() { · 112 unmodified lines | |||
| 343 | // we check for 'now' in the past in case people set their clock back | 343 | // we check for 'now' in the past in case people set their clock back |
| 344 | if (_lastUpdate < now) { | 344 | if (_lastUpdate < now) { |
| 345 | float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; | 345 | float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; |
| 346 | if (!_isFinished) { | 346 | if (!(_isFinished || _isStopping)) { |
| 347 | emit update(deltaTime); | 347 | emit update(deltaTime); |
| 348 | } | 348 | } |
| 349 | } | 349 | } |
| @@ -411,6 +411,27 @@ void ScriptEngine::waitTillDoneRunning() { · 61 unmodified lines | |||
| 411 | // We should never be waiting (blocking) on our own thread | 411 | // We should never be waiting (blocking) on our own thread |
| 412 | assert(workerThread != QThread::currentThread()); | 412 | assert(workerThread != QThread::currentThread()); |
| 413 | 413 | ||
| 414 | #ifdef Q_OS_MAC | ||
| 415 | // On mac, don't call QCoreApplication::processEvents() here. This is to prevent | ||
| 416 | // [NSApplication terminate:] from prematurely destroying the static destructors | ||
| 417 | // while we are waiting for the scripts to shutdown. We will pump the message | ||
| 418 | // queue later in the Application destructor. | ||
| 419 | if (workerThread->isRunning()) { | ||
| 420 | workerThread->quit(); | ||
| 421 | |||
| 422 | if (isEvaluating()) { | ||
| 423 | qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename(); | ||
| 424 | abortEvaluation(); | ||
| 425 | } | ||
| 426 | |||
| 427 | // Wait for the scripting thread to stop running, as | ||
| 428 | // flooding it with aborts/exceptions will persist it longer | ||
| 429 | static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; | ||
| 430 | if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { | ||
| 431 | workerThread->terminate(); | ||
| 432 | } | ||
| 433 | } | ||
| 434 | #else | ||
| 414 | auto startedWaiting = usecTimestampNow(); | 435 | auto startedWaiting = usecTimestampNow(); |
| 415 | while (workerThread->isRunning()) { | 436 | while (workerThread->isRunning()) { |
| 416 | // If the final evaluation takes too long, then tell the script engine to stop running | 437 | // If the final evaluation takes too long, then tell the script engine to stop running |
| @@ -448,6 +469,7 @@ void ScriptEngine::waitTillDoneRunning() { · 31 unmodified lines | |||
| 448 | // Avoid a pure busy wait | 469 | // Avoid a pure busy wait |
| 449 | QThread::yieldCurrentThread(); | 470 | QThread::yieldCurrentThread(); |
| 450 | } | 471 | } |
| 472 | #endif | ||
| 451 | 473 | ||
| 452 | scriptInfoMessage("Script Engine has stopped:" + getFilename()); | 474 | scriptInfoMessage("Script Engine has stopped:" + getFilename()); |
| 453 | } | 475 | } |