Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG]: make_iterator causes runtime error in second scoped_interpreter #3776

Open
3 tasks done
jasjuang opened this issue Mar 4, 2022 · 8 comments
Open
3 tasks done
Labels

Comments

@jasjuang
Copy link

jasjuang commented Mar 4, 2022

Required prerequisites

Problem description

This issue is similar to #2101, the reported minimal example problem in #2101 is fixed in the latest master because of #3744. However, the below minimal example will produce the error

1
2
3
terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  RuntimeError: instance allocation failed: new instance has no pybind11-registered base types

At:
  <string>(4): <module>

Aborted (core dumped)

I can reproduce this problem in Ubuntu and macOS.

The real use case is that each gtest TEST block will have its own py::scoped_interpreter, and this bug is preventing me from writing for i in testclass: more than once, but I would like to write for i in testclass: in multiple different TEST blocks.

Reproducible example code

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(pybindsample)

set(pybind11_DIR "/home/jasjuang/install/share/cmake/pybind11")
find_package(pybind11 REQUIRED)

pybind11_add_module(${PROJECT_NAME} pybindsample.cpp)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)

add_executable(pybindsample_test pybindsample_test.cpp)
target_link_libraries(pybindsample_test pybind11::module pybind11::embed)

pybindsample.cpp

#include "pybind11/pybind11.h"
#include "pybind11/stl.h"

namespace py = pybind11;

class TestClass {
public:
  TestClass() = default;
  std::vector<int>::iterator begin() noexcept { return vec.begin(); }
  std::vector<int>::iterator end() noexcept { return vec.end(); }

private:
  std::vector<int> vec = {1, 2, 3};
};

PYBIND11_MODULE(pybindsample, m) {
  py::class_<TestClass>(m, "TestClass")
      .def(py::init<>())
      .def(
          "__iter__",
          [](TestClass &t) { return py::make_iterator(t.begin(), t.end()); },
          py::keep_alive<0, 1>());
}

pybindsample_test.cpp

#include "pybind11/embed.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"

namespace py = pybind11;

int main() {
  {
    py::scoped_interpreter g;
    py::eval<py::eval_statements>("import pybindsample\n"
                                  "testclass=pybindsample.TestClass()\n"
                                  "for i in testclass:\n"
                                  "    print(i)\n");
  }
  {
    py::scoped_interpreter g;
    py::eval<py::eval_statements>("import pybindsample\n"
                                  "testclass=pybindsample.TestClass()\n"
                                  "for i in testclass:\n"
                                  "    print(i)\n");
  }

  return 0;
}
@jasjuang jasjuang added the triage New bug, unverified label Mar 4, 2022
@Skylion007 Skylion007 added bug and removed triage New bug, unverified labels Mar 4, 2022
@Skylion007
Copy link
Collaborator

Any thoughts @StarQTius?

@StarQTius
Copy link
Contributor

StarQTius commented Mar 4, 2022

The following snippet results in the expected behavior on my machine:

#include "pybind11/embed.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"

namespace py = pybind11;

class TestClass {
public:
  TestClass() = default;
  std::vector<int>::iterator begin() noexcept { return vec.begin(); }
  std::vector<int>::iterator end() noexcept { return vec.end(); }

private:
  std::vector<int> vec = {1, 2, 3};
};

PYBIND11_EMBEDDED_MODULE(pybindsample, m) {
  py::class_<TestClass>(m, "TestClass")
      .def(py::init<>())
      .def(
          "__iter__",
          [](TestClass &t) { return py::make_iterator(t.begin(), t.end()); },
          py::keep_alive<0, 1>());
}

int main() {
  {
    py::scoped_interpreter g;
    py::eval<py::eval_statements>("import pybindsample\n"
                                  "testclass=pybindsample.TestClass()\n"
                                  "for i in testclass:\n"
                                  "    print(i)\n");
  }
  {
    py::scoped_interpreter g;
    py::eval<py::eval_statements>("import pybindsample\n"
                                  "testclass=pybindsample.TestClass()\n"
                                  "for i in testclass:\n"
                                  "    print(i)\n");
  }

  return 0;
}

