Skip to content

[BUG]: Cannot import pybind11-based python module from subinterpreter #4245

Open
@mdorier

Description

@mdorier

Required prerequisites

Problem description

I have a code that uses Python's C API (not pybind11) and creates subinterpreters in which to run code. The code works fine, apart from when a subinterpreter tries to import a python package that is based on pybind11: in such a situation, it hangs. Importing other, non-pybind11 packages, works fine.

Reproducible example code

(main.cpp) Code that runs subinterpreters with Python C API:

#include <thread>
#include <Python.h>

// initialize and clean up python
struct initialize {
    initialize() {
        Py_InitializeEx(1);
        PyEval_InitThreads();
    }

    ~initialize() {
        Py_Finalize();
    }
};

// allow other threads to run
class enable_threads {
public:
    enable_threads() {
        _state = PyEval_SaveThread();
    }

    ~enable_threads() {
        PyEval_RestoreThread(_state);
    }

private:
    PyThreadState* _state;
};

// acquire and release the GIL
struct gil_lock
{
    PyGILState_STATE gstate;

    gil_lock() {
        gstate = PyGILState_Ensure();
    }

    ~gil_lock() {
        PyGILState_Release(gstate);
    }
};

// restore the thread state when the object goes out of scope
class restore_tstate
{
public:

    restore_tstate() {
        _ts = PyThreadState_Get();
    }

    ~restore_tstate() {
        PyThreadState_Swap(_ts);
    }

private:
    PyThreadState* _ts;
};

// swap the current thread state with ts, restore when the object goes out of scope
class swap_tstate
{
public:

    swap_tstate(PyThreadState* ts) {
        _ts = PyThreadState_Swap(ts);
    }

    ~swap_tstate() {
        PyThreadState_Swap(_ts);
    }

private:
    PyThreadState* _ts;
};

// create new thread state for interpreter interp, clean up on destruction
class thread_state
{
public:

    thread_state(PyInterpreterState* interp) {
        _ts = PyThreadState_New(interp);
    }

    ~thread_state() {
        if( _ts ) {
            PyThreadState_Clear(_ts);
            PyThreadState_Delete(_ts);

            _ts = nullptr;
        }
    }

    operator PyThreadState*() {
        return _ts;
    }

private:
    PyThreadState* _ts;
};

// represent a sub interpreter
class sub_interpreter
{
public:

    // perform the necessary setup and cleanup for a new thread running using a specific interpreter
    struct thread {
        gil_lock _lock;
        thread_state _state;
        swap_tstate _swap;

        thread(PyInterpreterState* interp) :
                _state(interp),
                _swap(_state)
        {}
    };

    sub_interpreter() {
        restore_tstate restore;
        _ts = Py_NewInterpreter();
    }

    ~sub_interpreter() {
        if( _ts ) {
            swap_tstate sts(_ts);
            Py_EndInterpreter(_ts);
        }
    }

    PyInterpreterState* interp() {
        return _ts->interp;
    }

private:
    PyThreadState* _ts;
};

char program[] = "print(\"Subinterpreter\"); import sys; sys.path.append('.'); import my_module";
char main_program[] = "print(\"Main program\")";

// run in a new thread
void f(PyInterpreterState* interp)
{
    sub_interpreter::thread scope(interp);
    PyRun_SimpleString(program);
}

int main()
{
    initialize init;

    sub_interpreter s1;
    sub_interpreter s2;
    sub_interpreter s3;

    std::thread t1(f, s1.interp() );
    std::thread t2(f, s2.interp() );
    std::thread t3(f, s3.interp() );

    std::thread t4(f, s1.interp() );

    PyRun_SimpleString(main_program);

    enable_threads t;

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}

(module.cpp) Code of a pybind11-based module

#include <pybind11/pybind11.h>

namespace py = pybind11;

PYBIND11_MODULE(my_module, m) {
    m.doc() = "Example module";
};

(CMakeLists.txt) CMake file to build the project

cmake_minimum_required (VERSION 3.15)

project (py1)

find_package (Python3 COMPONENTS Interpreter Development REQUIRED)
find_package (pybind11 REQUIRED)
include_directories (${PYTHON_INCLUDE_DIRS})

ADD_DEFINITIONS (-std=c++11)

add_executable (main main.cpp)
target_link_libraries (main Python3::Python)

add_library (my_module MODULE module.cpp)
target_link_libraries (my_module PRIVATE pybind11::module)
pybind11_extension (my_module)
pybind11_strip (my_module)

Metadata

Metadata

Labels

docsDocs or GitHub info

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions