History

Fix for DependencyManager crash on shutdown on Mac

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.

Anthony Thibault committed 7 years ago

Commit 6c66f5a1 parent 043aee48 files · +51 −19Browse files

Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 8 files). Show all

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+24−2
@@ -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 content226 // 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 back343 // 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 thread411 // 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 running437 // 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 wait469 // 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 }