So it has to do with the dynamic linkage or maybe EMBEDDED_MODULES do some cleanup that MODULES don't I believe. I've checked with nm whether the local internals were duplicated, but it doesn't seem so.

@jasjuang
Copy link
Author

jasjuang commented Mar 4, 2022

@StarQTius I can also confirm that PYBIND11_EMBEDDED_MODULE works as intended but not PYBIND11_MODULE.

@StarQTius
Copy link
Contributor

Well, I might have been wrong when I said locals were not duplicated. This is the address of the variable containing the internals when get_local_internals is called during the first interpreter finalization (in other word, this is when the fix of #3744 kicks in):

Breakpoint 1, pybind11::detail::get_local_internals () at /home/paulin/Desktop/test/pybind11/include/pybind11/detail/internals.h:515
515	    return locals;
(gdb) p &locals
$84 = (pybind11::detail::local_internals *) 0x55555559dd80 <pybind11::detail::get_local_internals()::locals>
(gdb) bt
#0  pybind11::detail::get_local_internals () at /home/paulin/Desktop/test/pybind11/include/pybind11/detail/internals.h:515
#1  0x000055555556a929 in pybind11::finalize_interpreter () at /home/paulin/Desktop/test/pybind11/include/pybind11/embed.h:203
#2  0x000055555556aa6e in pybind11::scoped_interpreter::~scoped_interpreter (this=0x7fffffffdd97, __in_chrg=<optimized out>)
    at /home/paulin/Desktop/test/pybind11/include/pybind11/embed.h:245
#3  0x000055555555b80e in main () at /home/paulin/Desktop/test/pybindsample_test.cpp:14

And this is what GDB displays when the breakpoint is hit twice more:

Breakpoint 1, pybind11::detail::get_local_internals () at /home/paulin/Desktop/test/pybind11/include/pybind11/detail/internals.h:515
515	    return locals;
(gdb) p &locals
$86 = (pybind11::detail::local_internals *) 0x7ffff6cc99a0 <pybind11::detail::get_local_internals()::locals>
(gdb) bt
#0  pybind11::detail::get_local_internals () at /home/paulin/Desktop/test/pybind11/include/pybind11/detail/internals.h:515
#1  0x00007ffff6c79788 in pybind11::detail::get_local_type_info (tp=...)
    at /home/paulin/Desktop/test/pybind11/include/pybind11/detail/type_caster_base.h:197

...

#55 0x000055555556f735 in pybind11::eval<(pybind11::eval_mode)2, 89ul> (s=..., global=..., local=...)
    at /home/paulin/Desktop/test/pybind11/include/pybind11/eval.h:85
#56 0x000055555555b87d in main () at /home/paulin/Desktop/test/pybindsample_test.cpp:17

It seems like the locals are internally linked. Is it the intended behavior ?

@Skylion007
Copy link
Collaborator

@StarQTius I don't think we have any defined behavior for the locals between interpreters (which is partially what caused these bugs). So whichever resolves these bugs. I don't see any reason why they should be linked unless there is some global configuration state that should be shared between them.

@StarQTius
Copy link
Contributor

Then I think the best solution would be to register a callback in PyModuleDef::m_free which clear the locals, and keep one local_internals instance by shared library. In that case, EMBEDDED_MODULES would share the same locals, though I don't know if it will really be an issue (it might if the user try to register the same type in two different embedded modules I guess).

@ssooffiiaannee
Copy link

Could Anyone provide the compilation command, I'm getting a segmentation fault, I'm new to this.

@jasjuang
Copy link
Author

jasjuang commented Mar 9, 2022

@ssooffiiaannee

git clone https://github.com/pybind/pybind11
cd pybind11 && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/home/jasjuang/install/ ..
make -j install
cd
mkdir sample && cd sample
<create CMakeLists.txt, pybindsample.cpp pybindsample_test.cpp>
mkdir build && cd build
cmake ..
make -j
./pybindsample_test 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants