简体   繁体   中英

Boost Python: stop interpreter

Is there any way to stop a running Python interpreter after a Python method was called via Boost Python from within C++?

I want to interrupt the call for 2 reasons:

  1. If a timeout period expired (ie the script runs "too long").
  2. If another (parallel running) Python script failed.

My search on the web and in the Boost documentation didn't turn up anything, but on the other hand I sometimes struggle to find the right paragraph in the Boost docs...

The only "sort of idea" I got from this StackOverflow question . The idea would be to send a signal to the script, but as the interpreter runs within my C++ process, this probably not a viable option?!

I am doing the following:

const boost::filesystem::path pythonScriptPath = /* Path to the script I want to execute a function from. */
const std::string pythonFunctionName = /* Name of the Python function to call. */;

boost::python::object mainModule = boost::python::import("__main__");
boost::python::object mainNameSpace = mainModule.attr("__dict__");

boost::python::dict locals;
locals["moduleName"] = pythonScriptPath.stem().string();
locals["modulePath"] = pythonScriptPath.string();

std::stringstream importModuleStream;
importModuleStream
    << "import imp\n"
    << "newModule = imp.load_module(moduleName, open(modulePath), modulePath, ('py', 'U', imp.PY_SOURCE))\n";

boost::python::exec(importModuleStream.str().c_str(), mainNameSpace, locals);

auto pythonScript = locals["newModule"];

auto pythonFunction = pythonScript_.attr(pythonFunctionName .c_str());
pythonFunction(/* args */);

Now the questions are:

Can I interrupt/abort the execution of pythonFunction() after I triggered it? If it is not possible the way I called it, is there another way to call a Python function with Boost Python, so I could abort the call?

I am running under Linux (just in case this enables some platform dependent solution, that I would be totally cool with).

I have not found a true "stop interpreter from the outside" approach. But I created a workaround that at least gets the job done in my situation. Maybe it will help someone else...

The idea is that I have a thread inside the Python script that does nothing else but waits to be woken up. It gets woken up by a call to an "abort" function from within C++. Once it is awake it kills the script from the inside. I chose a crude approach to stop the script in this example:

os._exit(1)

There are sure nicer ways to do this, but this is beyond the point right here. The whole abort and terminate stuff can also be wrapped nicer, but once again: I just want to sketch the idea.

My test Python script looks like this:

import threading
import time
import os

def abort():
    global run
    run = False
    global condition
    with condition:    
        condition.notify()

def threadBlock():
    while True:
        print( "Blocking!" )
        time.sleep(3)

def threadTerminate():    
    while run:
        global condition
        with condition:
            condition.wait()

    global kill
    if kill:
        os._exit(1)

def myEntryFunction()
    blockingThread = threading.Thread( target = threadBlock )
    terminatingThread = threading.Thread( target = threadTerminate )

    blockingThread.start()
    terminatingThread.start()

    threadBlock().join()
    global kill
    kill = False
    global condition
    with condition:    
        condition.notify()      
    terminatingThread.join()



run = True;
kill = True;
condition = threading.Condition()

From within C++ I kill the script like this:

// other code (see question)

std::thread killer([&pythonScript] () {
    std::chrono::seconds d(15);
    std::this_thread::sleep_for(d);
    AcquireGIL gil;
    pythonScript.executeFunction("abort");          
});

pythonFunction(/* args */);

With AcquireGIL looking like this:

#include <boost/python.hpp>

class AcquireGIL final
{
public:
    AcquireGIL();
    ~AcquireGIL();


private:
    PyGILState_STATE gilState_;
};

AcquireGIL::AcquireGIL()
: gilState_(PyGILState_Ensure()) {
    // nothing to do...
}

AcquireGIL::~AcquireGIL() {
    PyGILState_Release(gilState_);
}

Edit

Different (similar) approach

In the entry function of my script I start a thread as daemon that calls a helper function. The helper function calls a worker method (that does the stuff I actually want to do). After the worker method returns, the helper signals a condition variable. The main thread just waits for this condition. If I want to abort from the outside, I just notify the condition as well. When the main thread ends, the helper thread already ended or in case of an abort from the outside, will be cleaned up.

Attention

In case of an abort the helper thread will not be able to clean up properly. So you have to cope with that or take care of it manually.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM