pybind11/tests/pybind11_tests.h
vfdev ce2f005594
Fixed data race in all_type_info in free-threading mode (#5419)
* Fix data race all_type_info_populate in free-threading mode
Description:
- fixed data race all_type_info_populate in free-threading mode
- added test

For example, we have 2 threads entering `all_type_info`.
Both enter `all_type_info_get_cache`` function and
there is a first one which inserts a tuple (type, empty_vector) to the map
and second is waiting. Inserting thread gets the (iter_to_key, True) and non-inserting thread
after waiting gets (iter_to_key, False).
Inserting thread than will add a weakref and will then call into `all_type_info_populate`.
However, non-inserting thread is not entering `if (ins.second) {` clause and
returns `ins.first->second;`` which is just empty_vector.
Finally, non-inserting thread is failing the check in `allocate_layout`:
```c++
if (n_types == 0) {
    pybind11_fail(
        "instance allocation failed: new instance has no pybind11-registered base types");
}
```

* style: pre-commit fixes

* Addressed PR comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-11-07 09:32:09 -08:00

120 lines
3.7 KiB
C++

#pragma once
#include <pybind11/eval.h>
#include <pybind11/pybind11.h>
#include <memory>
namespace py = pybind11;
using namespace pybind11::literals;
class test_initializer {
using Initializer = void (*)(py::module_ &);
public:
explicit test_initializer(Initializer init);
test_initializer(const char *submodule_name, Initializer init);
};
#define TEST_SUBMODULE(name, variable) \
void test_submodule_##name(py::module_ &); \
test_initializer name(#name, test_submodule_##name); \
void test_submodule_##name(py::module_ &(variable))
/// Dummy type which is not exported anywhere -- something to trigger a conversion error
struct UnregisteredType {};
/// A user-defined type which is exported and can be used by any test
class UserType {
public:
UserType() = default;
explicit UserType(int i) : i(i) {}
int value() const { return i; }
void set(int set) { i = set; }
private:
int i = -1;
};
/// Like UserType, but increments `value` on copy for quick reference vs. copy tests
class IncType : public UserType {
public:
using UserType::UserType;
IncType() = default;
IncType(const IncType &other) : IncType(other.value() + 1) {}
IncType(IncType &&) = delete;
IncType &operator=(const IncType &) = delete;
IncType &operator=(IncType &&) = delete;
};
/// A simple union for basic testing
union IntFloat {
int i;
float f;
};
class UnusualOpRef {
public:
using NonTrivialType = std::shared_ptr<int>; // Almost any non-trivial type will do.
// Overriding operator& should not break pybind11.
NonTrivialType operator&() { return non_trivial_member; }
NonTrivialType operator&() const { return non_trivial_member; }
private:
NonTrivialType non_trivial_member;
};
/// Custom cast-only type that casts to a string "rvalue" or "lvalue" depending on the cast
/// context. Used to test recursive casters (e.g. std::tuple, stl containers).
struct RValueCaster {};
PYBIND11_NAMESPACE_BEGIN(pybind11)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<RValueCaster> {
public:
PYBIND11_TYPE_CASTER(RValueCaster, const_name("RValueCaster"));
static handle cast(RValueCaster &&, return_value_policy, handle) {
return py::str("rvalue").release();
}
static handle cast(const RValueCaster &, return_value_policy, handle) {
return py::str("lvalue").release();
}
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11)
template <typename F>
void ignoreOldStyleInitWarnings(F &&body) {
py::exec(R"(
message = "pybind11-bound class '.+' is using an old-style placement-new '(?:__init__|__setstate__)' which has been deprecated"
import warnings
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message=message, category=FutureWarning)
body()
)",
py::dict(py::arg("body") = py::cpp_function(body)));
}
// See PR #5419 for background.
class TestContext {
public:
TestContext() = delete;
TestContext(const TestContext &) = delete;
TestContext(TestContext &&) = delete;
static TestContext *createNewContextForInit() { return new TestContext("new-context"); }
pybind11::object contextEnter() {
py::object contextObj = py::cast(*this);
return contextObj;
}
void contextExit(const pybind11::object & /*excType*/,
const pybind11::object & /*excVal*/,
const pybind11::object & /*excTb*/) {}
private:
explicit TestContext(const std::string &context) : context(context) {}
std::string context;
};