-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
squash-merge smart_holder branch into master #5542
base: master
Are you sure you want to change the base?
squash-merge smart_holder branch into master #5542
Conversation
…, advanced/classes.rst
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only reviewed documentation.
I note that the documentation doesn't mention PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
at all. In my usage of pybind11, I enable that macro because (a) I'm lazy and (b) I assumed that because smart holder is awesome that eventually when it got merged it would become the default and then we could get rid of the macro.
I don't understand the backwards compatibility argument for not making smart holder the default, but if there really are known backwards compatibility issues and it's not going to be the default I think I lean towards getting rid of the macro? When reading pybind11 code it would be good to know "this is definitely using smart holder" and "this is definitely not" without having to know which compile flags are set.
Edit: lol clearly I didn't read the PR description carefully enough
docs/classes.rst
Outdated
|
||
.. note:: | ||
|
||
Starting with pybind11v3, it is recommended to use `py::classh` in most |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's another PR, but since it's the recommended default, we probably should change all of the documentation to use it, since users often just copy/paste from the documentation without thinking about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was on the fence, thanks for the feedback! I'll change it around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in commit ac9d31e.
(I made a fast pass, followed by a careful slow pass reviewing the generated html.)
@virtuald As someone who's used the smart_holder branch for a long time, what's you opinion about this question?
It only makes a difference to people who have been using the smart_holder branch before (probably a relative small number?). When they switch to pbyind11v3, they'll be forced to adjust their code (remove pybind11/smart_holder.h includes). Is that a hardship, or just a minor inconvenience? |
.. seealso:: | ||
|
||
The file :file:`tests/test_smart_ptr.cpp` contains a complete example | ||
that demonstrates how to work with custom reference-counting holder types | ||
in more detail. | ||
|
||
|
||
Be careful not to accidentally undermine automatic lifetime management |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I missed it, but is this a new section that has nothing to do with smart holder? Maybe move it to a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's definitely odd here, but it's based on this existing part:
pybind11/docs/advanced/smart_ptrs.rst
Lines 50 to 113 in d8565ac
One potential stumbling block when using holder types is that they need to be | |
applied consistently. Can you guess what's broken about the following binding | |
code? | |
.. code-block:: cpp | |
class Child { }; | |
class Parent { | |
public: | |
Parent() : child(std::make_shared<Child>()) { } | |
Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */ | |
private: | |
std::shared_ptr<Child> child; | |
}; | |
PYBIND11_MODULE(example, m) { | |
py::class_<Child, std::shared_ptr<Child>>(m, "Child"); | |
py::class_<Parent, std::shared_ptr<Parent>>(m, "Parent") | |
.def(py::init<>()) | |
.def("get_child", &Parent::get_child); | |
} | |
The following Python code will cause undefined behavior (and likely a | |
segmentation fault). | |
.. code-block:: python | |
from example import Parent | |
print(Parent().get_child()) | |
The problem is that ``Parent::get_child()`` returns a pointer to an instance of | |
``Child``, but the fact that this instance is already managed by | |
``std::shared_ptr<...>`` is lost when passing raw pointers. In this case, | |
pybind11 will create a second independent ``std::shared_ptr<...>`` that also | |
claims ownership of the pointer. In the end, the object will be freed **twice** | |
since these shared pointers have no way of knowing about each other. | |
There are two ways to resolve this issue: | |
1. For types that are managed by a smart pointer class, never use raw pointers | |
in function arguments or return values. In other words: always consistently | |
wrap pointers into their designated holder types (such as | |
``std::shared_ptr<...>``). In this case, the signature of ``get_child()`` | |
should be modified as follows: | |
.. code-block:: cpp | |
std::shared_ptr<Child> get_child() { return child; } | |
2. Adjust the definition of ``Child`` by specifying | |
``std::enable_shared_from_this<T>`` (see cppreference_ for details) as a | |
base class. This adds a small bit of information to ``Child`` that allows | |
pybind11 to realize that there is already an existing | |
``std::shared_ptr<...>`` and communicate with it. In this case, the | |
declaration of ``Child`` should look as follows: | |
.. _cppreference: http://en.cppreference.com/w/cpp/memory/enable_shared_from_this | |
.. code-block:: cpp | |
class Child : public std::enable_shared_from_this<Child> { }; |
I think it'll be best to move it only after this PR is merged, to not make this PR even more difficult to review.
No promises were really made about smart_holder and if one is using (what is effectively) a fork of a project then you just accept whatever comes with it, so I think whatever makes sense would be good. Since everything in that file is marked as deprecated, it seems good to get rid of it. Looking at my own usage, I used |
Thanks! I'm glad to get rid of it completely. I'll remove my note from the PR description.
I want to nudge people towards not ever using that macro in production situations, with this in mind:
One idea is to rename the macro, .e.g.: So in your case the old name would become a no-op, some tests would break, which you'd then fix by changing your I'm trying to think ahead, please chime in if I'm going off the tracks: You wouldn't be able to go back to an older pybind11 version easily. But then again, if your code requires the smart_holder feature, that wouldn't work anyway. So all good? But what will people do if I'm overlooking something and they have a reason to use They could go through their code to change |
…T_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
This should have been part of commit eb550d0.
@timohl, could you help with a second set of eyes reviewing the documentation changes and the PR description? |
@henryiii, can you think of anything that I might be overlooking? |
@lanctot, could you help reviewing the documentation changes and the PR description? Will this work for https://github.com/google-deepmind/open_spiel? |
@isuruf for awareness mainly. — But please let me know if you believe this PR will cause issues for Conda. (I don't think so; I believe any adjustments that may be needed on the Conda side (for migrating to pybind11 v3.0.0) will be for changes that exist on pybind11 master already.) |
Sure. I can check tomorrow. |
Hi @rwgk, My short-term solution for OpenSpiel will be to pin our checkout to a commit: (see google-deepmind/open_spiel#1315). Then I'll help whoever pulls in v3 at Google. Will this work for us.. i.e. will the smart_holder branch continue to exist post-merge with master, or should I fork it? |
@lanctot wrote:
I'm happy to keep it around indefinitely, although I'd want to rename it, e.g. archive/smart_holder. I'll be sure to coordinate with you before doing that. And I could hold off for a few months if the renaming causes hardships. |
A simple renaming will be an easy fix for us, so happy to coordinate with you to change our script when the name changes. I'll update that PR when it's renamed. Thanks! |
We have a dedicated pybind11 Slack workspace, and this morning there were discussions regarding the choice of
Given these priorities, the specific name choice ( I'm open to changing the name — now is the time — but only to one with the same number of characters to maintain these benefits. If anyone has an alternative solution that meets all three goals above, I’d be happy to discuss it. |
A bad name is a debt we carry to eternity; it makes it harder to learn, harder to use, and harder to understand. "PyClash" is what I see when I read this, and it's not clear what it is supposed to mean. Idea 1: Change the default What breaking changes would be introduced by making the default holder IMO, this is a 3.0 release, so we are allowed to make changes like these if it generally an improvement and doesn't break most users. Maybe we could even add a define that restores the 2.x style default for emergencies. py::class_<Thing>(...) // Smart holder
py::class_<Thing, std::unique_ptr<Thing>>(...) // Classic So what scale breakages are we talking about? 10%? 1%? 0.1%? Do we know? Idea 2: Give up on the "number of characters" requirement This seems like a really trivial requirement. When we replaced And people don't have to transition! Is there a benefit to replacing We already tell all our users to do Some ideas: py::cls<Thing>(...) // Smart holder
py::class_<Thing>(...) // Classic Idea 3: Don't recommend the shortcut or don't provide the shortcut This is, I think, my favorite one, assuming there are downsides to Idea 1, and the one most inline with the way pybind11 has been designed in the past: use (We could also secretly add this, or mention it once, but I think just showing users how to set it up, just like py::class_<Thing, py::smart_holder>(...) // Smart holder
py::class_<Thing>(...) // Classic
// Or, user setup shortcut:
template <typename type_, typename... options>
using py_class_h = class_<type_, smart_holder, options...>;
py_class_h<Thing>(...) // Smart holder If none of those works, then |
Also, why not this? template <typename type_, typename... options>
using classh = class_<type_, smart_holder, options...>; Why is this physically a different class? |
Thanks for asking that question! I'm testing commit ff7c087. The other day (when @petersteneteg reported his problem) I was thinking about using (I totally don't remember anymore why I decided to use inheritance.) |
I'm happy with "sclass"! Especially since someone other than me suggested it. 😁 To all Germans, "S class" is this. — Not a bad association to evoke. I'll make that change a little later.
There are so many existing and much worse naming accidents in this world ... if I balance that with the countless hours lost dealing with the fallout from not holding up the "three goals", I feel very sad. For entertainment, here is my all-time favorite naming accident: It was never corrected! I always thought that's the smartest thing they could do. It's just a name, but changing it would wipe out so many hours of human time. |
Testing passes! — @petersteneteg I think that resolves your issue, excellent. |
What about Idea 1 and Idea 3? Do we know what would break if we changed the default? Everything would just get a little larger and slower (I think you measured this? Or was it just the effect of having smart_holder present, but not using it)? Trying to get a feel of how much it's "let's not be risky" vs. a real risk. Do we have any large codebases where it's been tried? Also, if it is a bit slower and larger (looks like And idea 3 was just to not provide the shortcut at all, and let users write the |
I'm sure if they had seen it before shipping it, they would have corrected it. That's why I want to get this right, we haven't shipped it yet!
|
Updated robotpy-build branch to see if tests pass ... and they do, and the migration diff is really small. Interestingly enough, I found it simpler to do I haven't really looked at the code for this branch yet, but I assume that if it's effectively the same thing I've been using for the last few years then it's pretty solid. As the initial person who suggested |
It's not about spelling, the point is: What are the consequences of a move we can make, for the ecosystem at large? Two (alternative) choices we have:
That idea has far more value IMO:
But, in my last days at Google, I ran global testing with There are a bunch of wild things like that in the wild. I also had to patch on the order of ten (from memory) source files around the codebase to insert explicit That's not a lot of changes, but I translate that to: Making How can we get their input? |
Two years from now, no one will care about the number of characters in the name. They will care about what "classh" is and why they are supposed to use it instead of Unless we really want to push smart_holder as the default, I'd be fine to not provide a shortcut at all, and just document how to set up a shortcut. Then everyone who really wants a short name can pick whatever name they want. We can document the two lines required to provide a shortcut name. If we really wanted to provide our own name later, we can always add it later, maybe based on what people seem to be using. This also gives people a better mental model of how this works. If we do really want to push smart_holder as the default, let's make it the default. To be clear, 90% of our users don't care if they can convert things to a shared_ptr or not. They are happy with what we have provided for years. Having the ability to do this is fantastic for certain use cases, but I'm not convinced it needs to be the default over I'll have to play with my own benchmarking and code a bit before I can form my own opinion here, I've been going off of what I'm told so far. I'm really curious to see a very hot multithreaded loop compared. |
I don't. — I only want to recommend it, as I explained originally. I was responding to your "Idea 1" and your follow-on question "Do we know what would break if we changed the default?" — What I quoted above just sends us into circles. I cannot spend a lot more time on this. TBH, suggestions to not provide an alias at all just leave me shaking my head. How much harm can it do to provide one simple alias that makes it possible for people to experiment easily? And if they like it, the alias used will be uniform across the ecosystem. I'll backtrack, so that this PR merely adds the option to easily use the
I agree, the other 10% (a couple thousand people I guess) will probably find the recommendation and figure it out from there. |
Not sure what is meant by "access", I assume you mean to dereference it or getting the raw pointer, and that is most definitely not guarded by any mutex what so ever. Copying or assigning the shared_ptr will have to do some atomic operations on the refcounts in the control block. |
I don't care about what the default is as long as I can continue to use it. I would like to still have the What I really want is to have this merged now! And please stop having @rwgk running in circles to get this merged, he has already done monumental effort which I am truly grateful for! |
It will get merged. I'm worried only about what we are asking users to use. I'm just trying to review this for the long-term benefit of pybind11. And expecting 0 code changes when a branch (even one that's been around for a while) is merged into a project is not reasonable. There are no released versions of this, so no one will be surprised by the upgrade - every single branch user will need to switch to master or a released version of pybind11. We are only talking about two lines that would need to be added! Regardless of what we do, I am fine to have a "hidden" (undocumented)
Comments like this are not helpful. Stating this is something you want is fine (and I'm completely aware there's a significant number of users for whom this is vital, that's why this branch exists. That's why I suggested this be prepared for merging for 3.0 in the first place.) But demanding that something be merged because you really want it and it was a lot of work will get nowhere on any reputable project. I think @rwgk already knows this, but I'm always completely happy to make any change I suggest. I can update the docs if needed.
That's what I meant by "push it as the default". If we update all examples to use it, that's "pushing it as the default". For this PR, I think having an undocumented |
This reverts commit ac9d31e.
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
I left one mention of the shortcut, but focused on the explicit version. I think that's better for this PR, and maybe we can think about followups. |
.github/workflows/ci.yml
Outdated
@@ -7,6 +7,7 @@ on: | |||
branches: | |||
- master | |||
- stable | |||
- smart_holder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left over? (also on other CI files)
c08b428
to
d3f9f93
Compare
I added 4 commits. The first two restore or add mechanical fixes: This one clarifies why the number of characters in
This one strengthens the case for
I feel the pendulum has swung a little too far in the opposite direction. Previously the emphasis on I'm OK, though, to merge this PR as it is now. |
Description
Notes:
For easy reference: readthedocs preview for this PR.
For benchmarking results, see PR [smart_holder] smart_holder / master benchmarking 2025-02-16 #5529.
The only one existing (on master) test that is changed slightly is test_class.cpp/py. This is only to accommodate testing with
PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
(this exists primarily to ensure thatpy::smart_holder
is fully compatible with all existing functionality).All code was formally reviewed at least once. Much of the code was formally reviewed multiple times (as it was moved around between branches).
The only changes not reviewed already were merged (into smart_holder) under PR [smart_holder] Remove obsolete
detail::type_info::default_holder
member. #5541. All other code has been in production use at Google since September 2024. The smart_holder branch has been in production use at Google since February 2021. I.e. this code is extremely thoroughly tested. Additionally, testing via PyCLIF-pybind11 provided thousands of diverse use cases that needed to satisfy many million unit tests (the PyCLIF-pybind11 success was about 99.999%).The only significant updates under
docs/
are indocs/advanced/smart_ptrs.rst
anddocs/advanced/classes.rst
. The rest of thedocs/
updates are mostly mechanical, to replacepy::class_
withpy::classh
(based on this rationale).For easy reference, other recent PRs enabling this one:
PYBIND11_INTERNALS_VERSION
s4
and5
#5530The following is for this PR at commit a4cfd38:
git diff
counts under include/:git diff master include/ | grep '^-' | grep -v '^---' | wc -l
)git diff master include/ | grep '^+' | grep -v '^+++' | wc -l
)I.e., the existing production code is barely touched.
Similarly for tests/:
For more detail, results produced by
cloc --diff
for include/ between the master branch and this PR:I.e., this PR increases the production code size by 7.8%.
Suggested changelog entry:
To be covered in the upgrade guide:
py::classh&
gotcha (Peter's problem)