diff options
author | Christian Kamm <christian.d.kamm@nokia.com> | 2010-05-18 09:38:31 +0200 |
---|---|---|
committer | Christian Kamm <christian.d.kamm@nokia.com> | 2010-05-18 09:38:31 +0200 |
commit | e67705486cc18d44f13bbb20b863e245795fe7ee (patch) | |
tree | 241ed4e241cf3e468121c1e0fd11c39a30dcf00b | |
parent | 1ab9164ae7c782854a89f4a5021633c1c2c67aa5 (diff) |
Scripts: Use synchronized threads instead of coroutines.
This commit removes the Coroutine dependency and the need to specify
the DEPENDENCY_PATH qmake argument.
Coroutines were able to move WebKit's thread local storage into an
invalid state sometimes, leading to hard-to-reproduce crashes.
Threads don't have that problem. However, it means signals emitted from
ScriptInterfaces will now be queued. So their arguments need to be
registered with the Qt metatype system.
Task-number: QTSIM-22
Reviewed-by: owolff
-rw-r--r-- | library/components/locationui.cpp | 2 | ||||
-rw-r--r-- | library/remotecontrolwidget.pro | 18 | ||||
-rw-r--r-- | library/scriptadapter.cpp | 158 | ||||
-rw-r--r-- | library/scriptadapter.h | 36 |
4 files changed, 96 insertions, 118 deletions
diff --git a/library/components/locationui.cpp b/library/components/locationui.cpp index d658ab1..263aa06 100644 --- a/library/components/locationui.cpp +++ b/library/components/locationui.cpp @@ -46,6 +46,8 @@ LocationUi::LocationUi(QWidget *parent) , mTimeEdit(0) , mScriptInterface(0) { + qRegisterMetaType<LocationUi::LocationData>("LocationUi::LocationData"); + mScriptInterface = new LocationScriptInterface(this); QStringList tags; diff --git a/library/remotecontrolwidget.pro b/library/remotecontrolwidget.pro index 3174d14..ed18905 100644 --- a/library/remotecontrolwidget.pro +++ b/library/remotecontrolwidget.pro @@ -64,24 +64,6 @@ RESOURCES += \ style/style.qrc \ components/component.qrc - -# set dependency include and lib paths -!isEmpty(DEPENDENCY_PATH) { - INCLUDEPATH *= $$DEPENDENCY_PATH/include - !mac { - LIBS *= -L$$DEPENDENCY_PATH/lib - } else { - QMAKE_LFLAGS *= -F$$DEPENDENCY_PATH/lib - } - unix: isEmpty(INSTALLER) { - LIBS *= -Wl,-rpath,$$DEPENDENCY_PATH/lib - } -} - -# Coroutine library -qtAddLibrary(coroutine) - - # install rules isEmpty(PREFIX): PREFIX = $$OUT_PWD/install diff --git a/library/scriptadapter.cpp b/library/scriptadapter.cpp index a07aa5f..7885277 100644 --- a/library/scriptadapter.cpp +++ b/library/scriptadapter.cpp @@ -31,8 +31,6 @@ #include "optionsitem.h" -#include <coroutine/coroutine.h> - #include <QtCore/QMetaEnum> #include <QtCore/QCoreApplication> #include <QtCore/QTimer> @@ -50,7 +48,6 @@ ScriptAdapter::ScriptAdapter(QObject *parent) : QObject(parent) { mScriptRunner = new QTimer(this); - mScriptRunner->setSingleShot(true); mScriptRunner->setInterval(100); connect(mScriptRunner, SIGNAL(timeout()), this, SLOT(continueScripts())); mScriptRunner->start(); @@ -60,6 +57,16 @@ ScriptAdapter::ScriptAdapter(QObject *parent) ScriptAdapter::~ScriptAdapter() { + // stop active scripts + foreach (Script *script, mActiveScripts) { + if (!script->isFinished()) { + mCurrentScript = script; + script->requestTerminate(); + script->mWaitCondition.wakeOne(); + script->wait(10000); + } + } + delete mMessageBox; } @@ -99,6 +106,7 @@ Script *ScriptAdapter::run(const QString &filePath) QFileInfo fileInfo(file); Script *script = new Script(fileInfo.baseName(), file.readAll(), this); + script->start(); mActiveScripts += script; emit scriptStart(script); @@ -110,7 +118,7 @@ void ScriptAdapter::continueScripts() { for (int i = 0; i < mActiveScripts.length(); ++i) { Script *script = mActiveScripts[i]; - if (script->mCoroutine->status() == Coroutine::Terminated) { + if (script->isFinished()) { emit scriptStop(i); mActiveScripts.removeOne(script); @@ -120,125 +128,95 @@ void ScriptAdapter::continueScripts() { if (!script->isPaused() || script->isTerminating()) { mCurrentScript = script; - script->mCoroutine->cont(); + script->mSuspended = false; + script->mWaitCondition.wakeOne(); + while (!script->mSuspended && !script->isFinished()) + QThread::yieldCurrentThread(); mCurrentScript = 0; } - } - - mScriptRunner->start(); -} - -void ScriptAdapter::yield(int ms) -{ - if (!mCurrentScript) - return; - QTime timer; - timer.start(); - while (timer.elapsed() < ms && !mCurrentScript->isTerminating()) { - mCurrentScript->suspend(); + foreach (const MessageBoxData &message, mPendingMessages) { + mMessageBox->setText(message.text); + mMessageBox->setWindowTitle(message.title); + mMessageBox->exec(); + } + mPendingMessages.clear(); } } -void ScriptAdapter::messageBox(const QString &text, const QString &title) -{ - if (!mCurrentScript) - return; - - mMessageBox->setText(text); - mMessageBox->setWindowTitle(title); - - // message boxes should block - mCurrentScript->beginBlocking(); - mMessageBox->exec(); - mCurrentScript->endBlocking(); -} - -static QScriptValue safePrint(QScriptContext *context, QScriptEngine *engine) -{ - QString result; - for (int i = 0; i < context->argumentCount(); ++i) { - if (i != 0) - result.append(QLatin1Char(' ')); - QString s(context->argument(i).toString()); - if (engine->hasUncaughtException()) - break; - result.append(s); - } - if (engine->hasUncaughtException()) - return engine->uncaughtException(); - -#ifdef Q_OS_WIN - if (IsDebuggerPresent()) - qDebug("%s", qPrintable(result)); -#else - qDebug("%s", qPrintable(result)); -#endif - - return engine->undefinedValue(); -} Script::Script(const QString &name, const QString &scriptCode, ScriptAdapter *adapter) - : QObject(adapter) + : QThread(adapter) , mName(name) , mScriptCode(scriptCode) , mAdapter(adapter) + , mSuspended(false) , mPaused(false) , mTerminate(false) { - mCoroutine = Coroutine::build(this, &Script::run); - mCoroutine->createStack(524228); - - mStopTimer.setInterval(10); - connect(&mStopTimer, SIGNAL(timeout()), this, SLOT(suspend())); +} +void Script::setupScriptEngine() +{ // trigger event processing during script evaluation - mEngine.setProcessEventsInterval(10); + mEngine->setProcessEventsInterval(10); - // inject the ScriptAdapters functions into the global scope - QScriptValue adapterValue = mEngine.newQObject(adapter); + // inject the functions into the global scope + QScriptValue adapterValue = mEngine->newQObject(this); QStringList exposedFunctions = QStringList() << "yield" << "messageBox"; foreach (const QString &functionName, exposedFunctions) { QScriptValue functionValue = adapterValue.property(functionName); - mEngine.globalObject().setProperty(functionName, functionValue); + mEngine->globalObject().setProperty(functionName, functionValue); } - // override print, the default uses qDebug and that is not allowed in coroutines! - QScriptValue printFn = mEngine.newFunction(safePrint, 1); - mEngine.globalObject().setProperty("print", printFn); - + // setup script interfaces QHashIterator<QString, QObject *> iter(mAdapter->mScriptInterfaces); while (iter.hasNext()) { iter.next(); QStringList qualifiedName = iter.key().split(QLatin1Char('.')); - QScriptValue targetValue = mEngine.globalObject(); + QScriptValue targetValue = mEngine->globalObject(); for (int i = 0; i < qualifiedName.size() - 1; ++i) { QScriptValue v = targetValue.property(qualifiedName.at(i)); if (! v.isValid()) { - v = mEngine.newObject(); + v = mEngine->newObject(); targetValue.setProperty(qualifiedName.at(i), v); } targetValue = v; } - QScriptValue scriptInterface = mEngine.newQObject(iter.value()); + QScriptValue scriptInterface = mEngine->newQObject(iter.value()); targetValue.setProperty(qualifiedName.last(), scriptInterface); } } void Script::run() { - mStopTimer.start(); - QScriptValue value = mEngine.evaluate(mScriptCode); + mEngine = new QScriptEngine; + setupScriptEngine(); + + mStopTimer = new QTimer; + mStopTimer->setInterval(10); + mStopTimer->setSingleShot(true); + connect(mStopTimer, SIGNAL(timeout()), this, SLOT(suspend())); + + mMutex.lock(); + suspend(); + + QScriptValue value = mEngine->evaluate(mScriptCode); if (value.isError()) { - mAdapter->messageBox(value.toString(), tr("Script errors")); + messageBox(value.toString(), tr("Script errors")); } - mStopTimer.stop(); + + mMutex.unlock(); + + delete mStopTimer; + delete mEngine; } void Script::requestTerminate() { mTerminate = true; + mEngine->abortEvaluation(mEngine->currentContext()->throwError("Aborted")); } void Script::togglePause() @@ -248,22 +226,28 @@ void Script::togglePause() void Script::suspend() { - if (Coroutine::currentCoroutine() == mCoroutine) { - Coroutine::yield(); + if (mTerminate) + return; - if (mTerminate) { - mEngine.abortEvaluation(mEngine.currentContext()->throwError("Aborted")); - mStopTimer.stop(); - } - } + mSuspended = true; + mWaitCondition.wait(&mMutex); + mStopTimer->start(); } -void Script::beginBlocking() +void Script::yield(int ms) { - mStopTimer.stop(); + QTime timer; + timer.start(); + while (timer.elapsed() < ms && !isTerminating()) { + suspend(); + } } -void Script::endBlocking() +void Script::messageBox(const QString &text, const QString &title) { - mStopTimer.start(); + ScriptAdapter::MessageBoxData data; + data.text = text; + data.title = title; + mAdapter->mPendingMessages.append(data); + suspend(); } diff --git a/library/scriptadapter.h b/library/scriptadapter.h index c430d30..847280e 100644 --- a/library/scriptadapter.h +++ b/library/scriptadapter.h @@ -36,6 +36,9 @@ #include <QtCore/QTimer> #include <QtCore/QStringList> #include <QtCore/QHash> +#include <QtCore/QThread> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> #include <QtScript/QScriptEngine> class Script; @@ -48,9 +51,6 @@ public: explicit ScriptAdapter(QObject *parent = 0); virtual ~ScriptAdapter(); - Q_INVOKABLE void yield(int ms); - Q_INVOKABLE void messageBox(const QString &text, const QString &title = QString()); - void runAutostartScripts(); Script *script(int index); @@ -74,12 +74,16 @@ private: QMessageBox *mMessageBox; + struct MessageBoxData { + QString title; + QString text; + }; + QList<MessageBoxData> mPendingMessages; + friend class Script; }; -class Coroutine; - -class Script : public QObject +class Script : public QThread { Q_OBJECT public: @@ -97,25 +101,31 @@ public: bool isPaused() const { return mPaused; } - void beginBlocking(); - void endBlocking(); - public slots: void suspend(); +protected: + virtual void run(); + + Q_INVOKABLE void yield(int ms); + Q_INVOKABLE void messageBox(const QString &text, const QString &title = QString()); + private: - void run(); + void setupScriptEngine(); + + QWaitCondition mWaitCondition; + QMutex mMutex; QString mName; QString mScriptCode; ScriptAdapter *mAdapter; - Coroutine *mCoroutine; + bool mSuspended; bool mPaused; bool mTerminate; - QScriptEngine mEngine; - QTimer mStopTimer; + QScriptEngine *mEngine; + QTimer *mStopTimer; friend class ScriptAdapter; }; |