Merge pull request #7882 from zzmp/fix/script-thread-dtor

Wait and cleanly exit the scripting thread
This commit is contained in:
Chris Collins 2016-05-16 16:52:54 -07:00
commit b2ece6f4a7
5 changed files with 126 additions and 107 deletions

View file

@ -13,6 +13,7 @@
#include <QEventLoop> #include <QEventLoop>
#include <QScriptSyntaxCheckResult> #include <QScriptSyntaxCheckResult>
#include <QThreadPool>
#include <ColorUtils.h> #include <ColorUtils.h>
#include <AbstractScriptingServicesInterface.h> #include <AbstractScriptingServicesInterface.h>
@ -77,9 +78,30 @@ EntityTreeRenderer::~EntityTreeRenderer() {
int EntityTreeRenderer::_entitiesScriptEngineCount = 0; int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
void EntityTreeRenderer::setupEntitiesScriptEngine() { void entitiesScriptEngineDeleter(ScriptEngine* engine) {
QSharedPointer<ScriptEngine> oldEngine = _entitiesScriptEngine; // save the old engine through this function, so the EntityScriptingInterface doesn't have problems with it. class WaitRunnable : public QRunnable {
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)), &QObject::deleteLater); public:
WaitRunnable(ScriptEngine* engine) : _engine(engine) {}
virtual void run() override {
_engine->waitTillDoneRunning();
_engine->deleteLater();
}
private:
ScriptEngine* _engine;
};
// Wait for the scripting thread from the thread pool to avoid hanging the main thread
QThreadPool::globalInstance()->start(new WaitRunnable(engine));
}
void EntityTreeRenderer::resetEntitiesScriptEngine() {
// Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use
auto oldEngine = _entitiesScriptEngine;
auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(newEngine, entitiesScriptEngineDeleter);
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data()); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
_entitiesScriptEngine->runInThread(); _entitiesScriptEngine->runInThread();
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine.data()); DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine.data());
@ -87,16 +109,16 @@ void EntityTreeRenderer::setupEntitiesScriptEngine() {
void EntityTreeRenderer::clear() { void EntityTreeRenderer::clear() {
leaveAllEntities(); leaveAllEntities();
if (_entitiesScriptEngine) { if (_entitiesScriptEngine) {
// Unload and stop the engine here (instead of in its deleter) to
// avoid marshalling unload signals back to this thread
_entitiesScriptEngine->unloadAllEntityScripts(); _entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop(); _entitiesScriptEngine->stop();
} }
if (_wantScripts && !_shuttingDown) { if (_wantScripts && !_shuttingDown) {
// NOTE: you can't actually need to delete it here because when we call setupEntitiesScriptEngine it will resetEntitiesScriptEngine();
// assign a new instance to our shared pointer, which will deref the old instance and ultimately call
// the custom deleter which calls deleteLater
setupEntitiesScriptEngine();
} }
auto scene = _viewState->getMain3DScene(); auto scene = _viewState->getMain3DScene();
@ -125,7 +147,7 @@ void EntityTreeRenderer::init() {
entityTree->setFBXService(this); entityTree->setFBXService(this);
if (_wantScripts) { if (_wantScripts) {
setupEntitiesScriptEngine(); resetEntitiesScriptEngine();
} }
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities

View file

@ -126,7 +126,7 @@ protected:
} }
private: private:
void setupEntitiesScriptEngine(); void resetEntitiesScriptEngine();
void addEntityToScene(EntityItemPointer entity); void addEntityToScene(EntityItemPointer entity);
bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar); bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar);

View file

@ -136,10 +136,9 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
return false; return false;
} }
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) : ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString) :
_scriptContents(scriptContents), _scriptContents(scriptContents),
_timerFunctionMap(), _timerFunctionMap(),
_wantSignals(wantSignals),
_fileNameString(fileNameString), _fileNameString(fileNameString),
_arrayBufferClass(new ArrayBufferClass(this)) _arrayBufferClass(new ArrayBufferClass(this))
{ {
@ -153,7 +152,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
} }
ScriptEngine::~ScriptEngine() { ScriptEngine::~ScriptEngine() {
qCDebug(scriptengine) << "Script Engine shutting down (destructor) for script:" << getFilename(); qCDebug(scriptengine) << "Script Engine shutting down:" << getFilename();
auto scriptEngines = DependencyManager::get<ScriptEngines>(); auto scriptEngines = DependencyManager::get<ScriptEngines>();
if (scriptEngines) { if (scriptEngines) {
@ -161,16 +160,15 @@ ScriptEngine::~ScriptEngine() {
} else { } else {
qCWarning(scriptengine) << "Script destroyed after ScriptEngines!"; qCWarning(scriptengine) << "Script destroyed after ScriptEngines!";
} }
waitTillDoneRunning();
} }
void ScriptEngine::disconnectNonEssentialSignals() { void ScriptEngine::disconnectNonEssentialSignals() {
disconnect(); disconnect();
QThread* receiver; QThread* workerThread;
// Ensure the thread should be running, and does exist // Ensure the thread should be running, and does exist
if (_isRunning && _isThreaded && (receiver = thread())) { if (_isRunning && _isThreaded && (workerThread = thread())) {
connect(this, &ScriptEngine::doneRunning, receiver, &QThread::quit); connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
} }
} }
@ -229,15 +227,14 @@ void ScriptEngine::runDebuggable() {
return; return;
} }
stopAllTimers(); // make sure all our timers are stopped if the script is ending stopAllTimers(); // make sure all our timers are stopped if the script is ending
if (_wantSignals) {
emit scriptEnding(); emit scriptEnding();
emit finished(_fileNameString, this); emit finished(_fileNameString, this);
}
_isRunning = false; _isRunning = false;
if (_wantSignals) {
emit runningStateChanged(); emit runningStateChanged();
emit doneRunning(); emit doneRunning();
}
timer->deleteLater(); timer->deleteLater();
return; return;
} }
@ -247,9 +244,7 @@ void ScriptEngine::runDebuggable() {
if (_lastUpdate < now) { if (_lastUpdate < now) {
float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND;
if (!_isFinished) { if (!_isFinished) {
if (_wantSignals) { emit update(deltaTime);
emit update(deltaTime);
}
} }
} }
_lastUpdate = now; _lastUpdate = now;
@ -270,57 +265,72 @@ void ScriptEngine::runInThread() {
} }
_isThreaded = true; _isThreaded = true;
QThread* workerThread = new QThread();
QString scriptEngineName = QString("Script Thread:") + getFilename();
workerThread->setObjectName(scriptEngineName);
// The thread interface cannot live on itself, and we want to move this into the thread, so
// the thread cannot have this as a parent.
QThread* workerThread = new QThread();
workerThread->setObjectName(QString("Script Thread:") + getFilename());
moveToThread(workerThread);
// NOTE: If you connect any essential signals for proper shutdown or cleanup of // NOTE: If you connect any essential signals for proper shutdown or cleanup of
// the script engine, make sure to add code to "reconnect" them to the // the script engine, make sure to add code to "reconnect" them to the
// disconnectNonEssentialSignals() method // disconnectNonEssentialSignals() method
// when the worker thread is started, call our engine's run..
connect(workerThread, &QThread::started, this, &ScriptEngine::run); connect(workerThread, &QThread::started, this, &ScriptEngine::run);
// tell the thread to stop when the script engine is done
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
moveToThread(workerThread);
// Starts an event loop, and emits workerThread->started()
workerThread->start(); workerThread->start();
} }
void ScriptEngine::waitTillDoneRunning() { void ScriptEngine::waitTillDoneRunning() {
// If the script never started running or finished running before we got here, we don't need to wait for it
auto workerThread = thread(); auto workerThread = thread();
if (_isThreaded && workerThread) {
QString scriptName = getFilename();
auto startedWaiting = usecTimestampNow();
if (_isThreaded && workerThread) {
// We should never be waiting (blocking) on our own thread
assert(workerThread != QThread::currentThread());
// Engine should be stopped already, but be defensive
stop();
auto startedWaiting = usecTimestampNow();
while (workerThread->isRunning()) { while (workerThread->isRunning()) {
// NOTE: This will be called on the main application thread from stopAllScripts. // NOTE: This will be called on the main application thread from stopAllScripts.
// The application thread will need to continue to process events, because // The application thread will need to continue to process events, because
// the scripts will likely need to marshall messages across to the main thread, e.g. // the scripts will likely need to marshall messages across to the main thread, e.g.
// if they access Settings or Menu in any of their shutdown code. So: // if they access Settings or Menu in any of their shutdown code. So:
// Process events for the main application thread, allowing invokeMethod calls to pass between threads. // Process events for the main application thread, allowing invokeMethod calls to pass between threads.
QCoreApplication::processEvents(); // thread-safe :) QCoreApplication::processEvents();
// If we've been waiting a second or more, then tell the script engine to stop evaluating // If the final evaluation takes too long, then tell the script engine to stop running
static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND;
auto elapsedUsecs = usecTimestampNow() - startedWaiting; auto elapsedUsecs = usecTimestampNow() - startedWaiting;
static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND;
if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) {
qCDebug(scriptengine) <<
"Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] quitting.";
abortEvaluation(); // to allow the thread to quit
workerThread->quit(); workerThread->quit();
break;
if (isEvaluating()) {
qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename();
abortEvaluation();
} else {
qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename();
auto context = currentContext();
if (context) {
context->throwError("Timed out during shutdown");
}
}
// Wait for the scripting thread to stop running, as
// flooding it with aborts/exceptions will persist it longer
static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND;
if (workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {
workerThread->terminate();
}
} }
// Avoid a pure busy wait // Avoid a pure busy wait
QThread::yieldCurrentThread(); QThread::yieldCurrentThread();
} }
workerThread->deleteLater(); qCDebug(scriptengine) << "Script Engine has stopped:" << getFilename();
} }
} }
@ -356,17 +366,13 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip
if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) {
_debuggable = true; _debuggable = true;
} }
if (_wantSignals) { emit scriptLoaded(url.toString());
emit scriptLoaded(url.toString());
}
} }
// FIXME - switch this to the new model of ScriptCache callbacks // FIXME - switch this to the new model of ScriptCache callbacks
void ScriptEngine::errorInLoadingScript(const QUrl& url) { void ScriptEngine::errorInLoadingScript(const QUrl& url) {
qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__; qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
if (_wantSignals) { emit errorLoadingScript(_fileNameString); // ??
emit errorLoadingScript(_fileNameString); // ??
}
} }
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
@ -783,9 +789,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
--_evaluatesPending; --_evaluatesPending;
const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName()); const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName());
if (_wantSignals) { emit evaluationFinished(result, hadUncaughtException);
emit evaluationFinished(result, hadUncaughtException);
}
return result; return result;
} }
@ -799,9 +803,7 @@ void ScriptEngine::run() {
} }
_isRunning = true; _isRunning = true;
if (_wantSignals) { emit runningStateChanged();
emit runningStateChanged();
}
QScriptValue result = evaluate(_scriptContents, _fileNameString); QScriptValue result = evaluate(_scriptContents, _fileNameString);
@ -870,9 +872,7 @@ void ScriptEngine::run() {
if (_lastUpdate < now) { if (_lastUpdate < now) {
float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND;
if (!_isFinished) { if (!_isFinished) {
if (_wantSignals) { emit update(deltaTime);
emit update(deltaTime);
}
} }
} }
_lastUpdate = now; _lastUpdate = now;
@ -881,10 +881,10 @@ void ScriptEngine::run() {
hadUncaughtExceptions(*this, _fileNameString); hadUncaughtExceptions(*this, _fileNameString);
} }
qCDebug(scriptengine) << "Script Engine stopping:" << getFilename();
stopAllTimers(); // make sure all our timers are stopped if the script is ending stopAllTimers(); // make sure all our timers are stopped if the script is ending
if (_wantSignals) { emit scriptEnding();
emit scriptEnding();
}
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) { if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
// release the queue of edit entity messages. // release the queue of edit entity messages.
@ -902,15 +902,11 @@ void ScriptEngine::run() {
} }
} }
if (_wantSignals) { emit finished(_fileNameString, this);
emit finished(_fileNameString, this);
}
_isRunning = false; _isRunning = false;
if (_wantSignals) { emit runningStateChanged();
emit runningStateChanged(); emit doneRunning();
emit doneRunning();
}
} }
// NOTE: This is private because it must be called on the same thread that created the timers, which is why // NOTE: This is private because it must be called on the same thread that created the timers, which is why
@ -943,14 +939,8 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) {
void ScriptEngine::stop() { void ScriptEngine::stop() {
if (!_isFinished) { if (!_isFinished) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stop");
return;
}
_isFinished = true; _isFinished = true;
if (_wantSignals) { emit runningStateChanged();
emit runningStateChanged();
}
} }
} }
@ -1074,9 +1064,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
} }
void ScriptEngine::print(const QString& message) { void ScriptEngine::print(const QString& message) {
if (_wantSignals) { emit printedMessage(message);
emit printedMessage(message);
}
} }
// If a callback is specified, the included files will be loaded asynchronously and the callback will be called // If a callback is specified, the included files will be loaded asynchronously and the callback will be called
@ -1212,13 +1200,9 @@ void ScriptEngine::load(const QString& loadFile) {
if (_isReloading) { if (_isReloading) {
auto scriptCache = DependencyManager::get<ScriptCache>(); auto scriptCache = DependencyManager::get<ScriptCache>();
scriptCache->deleteScript(url.toString()); scriptCache->deleteScript(url.toString());
if (_wantSignals) { emit reloadScript(url.toString(), false);
emit reloadScript(url.toString(), false);
}
} else { } else {
if (_wantSignals) { emit loadScript(url.toString(), false);
emit loadScript(url.toString(), false);
}
} }
} }
@ -1307,8 +1291,23 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
setParentURL(scriptOrURL); setParentURL(scriptOrURL);
} }
const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND;
QScriptEngine sandbox; QScriptEngine sandbox;
QScriptValue testConstructor = sandbox.evaluate(program); sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT);
QScriptValue testConstructor;
{
QTimer timeout;
timeout.setSingleShot(true);
timeout.start(SANDBOX_TIMEOUT);
connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT]{
auto context = sandbox.currentContext();
if (context) {
// Guard against infinite loops and non-performant code
context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT));
}
});
testConstructor = sandbox.evaluate(program);
}
if (hadUncaughtExceptions(sandbox, program.fileName())) { if (hadUncaughtExceptions(sandbox, program.fileName())) {
return; return;
} }

View file

@ -67,10 +67,7 @@ public:
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
Q_OBJECT Q_OBJECT
public: public:
ScriptEngine(const QString& scriptContents = NO_SCRIPT, ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
const QString& fileNameString = QString(""),
bool wantSignals = true);
~ScriptEngine(); ~ScriptEngine();
/// run the script in a dedicated thread. This will have the side effect of evalulating /// run the script in a dedicated thread. This will have the side effect of evalulating
@ -83,10 +80,15 @@ public:
/// run the script in the callers thread, exit when stop() is called. /// run the script in the callers thread, exit when stop() is called.
void run(); void run();
void waitTillDoneRunning();
QString getFilename() const; QString getFilename() const;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts
Q_INVOKABLE void stop();
// Stop any evaluating scripts and wait for the scripting thread to finish.
void waitTillDoneRunning();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
// properly ensure they are only called on the correct thread // properly ensure they are only called on the correct thread
@ -142,10 +144,6 @@ public:
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts
Q_INVOKABLE void stop();
bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget
bool isRunning() const { return _isRunning; } // used by ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget
@ -195,7 +193,6 @@ protected:
bool _isInitialized { false }; bool _isInitialized { false };
QHash<QTimer*, CallbackData> _timerFunctionMap; QHash<QTimer*, CallbackData> _timerFunctionMap;
QSet<QUrl> _includedURLs; QSet<QUrl> _includedURLs;
bool _wantSignals { true };
QHash<EntityItemID, EntityScriptDetails> _entityScripts; QHash<EntityItemID, EntityScriptDetails> _entityScripts;
bool _isThreaded { false }; bool _isThreaded { false };
QScriptEngineDebugger* _debugger { nullptr }; QScriptEngineDebugger* _debugger { nullptr };
@ -203,6 +200,7 @@ protected:
qint64 _lastUpdate; qint64 _lastUpdate;
void init(); void init();
bool evaluatePending() const { return _evaluatesPending > 0; } bool evaluatePending() const { return _evaluatesPending > 0; }
void timerFired(); void timerFired();
void stopAllTimers(); void stopAllTimers();

