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
224224 if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) {
225225 QObject::connect(this, &ScriptEngine::update, this, [this]() {
226226 // process pending entity script content
227− if (!_contentAvailableQueue.empty()) {
227+ if (!_contentAvailableQueue.empty() && !(_isFinished || _isStopping)) {
228228 EntityScriptContentAvailableMap pending;
229229 std::swap(_contentAvailableQueue, pending);
230230 for (auto& pair : pending) {
@@ -343,7 +343,7 @@ void ScriptEngine::runDebuggable() { · 112 unmodified lines
343343 // we check for 'now' in the past in case people set their clock back
344344 if (_lastUpdate < now) {
345345 float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND;
346− if (!_isFinished) {
346+ if (!(_isFinished || _isStopping)) {
347347 emit update(deltaTime);
348348 }
349349 }
@@ -411,6 +411,27 @@ void ScriptEngine::waitTillDoneRunning() { · 61 unmodified lines
411411 // We should never be waiting (blocking) on our own thread
412412 assert(workerThread != QThread::currentThread());
413413
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
414435 auto startedWaiting = usecTimestampNow();
415436 while (workerThread->isRunning()) {
416437 // 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
448469 // Avoid a pure busy wait
449470 QThread::yieldCurrentThread();
450471 }
472+#endif
451473
452474 scriptInfoMessage("Script Engine has stopped:" + getFilename());
453475 }