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

[smart_holder] return value policies allowed for shared_ptr #3039

Merged
merged 17 commits into from
Jun 29, 2021

Conversation

jakobandersen
Copy link
Contributor

@jakobandersen jakobandersen commented Jun 16, 2021

Description

Allow copy and move policy for smart holder casting of std::shared_ptr.

@jakobandersen
Copy link
Contributor Author

@rwgk, @rhaschke, some initial code to get started.

Without -DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT I locally get a "double free or corruption" error (i guess because MyObject3a is not specified to be held by shared_ptr but is appended to the list as such anyway). This happens both after the first commit and the second commit.

With -DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT, after the first commit it gives the Invalid return_value_policy for shared_ptr. error, and after the second commit, simply allowing copy as policy in smart_holder_type_caster it seemingly works.

I'm not really sure how the whole smart holder stuff works, so any ideas of how to progress on this?
The way I added the test doesn't seem right, should it only be run in progressive mode tests?

(By the way, can you enable the workflows on the PR, it only tests in appveyor right now)

@rwgk
Copy link
Collaborator

rwgk commented Jun 16, 2021

Unfortunately I have to manually trigger the workflows each time you push something new, and I'm not getting automatic notifications. Please tag me here each time you want to run the CI (some dummy comment with @rwgk).

To make the Format workflow happy, I always blindly run pre-commit run --all-files before a git commit. See .github/CONTRIBUTING.md.

Maybe for later/general background: test_smart_ptrs.cpp,py is one of the few tests that does NOT exercise any smart_holder functionality. It would be better if you added to some test_class_sh_*.cpp,py or ideally simply make a new test_class_sh_xxx.cpp,py pair. The extra overhead is super tiny (one more line in tests/CMakeLists.txt). Then we can decide later if we want to keep the new tests separate, or fold them into something else (also super easy).

About the copy policy, when enabling it, I'd want to be certain we emulate the existing behavior exactly. We could either do this via a test that uses type_caster_base in CI-SH-AVL, and smart_holder in CI-SH-DEF, i.e. via the PYBIND11_SH_AVL macro, although I think the alternative of having one test that exercises both simultaneously would be ideal. It's actually pretty simple, combining these two tricks:

The first tracks copy and move, the second adds a serial number, so that you can wrap with both py::class_<Atype<0>, std::shared_ptr<Atype<0>>> and py::classh<Atype<1>> in the same test_*.cpp, then in test_*.py you can loop over both wrapped types, for the exact same tests.

Copy link
Contributor

@rhaschke rhaschke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first sight, I don't see a particular reason for the double-free error - unfortunately.

Comment on lines 372 to 373
// this doesn't compile, should it?
//m.def("print_myobject3a_4", [](const std::shared_ptr<MyObject3a> *obj) { py::print((*obj)->toString()); });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. A shared_ptr is expected to be passed by some kind of reference (or value).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. A shared_ptr is expected to be passed by some kind of reference (or value).

I'm thinking of allowing const pointers, but definitely not non-const pointers.
Rationale: It could be useful to pass nullptr, that seems like a valid use case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nullptr can be represented by an empty shared_ptr. IMO we always want to pass a pointer to a held object.
The shared_ptr is just a convenient replacement for a raw pointer. Passing a pointer to a (shared) pointer doesn't make sense to me - no matter being it const or not: We cannot modify the pointer anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nullptr can be represented by an empty shared_ptr. IMO we always want to pass a pointer to a held object.
The shared_ptr is just a convenient replacement for a raw pointer. Passing a pointer to a (shared) pointer doesn't make sense to me - no matter being it const or not: We cannot modify the pointer anyway.

OK, let's keep this as-is for now. Let's just wait and see if anyone later has a strong need for it.

@jakobandersen jakobandersen force-pushed the smart_holder_shared_ptr branch from 79e625d to e950b28 Compare June 18, 2021 19:51
@jakobandersen jakobandersen requested a review from henryiii as a code owner June 18, 2021 19:51
@jakobandersen
Copy link
Contributor Author

