Fatal error during Py_Finalize in embedded Python application

Thanks for your help on this - variations on this question have been asked many times, but I haven't found a complete answer. I am adding inline Python 3.4.2 to an existing simulator tool written in C ++ using MS MFC classes. The application is multi-threaded so that the user can execute Python scripts and interact with the simulator system.

How do I log out successfully? Am I using GIL and thread state commands in the correct order? Am I terminating a Python interpreter thread prematurely and breaking the Python thread consolidation mechanism?

My problem is that when I call Py_Finalize it calls wait_for_thread_shutdown, then PyThreadState_Get, and it gets a fatal error: "PyThreadState_Get: no current thread". Based on the point where the fatal error is found, it seems to be related to thread consolidation at the end of a multi-threaded embedded Python application.

I have shortened my code to clarify it and eliminate anything that doesn't seem relevant. We apologize if I have gone too far or not far enough. The main thread initializes and exits Python.

BOOL CSimApp::InitInstance()
{
    ...
    // Initialize command table for appl. object and for documents
    int iReturn = PyImport_AppendInittab("sim", &PyInit_SimApp);
    iReturn = PyImport_AppendInittab("sim_doc", &PyInit_SimDoc);

    // Initialize Python and prepar to create threads
    _pHInstance = new CPyInstance();
    ...
}

int CSimApp::ExitInstance() 
{
    ...
    if (_pHInstance) {
        delete _pHInstance;
        _pHInstance = NULL;
    }
    ...
}

      

I am using utility classes to create a Python instance (CPyInstance) and to manage the Python GIL (ACQUIRE_PY_GIL). When the application is initialized, an instance of CPyInstance is created. The CPyInstance class initializes and terminates Python thread management. Python global locking is controlled using the ACQUIRE_PY_GIL and RELEASE_PY_GIL structures.

class CPyInstance
{
public:
    CPyInstance();
    ~CPyInstance();
    static PyThreadState * mainThreadState;
};

inline CPyInstance::CPyInstance()
{
    mainThreadState = NULL;
    Py_Initialize();
    PyEval_InitThreads();
    mainThreadState = PyThreadState_Get();
    PyEval_ReleaseLock();
}

inline CPyInstance::~CPyInstance()
{
    Py_Finalize();
}

static CPyInstance    *_pHInstance = NULL;

int PyExit()
{
    if (_pHInstance) {
        delete _pHInstance;
        _pHInstance = NULL;
    }
    return 0;
}

struct ACQUIRE_PY_GIL {
    PyGILState_STATE state;
    ACQUIRE_PY_GIL() { state = PyGILState_Ensure(); }
    ~ACQUIRE_PY_GIL() { PyGILState_Release(state); }
};

struct RELEASE_PY_GIL {
    PyThreadState *state;
    RELEASE_PY_GIL() {  state = PyEval_SaveThread(); }
    ~RELEASE_PY_GIL() { PyEval_RestoreThread(state); }
};

      

The Python interpreter thread is created in response to a Windows message processed by the CMainFrame. The Python thread and interpreter are launched in response to a user command. When the user is done with the interpreter (Control-Z), the interpreter exits, the thread clears and clears the state of the Python thread, and then the thread exits itself.

void CMainFrame::OnOpenPythonInterpreter()
{
    // Create PyThread thread
    m_pPyThread = (CPyThread*)AfxBeginThread(RUNTIME_CLASS(CPyThread),
                    THREAD_PRIORITY_BELOW_NORMAL,0, CREATE_SUSPENDED);
    CMainFrame* mf = (CMainFrame*)theApp.m_pMainWnd;
    m_pPyThread->SetOwner(this,((CWnd*)mf)->GetSafeHwnd());
    m_pPyThread->CreateLocks(&m_PyThreadEvent,&m_PyThreadBusyMutex);
    m_pPyThread->ResumeThread();
}

      

The CPyThread class actually calls the Python interpreter. When the interpreter returns, the GIL is released and the state of the Python thread is flushed and discarded. The stream ends in response to a PostQuitMessage.

int CPyThread::Run() 
{
    PyEval_AcquireLock();
    PyInterpreterState * mainInterpreterState = CPyInstance::mainThreadState->interp;
    PyThreadState * myThreadState = PyThreadState_New(mainInterpreterState);
    PyEval_ReleaseLock();

    try {
        ACQUIRE_PY_GIL    lock;
        FILE* fp1 = stdin;
        char *filename = "Embedded";
        PyRun_InteractiveLoop(fp1, filename);
    } catch(const std::exception &e) {
        safe_cout << "Exception in PyRun_InteractiveLoop: " << e.what() << "\n";
    } catch(...) {
        std::cout << "Exception in Python code: UNKNOWN\n";
    }

    PyThreadState_Clear(myThreadState);
    PyThreadState_Delete(myThreadState);

    ::PostQuitMessage(0);
    return 0;
}

int CPyThread::ExitInstance() 
{
    return CWinThread::ExitInstance();
}

      

In the suggestion "user4815162342" I changed my destructor of my CPiInstance () to get the GIL before calling Py_Finalize (). Now my app seems to be exiting correctly, thanks.

inline CPyInstance::~CPyInstance()
{
    try {
        PyGILState_STATE state = PyGILState_Ensure();
        Py_Finalize();
    } catch(const std::exception &e) {
        safe_cout << "Exception in ~CPyInstance(): " << e.what() << "\n";
    } catch(...) {
        std::cout << "Exception in Python code: UNKNOWN\n";
    }
}

      

+3


source to share


1 answer


You are calling Py_Finalize

without holding the lock on the global interpreter. This is not allowed: a lock must be held for every Python API call, with the one exception of a call that the GIL itself acquires.



The ACQUIRE_PY_GIL

RAII custodian is not suitable for this purpose, as it will try to free the GIL upon return Py_Finalize

- in which case you must call PyGILState_Ensure

without a matching version.

0


source







All Articles