View file

@ -150,6 +150,7 @@ void ScriptEngines::shutdownScripting() {
// NOTE: typically all script engines are running. But there's at least one known exception to this, the // NOTE: typically all script engines are running. But there's at least one known exception to this, the
// "entities sandbox" which is only used to evaluate entities scripts to test their validity before using // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
// them. We don't need to stop scripts that aren't running. // them. We don't need to stop scripts that aren't running.
// TODO: Scripts could be shut down faster if we spread them across a threadpool.
if (scriptEngine->isRunning()) { if (scriptEngine->isRunning()) {
qCDebug(scriptengine) << "about to shutdown script:" << scriptName; qCDebug(scriptengine) << "about to shutdown script:" << scriptName;
@ -158,8 +159,7 @@ void ScriptEngines::shutdownScripting() {
// and stop. We can safely short circuit this because we know we're in the "quitting" process // and stop. We can safely short circuit this because we know we're in the "quitting" process
scriptEngine->disconnect(this); scriptEngine->disconnect(this);
// Calling stop on the script engine will set it's internal _isFinished state to true, and result // Gracefully stop the engine's scripting thread
// in the ScriptEngine gracefully ending it's run() method.
scriptEngine->stop(); scriptEngine->stop();
// We need to wait for the engine to be done running before we proceed, because we don't // We need to wait for the engine to be done running before we proceed, because we don't
@ -171,7 +171,7 @@ void ScriptEngines::shutdownScripting() {
scriptEngine->deleteLater(); scriptEngine->deleteLater();
// If the script is stopped, we can remove it from our set // Once the script is stopped, we can remove it from our set
i.remove(); i.remove();
} }
} }
@ -427,7 +427,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
return scriptEngine; return scriptEngine;
} }
scriptEngine = new ScriptEngine(NO_SCRIPT, "", true); scriptEngine = new ScriptEngine(NO_SCRIPT, "");
scriptEngine->setUserLoaded(isUserLoaded); scriptEngine->setUserLoaded(isUserLoaded);
connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] { connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] {
scriptEngine->deleteLater(); scriptEngine->deleteLater();