-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix thread safety for pybind11 loader_life_support (#3237)
* Fix thread safety for pybind11 loader_life_support Fixes issue: #2765 This converts the vector of PyObjects to either a single void* or a per-thread void* depending on the WITH_THREAD define. The new field is used by each thread to construct a stack of loader_life_support frames that can extend the life of python objects. The pointer is updated when the loader_life_support object is allocated (which happens before a call) as well as on release. Each loader_life_support maintains a set of PyObject references that need to be lifetime extended; this is done by storing them in a c++ std::unordered_set and clearing the references when the method completes. * Also update the internals version as the internal struct is no longer compatible * Add test demonstrating threading works correctly. It may be appropriate to run this under msan/tsan/etc. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test to use lifetime-extended references rather than std::string_view, as that's a C++ 17 feature. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Make loader_life_support members private * Update version to dev2 * Update test to use python threading rather than concurrent.futures * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove unnecessary env in test * Remove unnecessary pytest in test * Use native C++ thread_local in place of python per-thread data structures to retain compatability * clang-format test_thread.cpp * Add a note about debugging the py::cast() error * thread_test.py now propagates exceptions on join() calls. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove unused sys / merge * Update include order in test_thread.cpp * Remove spurious whitespace * Update comment / whitespace. * Address review comments * lint cleanup * Fix test IntStruct constructor. * Add explicit to constructor Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Aaron Gokaslan <skylion.aaron@gmail.com>
- Loading branch information
1 parent
121b91f
commit 0e59958
Showing
7 changed files
with
148 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
tests/test_thread.cpp -- call pybind11 bound methods in threads | ||
Copyright (c) 2021 Laramie Leavitt (Google LLC) <lar@google.com> | ||
All rights reserved. Use of this source code is governed by a | ||
BSD-style license that can be found in the LICENSE file. | ||
*/ | ||
|
||
#include <pybind11/cast.h> | ||
#include <pybind11/pybind11.h> | ||
|
||
#include <chrono> | ||
#include <thread> | ||
|
||
#include "pybind11_tests.h" | ||
|
||
namespace py = pybind11; | ||
|
||
namespace { | ||
|
||
struct IntStruct { | ||
explicit IntStruct(int v) : value(v) {}; | ||
~IntStruct() { value = -value; } | ||
IntStruct(const IntStruct&) = default; | ||
IntStruct& operator=(const IntStruct&) = default; | ||
|
||
int value; | ||
}; | ||
|
||
} // namespace | ||
|
||
TEST_SUBMODULE(thread, m) { | ||
|
||
py::class_<IntStruct>(m, "IntStruct").def(py::init([](const int i) { return IntStruct(i); })); | ||
|
||
// implicitly_convertible uses loader_life_support when an implicit | ||
// conversion is required in order to lifetime extend the reference. | ||
// | ||
// This test should be run with ASAN for better effectiveness. | ||
py::implicitly_convertible<int, IntStruct>(); | ||
|
||
m.def("test", [](int expected, const IntStruct &in) { | ||
{ | ||
py::gil_scoped_release release; | ||
std::this_thread::sleep_for(std::chrono::milliseconds(5)); | ||
} | ||
|
||
if (in.value != expected) { | ||
throw std::runtime_error("Value changed!!"); | ||
} | ||
}); | ||
|
||
m.def( | ||
"test_no_gil", | ||
[](int expected, const IntStruct &in) { | ||
std::this_thread::sleep_for(std::chrono::milliseconds(5)); | ||
if (in.value != expected) { | ||
throw std::runtime_error("Value changed!!"); | ||
} | ||
}, | ||
py::call_guard<py::gil_scoped_release>()); | ||
|
||
// NOTE: std::string_view also uses loader_life_support to ensure that | ||
// the string contents remain alive, but that's a C++ 17 feature. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import threading | ||
|
||
from pybind11_tests import thread as m | ||
|
||
|
||
class Thread(threading.Thread): | ||
def __init__(self, fn): | ||
super(Thread, self).__init__() | ||
self.fn = fn | ||
self.e = None | ||
|
||
def run(self): | ||
try: | ||
for i in range(10): | ||
self.fn(i, i) | ||
except Exception as e: | ||
self.e = e | ||
|
||
def join(self): | ||
super(Thread, self).join() | ||
if self.e: | ||
raise self.e | ||
|
||
|
||
def test_implicit_conversion(): | ||
a = Thread(m.test) | ||
b = Thread(m.test) | ||
c = Thread(m.test) | ||
for x in [a, b, c]: | ||
x.start() | ||
for x in [c, b, a]: | ||
x.join() | ||
|
||
|
||
def test_implicit_conversion_no_gil(): | ||
a = Thread(m.test_no_gil) | ||
b = Thread(m.test_no_gil) | ||
c = Thread(m.test_no_gil) | ||
for x in [a, b, c]: | ||
x.start() | ||
for x in [c, b, a]: | ||
x.join() |