@rwgk, thanks for the explanations. I think I made essentially the changes you suggested, and it is ready for another CI run.
The candidate fix is still the last commit (I'll squash the first two later), and before it the test_avl() fails with the casting error.

@rwgk
Copy link
Collaborator

rwgk commented Jun 18, 2021

Thanks, that looks great! Please don't worry about squashing. In my experience it's easiest and best to not mess with the history. The PR will be squashed automatically when merging.

@rwgk
Copy link
Collaborator

rwgk commented Jun 18, 2021

Please keep tagging me here for triggering the CI. 10 times a day is fine, no worries.

@jakobandersen jakobandersen force-pushed the smart_holder_shared_ptr branch from e950b28 to f653b62 Compare June 19, 2021 08:49
@jakobandersen
Copy link
Contributor Author

Ping @rwgk for CI :-).

Copy link
Contributor

@rhaschke rhaschke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@jakobandersen jakobandersen changed the title [WIP, smart_holder] return value policies allowed for shared_ptr [smart_holder] return value policies allowed for shared_ptr Jun 19, 2021


def test_avl_copy():
m.test_avl_copy()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be sure the copy and move behavior is as expected, something like:

    l = m.test_shp_copy()
    assert m.get_mtxt(l[0]) == "what we expect"

You probably have to allow for differences between platforms, using regular expressions, ideally this function from @rhaschke's still-open PR:

https://github.com/pybind/pybind11/pull/2916/files#diff-e614dc3a68da8c26363419d3c06816825b1b4344e35103c5dd004f7dcf3a6f84

def check_regex(expected, actual):
    result = re.match(expected + "$", actual)
    if result is None:
        pytest.fail("expected: '{}' != actual: '{}'".format(expected, actual))

For this you will also need the get_mtxt helper from test_class_sh_basic.cpp.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea,. Though, implemented as exact string compare, as in this case there should be no modification of the Foo objects after construction. The copy/move constructors are now deleted, just to ensure that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to think about this one. We don't want to leave any holes and want to know exactly what happens in all situations incl. different platforms. Maybe we need multiple Foo types with different combinations of (copy, move) x (ctor, assignment-op)? Is some or all of that obviously non-sensical? But let's get the CI green first with what you have right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anywhere where the pybind code is conditional on the class being moveable/copyable?
It may be good to add a move-only Foo and copyable Foo to test that indeed the object is not modified in any way, and it's only pointer copying.

