I'm using multiple interpreters with multiple threads, and I'm trying to use PyThreadState_SetAsyncExc
to stop a script, but it has no effect:
void stop()
{
PyThreadState *ts = PyThreadState_Swap(this->tstate);
PyObject *exc = PyObject_CallObject(PyExc_Exception, Py_BuildValue("(s)", "stopped"));
int n = PyThreadState_SetAsyncExc(this->tstate->thread_id, exc);
if(n < 1)
std::cerr << "Script::stop: thread not found!" << std::endl;
PyThreadState_Swap(ts);
}
The returned value is 1, but the thread continues running. Why?
Complete testcase:
#include <Python.h>
#include <iostream>
#include <thread>
using namespace std::chrono_literals;
struct Script
{
Script()
{
PyThreadState *ts = PyThreadState_Get();
this->tstate = Py_NewInterpreter();
PyThreadState_Swap(ts);
}
virtual ~Script()
{
if(!this->tstate) return;
PyThreadState *ts = PyThreadState_Swap(this->tstate);
Py_EndInterpreter(this->tstate);
PyThreadState_Swap(ts);
}
void run(const char *code)
{
PyThreadState *ts = PyThreadState_Swap(this->tstate);
PyRun_SimpleStringFlags(code, nullptr);
PyThreadState_Swap(ts);
}
void run_thread(const char *code)
{
thread = std::thread{std::bind(&Script::thread_func, this, code)};
}
void join()
{
thread.join();
}
void stop()
{
PyThreadState *ts = PyThreadState_Swap(this->tstate);
PyObject *exc = PyObject_CallObject(PyExc_Exception, Py_BuildValue("(s)", "stopped"));
int n = PyThreadState_SetAsyncExc(this->tstate->thread_id, exc);
if(n < 1)
std::cerr << "Script::stop: thread not found!" << std::endl;
PyThreadState_Swap(ts);
}
private:
void thread_func(const char *code)
{
PyThreadState *ts = PyThreadState_New(this->tstate->interp);
PyEval_RestoreThread(ts);
PyThreadState *ts0 = PyThreadState_Swap(ts);
PyRun_SimpleStringFlags(code, nullptr);
PyThreadState_Swap(ts0);
PyThreadState_Clear(ts);
PyThreadState_DeleteCurrent();
}
PyThreadState *tstate;
std::thread thread;
};
int main(int argc, char *argv[])
{
Py_InitializeEx(1);
PyEval_InitThreads(); // implicit since python 3.7
Script s1, s2;
s1.run_thread(
R"PY(
import time
import sys
i = 0
while 1:
i += 1
time.sleep(1)
sys.stdout.write('s1: %d\n' % i); sys.stdout.flush()
)PY");
PyThreadState *ts = PyEval_SaveThread();
std::this_thread::sleep_for(4s);
std::cout << "trying to stop s1..." << std::endl;
s1.stop();
s1.join();
PyEval_RestoreThread(ts);
PyMem_RawFree(program);
if (Py_FinalizeEx() < 0)
return 120;
return 0;
}