From 4036cd37b623600c5f3b51791caff9e4c0850111 Mon Sep 17 00:00:00 2001 From: Martyn Gigg Date: Sun, 3 Sep 2017 21:20:15 +0100 Subject: [PATCH] Add pre/post setup for recursive async calls --- MantidPlot/src/ApplicationWindow.cpp | 25 ++++-- MantidPlot/src/PythonScript.cpp | 128 +++++---------------------- MantidPlot/src/PythonScript.h | 18 ---- MantidPlot/src/Script.cpp | 11 --- MantidPlot/src/Script.h | 11 ++- 5 files changed, 41 insertions(+), 152 deletions(-) diff --git a/MantidPlot/src/ApplicationWindow.cpp b/MantidPlot/src/ApplicationWindow.cpp index 765cc32e6876..20d5eac82f27 100644 --- a/MantidPlot/src/ApplicationWindow.cpp +++ b/MantidPlot/src/ApplicationWindow.cpp @@ -573,8 +573,8 @@ void ApplicationWindow::init(bool factorySettings, const QStringList &args) { // Scripting m_script_envs = QHash(); + m_iface_script = nullptr; setScriptingLanguage(defaultScriptingLang); - m_iface_script = NULL; m_interpreterDock = new QDockWidget(this); m_interpreterDock->setObjectName( @@ -15043,10 +15043,9 @@ bool ApplicationWindow::runPythonScript(const QString &code, bool async, bool quiet, bool redirect) { if (code.isEmpty()) return false; - - if (m_iface_script == NULL) { + if (!m_iface_script) { if (setScriptingLanguage("Python")) { - m_iface_script = scriptingEnv()->newScript("", NULL, + m_iface_script = scriptingEnv()->newScript("", nullptr, Script::NonInteractive); } else { return false; @@ -15065,12 +15064,20 @@ bool ApplicationWindow::runPythonScript(const QString &code, bool async, } bool success(false); if (async) { - QFuture job = m_iface_script->executeAsync(ScriptCode(code)); - while (job.isRunning()) { - QCoreApplication::instance()->processEvents(); + m_iface_script->recursiveAsyncSetup(); + auto job = m_iface_script->executeAsync(ScriptCode(code)); + // Start a local event loop to keep processing events + // while we are running the script. Inspired by the IPython + // Qt inputhook in IPython.terminal.pt_inputhooks.qt + QEventLoop eventLoop(QApplication::instance()); + QTimer timer; + connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); + while (!job.isFinished()) { + timer.start(50); + eventLoop.exec(); + timer.stop(); } - // Ensure the remaining events are processed - QCoreApplication::instance()->processEvents(); + m_iface_script->recursiveAsyncTeardown(); success = job.result(); } else { success = m_iface_script->execute(ScriptCode(code)); diff --git a/MantidPlot/src/PythonScript.cpp b/MantidPlot/src/PythonScript.cpp index 78438e29ed58..c0b617a2e0a0 100644 --- a/MantidPlot/src/PythonScript.cpp +++ b/MantidPlot/src/PythonScript.cpp @@ -454,6 +454,26 @@ void PythonScript::endStdoutRedirect() { Py_XDECREF(stderrSave); } +/** + * To be called from the main thread before an async call that is + * recursive. See ApplicationWindow::runPythonScript + */ +void PythonScript::recursiveAsyncSetup() { + if (gil().locked()) { + gil().release(); + } +} + +/** + * To be called from the main thread immediately after an async call + * that is recursive. See ApplicationWindow::runPythonScript + */ +void PythonScript::recursiveAsyncTeardown() { + if (!gil().locked()) { + gil().acquire(); + } +} + /** * Compile the code returning true if successful, false otherwise * @param code @@ -793,111 +813,3 @@ PyObject *PythonScript::compileToByteCode(bool for_eval) { } return compiledCode; } - -//-------------------------------------------------------------------------------------------- - -/** - * Listen to add notifications from the ADS and add a Python variable of the - * workspace name - * to the current scope - * @param wsName The name of the workspace - * @param ws The ws ptr (unused) - */ -void PythonScript::addHandle(const std::string &wsName, - const Mantid::API::Workspace_sptr ws) { - addPythonReference(wsName, ws); -} - -/** - * Listen to add/replace notifications from the ADS and add a Python variable - * of - * the workspace name - * to the current scope - * @param wsName The name of the workspace - * @param ws The ws ptr (unused) - */ -void PythonScript::afterReplaceHandle(const std::string &wsName, - const Mantid::API::Workspace_sptr ws) { - addPythonReference(wsName, ws); -} - -/** - * Removes a Python variable of the workspace name from the current scope - * @param wsName The name of the workspace - * @param ws The ws ptr (unused) - */ -void PythonScript::postDeleteHandle(const std::string &wsName) { - deletePythonReference(wsName); -} - -/** - * Clear all workspace handle references - */ -void PythonScript::clearADSHandle() { - for (auto itr = m_workspaceHandles.cbegin(); - itr != m_workspaceHandles.cend();) { - // This also erases the element from current set. The standard says that - // erase only invalidates - // iterators of erased elements so we need to increment the iterator and - // get - // back the previous value - // i.e. the postfix operator - this->deletePythonReference(*(itr++)); - } - - assert(m_workspaceHandles.empty()); -} - -/** - * Add a Python variable of the workspace name - * to the current scope - * @param wsName The name of the workspace - * @param ws The ws ptr (unused) - */ -void PythonScript::addPythonReference(const std::string &wsName, - const Mantid::API::Workspace_sptr ws) { - UNUSED_ARG(ws); - - // Compile a code object - const size_t length = wsName.length() * 2 + 10; - char *code = new char[length + 1]; - const char *name = wsName.c_str(); - sprintf(code, "%s = mtd['%s']", name, name); - PyObject *codeObj = - Py_CompileString(code, "PythonScript::addPythonReference", Py_file_input); - if (codeObj) { - PyObject *ret = PyEval_EvalCode(CODE_OBJECT(codeObj), localDict, localDict); - Py_XDECREF(ret); - } - if (PyErr_Occurred()) { - PyErr_Clear(); - } else { - // Keep track of it - m_workspaceHandles.insert(m_workspaceHandles.end(), wsName); - } - Py_XDECREF(codeObj); - delete[] code; -} - -/** - * Delete a Python reference to the given workspace name - * @param wsName The name of the workspace - */ -void PythonScript::deletePythonReference(const std::string &wsName) { - const size_t length = wsName.length() + 4; - char *code = new char[length + 1]; - sprintf(code, "del %s", wsName.c_str()); - PyObject *codeObj = - Py_CompileString(code, "PythonScript::deleteHandle", Py_file_input); - if (codeObj) { - PyObject *ret = PyEval_EvalCode(CODE_OBJECT(codeObj), localDict, localDict); - Py_XDECREF(ret); - } - if (PyErr_Occurred()) { - PyErr_Clear(); - } else { - m_workspaceHandles.erase(wsName); - } - Py_XDECREF(codeObj); - delete[] code; -} diff --git a/MantidPlot/src/PythonScript.h b/MantidPlot/src/PythonScript.h index 4372ad93f14e..33d23551d1aa 100644 --- a/MantidPlot/src/PythonScript.h +++ b/MantidPlot/src/PythonScript.h @@ -195,24 +195,6 @@ class PythonScript : public Script, MantidQt::API::WorkspaceObserver { /// Compile to bytecode PyObject *compileToByteCode(bool for_eval = true); - // ---------------------------- Variable reference - // --------------------------------------------- - /// Listen to add notifications from the ADS - void addHandle(const std::string &wsName, - const Mantid::API::Workspace_sptr ws) override; - /// Listen to add/replace notifications from the ADS - void afterReplaceHandle(const std::string &wsName, - const Mantid::API::Workspace_sptr ws) override; - /// Listen to delete notifications - void postDeleteHandle(const std::string &wsName) override; - /// Listen to ADS clear notifications - void clearADSHandle() override; - /// Add/update a Python reference to the given workspace - void addPythonReference(const std::string &wsName, - const Mantid::API::Workspace_sptr ws); - /// Delete a Python reference to the given workspace name - void deletePythonReference(const std::string &wsName); - /// Send out an error and clear it from python. void emit_error(); diff --git a/MantidPlot/src/Script.cpp b/MantidPlot/src/Script.cpp index d82f2d5383b0..ab0019e99a98 100644 --- a/MantidPlot/src/Script.cpp +++ b/MantidPlot/src/Script.cpp @@ -158,14 +158,3 @@ void Script::setIsRunning() { m_execMode = Running; } * Sets the offset & code string */ void Script::setupCode(const ScriptCode &code) { m_code = code; } - -/** - * Ensure that any line endings are converted to single '\n' so that the Python - * C API is happy - * @param text :: The text to check and convert - */ -QString Script::normaliseLineEndings(QString text) const { - text = text.replace(QRegExp("\\r\\n"), QString("\n")); - text = text.replace(QRegExp("\\r"), QString("\n")); - return text; -} diff --git a/MantidPlot/src/Script.h b/MantidPlot/src/Script.h index d6404b3af2ee..0baf7eebeccc 100644 --- a/MantidPlot/src/Script.h +++ b/MantidPlot/src/Script.h @@ -91,6 +91,9 @@ class Script : public QObject { bool redirectStdOut() const { return m_redirectOutput; } void redirectStdOut(bool on) { m_redirectOutput = on; } + virtual void recursiveAsyncSetup() {} + virtual void recursiveAsyncTeardown() {} + /// Create a list of keywords for the code completion API virtual void generateAutoCompleteList() {} // Does the code compile to a complete statement, i.e no more input is @@ -152,6 +155,8 @@ public slots: virtual bool executeImpl() = 0; /// Implementation of the abort request virtual void abortImpl() = 0; + /// Setup the code from a script code object + void setupCode(const ScriptCode &code); private: /** @@ -175,12 +180,6 @@ public slots: ScriptThreadPool(); }; - /// Setup the code from a script code object - void setupCode(const ScriptCode &code); - /// Normalise line endings for the given code. The Python C/API does not seem - /// to like CRLF endings so normalise to just LF - QString normaliseLineEndings(QString text) const; - ScriptingEnv *m_env; std::string m_name; // Easier to convert to C string ScriptCode m_code;