Foo(Foo &&other) = delete;
std::string get_text() const {
std::string res = "Foo";
if (SerNo == 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The appveyor build is (and probably all our 30+ MSVC builds would be) failing:

C:\projects\pybind11\tests\test_class_sh_shared_ptr_copy_move.cpp(20): warning C4127: conditional expression is constant [C:\projects\pybind11\tests\pybind11_tests.vcxproj]
C:\projects\pybind11\tests\test_class_sh_shared_ptr_copy_move.cpp(22): warning C4127: conditional expression is constant [C:\projects\pybind11\tests\pybind11_tests.vcxproj]

My recommendation is to keep the code here as simple as possible (so it's easier to reason about) and to use this approach here:

py::classh<atyp>(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) {

Then set the initial mtxt from Python.
BTW in some more recent code I called it history, if you like that better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning fixed and member renamed to history.



def test_avl_copy():
m.test_avl_copy()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to think about this one. We don't want to leave any holes and want to know exactly what happens in all situations incl. different platforms. Maybe we need multiple Foo types with different combinations of (copy, move) x (ctor, assignment-op)? Is some or all of that obviously non-sensical? But let's get the CI green first with what you have right now.

@jakobandersen
Copy link
Contributor Author

Ping @rwgk for CI, now that Appveyor is green.

Copy link
Collaborator

@rwgk rwgk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anywhere where the pybind code is conditional on the class being moveable/copyable?

Yes, there are certainly conditionals, I think both compile-time (using type traits) and runtime (looking at refcounts). In and around cast.h. I don't know what all the details are. The behavior is generally well exercised in test_copy_move.cpp/py.

I'll trigger the CI now although the missing history tracking means we won't get much information.

std::string hisotry;
Foo(const std::string &hisotry_) : hisotry(hisotry_) {}
Foo(const Foo &other) = delete;
Foo(Foo &&other) = delete;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of the history member is completely lost: the move and copy assignment operators are not deleted, but also not tracked. How those behave is exactly what we want to test here. (At the moment idk what our conclusion will be when we see the results on all platforms, but it's important that we know what they are.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, there should now be tracking of the special member functions.

@rwgk
Copy link
Collaborator

rwgk commented Jun 24, 2021

The many MSVC failures are due to some unrelated issue with downloading catch.hpp. I see it in other PRs as well. I'm trying to find out what's wrong.

@rwgk
Copy link
Collaborator

rwgk commented Jun 24, 2021

Another class of multiple CI failures are sad old compilers that complain about implicit copy constructors. Annoyingly we have to make them explicit to keep those compilers happy. I usually simply add them in without #ifdef clutter, but a comment, e.g. in test_class_sh_inheritance.cpp (// Some compilers complain about implicitly defined versions of some of the following:). That's a little overkill but I didn't want to torture our CI too much or spend a lot of time messing with docker containers.

@rwgk
Copy link
Collaborator

rwgk commented Jun 24, 2021

Unfortunately we're currently blind to the MSVC behavior (2 x 18 builds), but everything else is working. Except for clang-tidy wanting two noexcept. See below.

Did you already clang-format the new code? (clang-format -style=file -i test_class_sh_shared_ptr_copy_move.cpp)
I'm trying to keep all smart_holder code clang-formatted, but currently that is not enforced in the CI runs.

Do you feel you could add in something like this (snippet from test_shared_ptrs.cpp):

        .def_property_readonly(                                                 
            "holder_copy",                                                      
            [](const SharedPtrRef &s) { return s.shared; },                     
            py::return_value_policy::copy)                                      

And similar for py::return_value_policy::move.
I'm not sure what exactly makes sense. I'd probably start by emulating the existing test. What I think we need to know is the history of the returned value (copied or moved), and also that of s.shared in the snippet above. If you feel that's too involved for this PR we could merge first, although I'd feel a bit uneasy about enabling the return value policies without being sure what exactly the behavior is. If it's out there for a while it will become difficult to change, even if inconsistent with the non-smart_holder behavior, and then we may have to explain any inconsistencies forever.

/__w/pybind11/pybind11/tests/test_class_sh_shared_ptr_copy_move.cpp:19:5: error: move constructors should be marked noexcept [performance-noexcept-move-constructor,-warnings-as-errors]
    Foo(Foo &&other) : history(other.history + "_MvCtor") {}
    ^
                      noexcept 
/__w/pybind11/pybind11/tests/test_class_sh_shared_ptr_copy_move.cpp:24:10: error: move assignment operators should be marked noexcept [performance-noexcept-move-constructor,-warnings-as-errors]
    Foo &operator=(Foo &&other) {
         ^
                                 noexcept 

@rwgk
Copy link
Collaborator

rwgk commented Jun 24, 2021

I just pushed a temporary patch to the smart_holder branch with a workaround for the Windows breakages. Could you please rebase your branch and force push? Then we should be able to get a full set of CI results.

@jakobandersen jakobandersen force-pushed the smart_holder_shared_ptr branch from 92a1e1f to 53a04e8 Compare June 24, 2021 19:15
@jakobandersen
Copy link
Contributor Author

I just pushed a temporary patch to the smart_holder branch with a workaround for the Windows breakages. Could you please rebase your branch and force push? Then we should be able to get a full set of CI results.

Sure, done.

@rwgk
Copy link
Collaborator

rwgk commented Jun 24, 2021

I pushed trivial automatic clang-tidy and clang-format fixes directly to your fork.
(Looks like it worked. This was the first time I tried that.)

@rwgk
Copy link
Collaborator

rwgk commented Jun 24, 2021

The CI is looking great now. But the behavior test I'm most worried about (something like the .def_property_readonly(..., py::return_value_policy::copy) and move) is still missing.

@jakobandersen
Copy link
Contributor Author

jakobandersen commented Jun 29, 2021

I pushed trivial automatic clang-tidy and clang-format fixes directly to your fork.
(Looks like it worked. This was the first time I tried that.)

Great, thanks.

The CI is looking great now. But the behavior test I'm most worried about (something like the .def_property_readonly(..., py::return_value_policy::copy) and move) is still missing.

I have pushed a few commits. Is it something along those lines you mean?
It tries retrieving an attribute of type FooShPtr and FooSmHld, defined via def_readonly, def_readwrite, and def_property_readonly, in policy combinations move, copy, and whatever the default is.

@rwgk
Copy link
Collaborator

rwgk commented Jun 29, 2021

Wow, thanks! Those macros are interesting! (apparently clang-format has a rough time with them)

It looks like the Foo's copy/move/operator= are never invoked, because they never appear in the history. See below.

In other words: When returning std::shared_ptr, the copy and move return value policies have no effect at all, compared to the default policy (I think it's automatic).

Probably not what I would have done, but if that's established behavior, and we have a test for it now to ensure the behavior is identical for std::shared_ptr as holder and smart_holder, I agree it's good to get this in.

No need for more tests IMO (e.g. move-only Foo, copy-only Foo). It would be nice to parameterize test_properties, which could probably be done with itertools.product, but maybe not worth the trouble (it only adds value if some but not all tests are failing).

I see the CI is green! I'll merge this now. If we want to do anything else we can just create new PRs.

$ grep -e _CpCtor -e _MvCtor -e _OpEqLv -e _OpEqRv tests/test_class_sh_shared_ptr_copy_move.*
tests/test_class_sh_shared_ptr_copy_move.cpp:    Foo(const Foo &other) : history(other.history + "_CpCtor") {}
tests/test_class_sh_shared_ptr_copy_move.cpp:    Foo(Foo &&other) noexcept : history(other.history + "_MvCtor") {}
tests/test_class_sh_shared_ptr_copy_move.cpp:        history = other.history + "_OpEqLv";
tests/test_class_sh_shared_ptr_copy_move.cpp:        history = other.history + "_OpEqRv";

@rwgk rwgk merged commit b9241c0 into pybind:smart_holder Jun 29, 2021
@github-actions github-actions bot added the needs changelog Possibly needs a changelog entry label Jun 29, 2021
@rwgk rwgk removed the needs changelog Possibly needs a changelog entry label Jun 29, 2021
@rwgk
Copy link
Collaborator

rwgk commented Jun 29, 2021

FYI: The Rebase merge was an accident (my intention was to Squash). I didn't realize the GUI keeps the previous choice. But it's OK, I'll pay more attention next time.

@jakobandersen
Copy link
Contributor Author

Thanks!

I tried a bit to make clang format not mangle it too much, but perhaps disabling it around that code may be better.

The semantics of the attributes/properties with the default policy also matches what happens if the code is directly transcribed to Boost.Python.
I guess a similar property test with unique_ptr could be interesting as well.

@rwgk
Copy link
Collaborator

rwgk commented Jun 29, 2021

Thanks!

I tried a bit to make clang format not mangle it too much, but perhaps disabling it around that code may be better.

Yeah, I was thinking the same.

The semantics of the attributes/properties with the default policy also matches what happens if the code is directly transcribed to Boost.Python.

Interesting to know. One more reason to go with the flow.

I guess a similar property test with unique_ptr could be interesting as well.

Again same thought, but I didn't want to bother you too much with requests :-)

@jakobandersen
Copy link
Contributor Author

The semantics of the attributes/properties with the default policy also matches what happens if the code is directly transcribed to Boost.Python.

Interesting to know. One more reason to go with the flow.

Indeed, there shouldn't be nasty surprises. Though, they do have a type of return value policy as well, but I'm not sure if they semantically map cleanly. But it may be worth checking out if there are strange edge cases. See e.g., https://wiki.python.org/moin/boost.python/CallPolicy

I guess a similar property test with unique_ptr could be interesting as well.

Again same thought, but I didn't want to bother you too much with requests :-)

No problem. I might make a PR for it at some point, now that I know the technical setup.

Btw. I couldn't immediately find it, but what is the status of the smart holder branch with respect to a merge into master?

@rwgk
Copy link
Collaborator

rwgk commented Jun 29, 2021

Btw. I couldn't immediately find it, but what is the status of the smart holder branch with respect to a merge into master?

Earlier this year I proposed a "road map" that @henryiii used as a starting point for https://github.com/pybind/pybind11/wiki/Roadmap.

We're still figuring out how to get this work done.

In the meantime I'm striving to merge everything from master asap, keeping the smart_holder behavior in line with master as much as possible.

rwgk added a commit that referenced this pull request Jun 29, 2021
rwgk added a commit to rwgk/pybind11 that referenced this pull request Mar 5, 2025
…major and/or influential contributors to smart_holder branch

* pybind#2904 by @rhaschke was merged on Mar 16, 2021
* pybind#3012 by @rhaschke was merged on May 28, 2021
* pybind#3039 by @jakobandersen was merged on Jun 29, 2021
* pybind#3048 by @Skylion007 was merged on Jun 18, 2021
* pybind#3588 by @virtuald was merged on Jan 3, 2022
* pybind#3633 by @wangxf123456 was merged on Jan 25, 2022
* pybind#3635 by @virtuald was merged on Jan 26, 2022
* pybind#3645 by @wangxf123456 was merged on Jan 25, 2022
* pybind#3796 by @wangxf123456 was merged on Mar 10, 2022
* pybind#3807 by @wangxf123456 was merged on Mar 18, 2022
* pybind#3838 by @wangxf123456 was merged on Apr 15, 2022
* pybind#3929 by @tomba was merged on May 7, 2022
* pybind#4031 by @wangxf123456 was merged on Jun 27, 2022
* pybind#4343 by @wangxf123456 was merged on Nov 18, 2022
* pybind#4381 by @wangxf123456 was merged on Dec 5, 2022
* pybind#4539 by @wangxf123456 was merged on Feb 28, 2023
* pybind#4609 by @wangxf123456 was merged on Apr 6, 2023
* pybind#4775 by @wangxf123456 was merged on Aug 3, 2023
* pybind#4921 by @iwanders was merged on Nov 7, 2023
* pybind#4924 by @iwanders was merged on Nov 6, 2023
* pybind#5401 by @msimacek was merged on Oct 8, 2024

Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com>
Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com>
Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com>
Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk>
Co-authored-by: Michael Šimáček <michael.simacek@oracle.com>
Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com>
Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi>
Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com>
rwgk added a commit that referenced this pull request Mar 5, 2025
* Pure `git merge --squash smart_holder` (no manual interventions).

* Remove ubench/ directory.

* Remove include/pybind11/smart_holder.h

* [ci skip] smart_ptrs.rst updates [WIP/unfinished]

* [ci skip] smart_ptrs.rst updates continued; also updating classes.rst, advanced/classes.rst

* Remove README_smart_holder.rst

* Restore original README.rst from master

* [ci skip] Minimal change to README.rst, to leave a hint that this is pybind11v3

* [ci skip] Work in ChatGPT suggestions.

* Change macro name to PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE

* Add a note pointing to the holder reinterpret_cast.

* Incorporate suggestion by @virtuald: #5542 (comment)

* Systematically change most py::class_ to py::classh under docs/

* Remove references to README_smart_holder.rst

This should have been part of commit eb550d0.

* [ci skip] Fix minor oversight (``class_`` -> ``py::class_``) noticed by chance.

* [ci skip] Resolve suggestion by @virtuald

#5542 (comment)

* [ci skip] Apply suggestions by @timohl (thanks!)

* #5542 (comment)
* #5542 (comment)
* #5542 (comment)

* Replace `classh : class_` inhertance with `using`, as suggested by @henryiii

#5542 (comment)

* Revert "Systematically change most py::class_ to py::classh under docs/"

This reverts commit ac9d31e.

* docs: focus on py::smart_holder instead of py::classh

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* Restore minor general fixes that got lost when ac9d31e was reverted.

* Remove `- smart_holder` from list of branches in all .github/workflows

* Extend classh note to explain whitespace noise motivation.

* Suggest `py::smart_holder` for "most situations for safety"

* Add back PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT

This define was
* introduced with #5286
* removed with #5531

It is has been in use here:
* https://github.com/pybind/pybind11_protobuf/blob/f02a2b7653bc50eb5119d125842a3870db95d251/pybind11_protobuf/native_proto_caster.h#L89-L101

Currently pybind11 unit tests for the two holder caster backwards compatibility traits

* `copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled`
* `move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled`

are missing.

* Add py::trampoline_self_life_support to all trampoline examples under docs/.

Address suggestion by @timohl:

* #5542 (comment)

Add to the "please think twice" note: the overhead for safety is likely in the noise.

Also fix a two-fold inconsistency introduced by revert-commit 1e646c9:

1.

py::trampoline_self_life_support is mentioned in a note, but is missing in the example right before.

2.

The section starting with

    To enable safely passing a ``std::unique_ptr`` to a trampoline object between

is obsolete.

* Fix whitespace accident (indentation) introduced with 1e646c9

Apparently the mis-indentation was introduced when resolving merge conflicts for what became 1e646c9

* WHITESPACE CHANGES ONLY in README.rst (list of people that made significant contributions)

* Add Ethan Steinberg to list of people that made significant contributions (for completeness, unrelated to smart_holder work).

* [ci skip] Add to list of people that made significant contributions: major and/or influential contributors to smart_holder branch

* #2904 by @rhaschke was merged on Mar 16, 2021
* #3012 by @rhaschke was merged on May 28, 2021
* #3039 by @jakobandersen was merged on Jun 29, 2021
* #3048 by @Skylion007 was merged on Jun 18, 2021
* #3588 by @virtuald was merged on Jan 3, 2022
* #3633 by @wangxf123456 was merged on Jan 25, 2022
* #3635 by @virtuald was merged on Jan 26, 2022
* #3645 by @wangxf123456 was merged on Jan 25, 2022
* #3796 by @wangxf123456 was merged on Mar 10, 2022
* #3807 by @wangxf123456 was merged on Mar 18, 2022
* #3838 by @wangxf123456 was merged on Apr 15, 2022
* #3929 by @tomba was merged on May 7, 2022
* #4031 by @wangxf123456 was merged on Jun 27, 2022
* #4343 by @wangxf123456 was merged on Nov 18, 2022
* #4381 by @wangxf123456 was merged on Dec 5, 2022
* #4539 by @wangxf123456 was merged on Feb 28, 2023
* #4609 by @wangxf123456 was merged on Apr 6, 2023
* #4775 by @wangxf123456 was merged on Aug 3, 2023
* #4921 by @iwanders was merged on Nov 7, 2023
* #4924 by @iwanders was merged on Nov 6, 2023
* #5401 by @msimacek was merged on Oct 8, 2024

Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com>
Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com>
Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com>
Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk>
Co-authored-by: Michael Šimáček <michael.simacek@oracle.com>
Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com>
Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi>
Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com>

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com>
Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com>
Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com>
Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk>
Co-authored-by: Michael Šimáček <michael.simacek@oracle.com>
Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com>
Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi>
Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants