HAL
python_thread.cpp
Go to the documentation of this file.
3 #include <QDebug>
4 
5 #include <pyerrors.h>
6 #include <ceval.h>
7 #include <pystate.h>
10 #include <QApplication>
11 #include <QDebug>
12 
13 namespace hal {
15  {
16  mState = PyGILState_Ensure();
17  }
18 
20  {
21  PyGILState_Release((PyGILState_STATE)mState);
22  }
23 
24  PythonThread::PythonThread(const QString& script, bool singleStatement, QObject* parent)
25  : QThread(parent), mScript(script), mSingleStatement(singleStatement),
26  mAbortRequested(false), mSpamCount(0)
27  {
28  // qDebug() << "+++PythonThread" << hex << (quintptr) this;
29  }
30 
32  {
33  // qDebug() << "---PythonThread" << hex << (quintptr) this;
34  }
35 
37  {
38  // decides it's our turn.
39  PythonMutex pmutex;
40  py::dict tmp_context(py::globals());
41  PythonContext::initializeScript(&tmp_context);
42 
43  // Capture the Python-internal thread ID, which is needed if we want to
44  // interrupt the thread later
45  pybind11::object rc = py::eval("threading.get_ident()", tmp_context, tmp_context);
46  mPythonThreadID = rc.cast<unsigned long>();
47 
48  mElapsedTimer.start();
49  try
50  {
51  py::object rc;
52  if (mSingleStatement)
53  rc = py::eval<py::eval_single_statement>(mScript.toStdString(), tmp_context, tmp_context);
54  else
55  rc = py::eval<py::eval_statements>(mScript.toStdString(), tmp_context, tmp_context);
56 
57  if (!rc.is_none())
58  mResult = QString::fromStdString(py::str(rc).cast<std::string>());
59  }
60  catch (py::error_already_set& e)
61  {
62  qDebug() << "AlreadySet";
63  mErrorMessage = QString::fromStdString(std::string(e.what()) + "\n");
64  e.restore();
65  PyErr_Clear();
66  }
67  catch (std::exception& e)
68  {
69  qDebug() << "Exception";
70  mErrorMessage = QString::fromStdString(std::string(e.what()) + "#\n");
71  }
72 
73  // running out of scope calls PyGILState_Release(state);
74  }
75 
76  void PythonThread::handleStdout(const QString& output)
77  {
78  if (mElapsedTimer.elapsed() > 100)
79  {
80  if (!mStdoutBuffer.isEmpty())
81  ++mSpamCount;
82  else
83  mSpamCount = 0;
84  if (mSpamCount > 1000)
85  mStdoutBuffer += output;
86  else if (mSpamCount < 10 || mElapsedTimer.elapsed() > 1000)
87  {
88  Q_EMIT stdOutput(mStdoutBuffer + output);
89  mStdoutBuffer.clear();
90  mElapsedTimer.restart();
91  }
92  else
93  mStdoutBuffer += output;
94  }
95  else
96  mStdoutBuffer += output;
97  }
98 
100  {
101  QString retval = mStdoutBuffer;
102  mStdoutBuffer.clear();
103  mElapsedTimer.restart();
104  return retval;
105  }
106 
107  void PythonThread::handleError(const QString& output)
108  {
110  }
111 
113  {;}
114 
116  if (!mInputMutex.tryLock())
117  {
118  mAbortRequested = true;
119  mInputMutex.unlock();
120  return;
121  }
122  else
123  {
124  mInputMutex.unlock();
125  }
126  qDebug() << "about to terminate thread..." << mPythonThreadID;
127  PythonMutex pmutex;
128  // We interrupt the thread by forcibly injecting an exception
129  // (namely the KeyboardInterrupt exception, but any other one works as well)
130  int nThreads = PyThreadState_SetAsyncExc(mPythonThreadID, PyExc_KeyboardInterrupt);
131  if (nThreads == 0)
132  {
133  qDebug() << "Oh no! The Python interpreter doesn't know that thread.";
134  }
135  else if (nThreads > 1)
136  {
137  // apparently this can actually happen if you mess up the C<->Python bindings
138  qDebug() << "Oh no! There seem to be multiple threads with the same ID!";
139  }
140  qDebug() << "thread terminated";
141  // running out of scope calls PyGILState_Release(state);
142  }
143 
145  {
146  if (!mInputMutex.tryLock())
147  {
148  qDebug() << "Oh no! Function already locked waiting for input.";
149  return false;
150  }
151  if (type != WaitForMenuSelection)
152  Q_EMIT requireInput(type,prompt,defaultValue);
153  mInputMutex.lock(); // wait for set Input
154  mInputMutex.unlock();
155  if (mAbortRequested)
156  {
157  throw std::runtime_error(std::string("Python script aborted by user"));
158  return false;
159  }
160  return true;
161  }
162 
163  std::string PythonThread::handleConsoleInput(const QString& prompt)
164  {
165  if (!getInput(ConsoleInput, prompt, QString())) return std::string();
166  return mInput.toString().toStdString();
167  }
168 
169  std::string PythonThread::handleStringInput(const QString& prompt, const QString& defval)
170  {
171  if (!getInput(StringInput, prompt, defval)) return std::string();
172  return mInput.toString().toStdString();
173  }
174 
175  int PythonThread::handleNumberInput(const QString& prompt, int defval)
176  {
177  if (!getInput(NumberInput, prompt, defval)) return 0;
178  return mInput.toInt();
179  }
180 
182  {
183  if (!getInput(ModuleInput, prompt, QString())) return 0;
184  return static_cast<Module*>(mInput.value<void*>());
185  }
186 
188  {
189  if (!getInput(GateInput, prompt, QString())) return 0;
190  return static_cast<Gate*>(mInput.value<void*>());
191  }
192 
193  std::string PythonThread::handleFilenameInput(const QString& prompt, const QString& filetype)
194  {
195  if (!getInput(FilenameInput, prompt, filetype)) return std::string();
196  return mInput.toString().toStdString();
197  }
198 
200  {
201  mInput = inp;
202  mInputMutex.unlock();
203  }
204 
206  {
207  // unlocking if not locked might cause undefined behavior
208  mInputMutex.tryLock();
209  mInputMutex.unlock();
210  }
211 }
Definition: gate.h:58
PythonMutex()
Aquire GIL.
~PythonMutex()
Release GIL.
void clear() override
void stdError(QString txt)
Gate * handleGateInput(const QString &prompt)
void handleStdout(const QString &output) override
void setInput(const QVariant &inp)
std::string handleStringInput(const QString &prompt, const QString &defval)
int handleNumberInput(const QString &prompt, int defval)
bool getInput(InputType type, QString prompt, QVariant defaultValue)
std::string handleConsoleInput(const QString &prompt)
Module * handleModuleInput(const QString &prompt)
void handleError(const QString &output) override
void stdOutput(QString txt)
void requireInput(int type, QString prompt, QVariant defaultValue)
void run() override
PythonThread(const QString &script, bool singleStatement, QObject *parent=nullptr)
std::string handleFilenameInput(const QString &prompt, const QString &filetype)
PinType type
qint64 elapsed() const const
qint64 restart()
void lock()
bool tryLock(int timeout)
void unlock()
Q_EMITQ_EMIT
void clear()
QString fromStdString(const std::string &str)
bool isEmpty() const const
std::string toStdString() const const
int toInt(bool *ok) const const
QString toString() const const
T value() const const