GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — MRO: Behavior change from 3.10 to 3.11: multiple inheritance issue with C-API (pybind11) · Issue #92678 · python/cpython

MRO: Behavior change from 3.10 to 3.11: multiple inheritance issue with C-API (pybind11) #92678

New issue
Copy link
New issue
Copy link
Closed
Closed
MRO: Behavior change from 3.10 to 3.11: multiple inheritance issue with C-API (pybind11)#92678
Copy link
Assignees
markshannonFidget-Spinner
Labels
3.11only security fixesonly security fixestype-featureA feature request or enhancementA feature request or enhancement

Description

@Skylion007
Skylion007
opened on May 11, 2022
Issue body actions

Bug report

  • We have started testing the 3.11 beta branch in pybind11 and have found an issue where a class constructed using the C-API that inherits from two base classes that have different tpget_set implementations. Interestingly, this issue occurs when the first base does not have dynamic attributes enabled, but another base class does. I tried making the child class also have dynamic attributes (and therefore a larger size to store the dict etc.), but I still get a PyTypeReady failed. We've been encountering this issue since alpha 3, but I have not found any issues on BPO of other people having similar issues. I am wondering what changed and how we can fix our API usage to allow for full support of Python 3.11 as it enters beta.

  • I suspect it's something very subtle with how we are constructing our Python types, but there was nothing in the 3.11 migration guide that flags this issue. Any thoughts on how to fix issue? Is this known behavior or a bug? Or is it something that should be added to the migration guide?

  • @vstinner I know you are very familiar with the C-API and helped us deal with some of the other API changes, any thoughts?

Here is the failing test: pybind/pybind11#3923

  • Your environment
  • CPython versions tested on: 3.11
  • Operating system and architecture: Ubuntu-latest

Linked PRs

  • gh-92678: Fix tp_dictoffset inheritance. #95596

Activity

Next
  1. Skylion007
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on May 11, 2022
  2. JelleZijlstra commented on May 11, 2022

    @JelleZijlstra
    JelleZijlstra
    on May 11, 2022
    Member
    More actions

    Do you have a minimal example that reproduces the issue, or failing that an example of a piece of pybind11 code demonstrating the problem?

    You say that the issue first appeared in 3.11a3. Are you able to bisect further to a specific commit?

  3. Skylion007 commented on May 11, 2022

    @Skylion007
    Skylion007
    on May 11, 2022
    Author
    More actions

    I have looked through the commit list, but was unable to find any suspicious commits. @henryiii @rwgk Any thoughts on which commit might breaking out test?

  4. Skylion007 commented on May 11, 2022

    @Skylion007
    Skylion007
    on May 11, 2022
    Author
    More actions

    @JelleZijlstra The failing test can be found here: pybind/pybind11#3923

  5. vstinner commented on May 11, 2022

    @vstinner
    vstinner
    on May 11, 2022
    Member
    More actions

    PyTypeReady failed

    Do you get an exception? What is the exception?

  6. vstinner commented on May 11, 2022

    @vstinner
    vstinner
    on May 11, 2022
    Member
    More actions

    @JelleZijlstra The failing test can be found here: pybind/pybind11#3923

    Ah, the "Upstream / 🐍 3.11 dev • ubuntu-latest • x64 (pull_request)" failed with:

    ImportError while loading conftest '/home/runner/work/pybind11/pybind11/tests/conftest.py'.
    conftest.py:16: in <module>
        import pybind11_tests  # noqa: F401
    E   ImportError: VanillaDictMix1: PyType_Ready failed (TypeError: mro() returned base with unsuitable layout ('pybind11_tests.multiple_inheritance.WithDict'))!
    

    This error message comes from the mro_check() function of typeobject.c:

            if (!PyType_IsSubtype(solid, solid_base(base))) {
                PyErr_Format(
                    PyExc_TypeError,
                    "mro() returned base with unsuitable layout ('%.500s')",
                    base->tp_name);
                return -1;
            }
    

    with:

    static PyTypeObject *
    solid_base(PyTypeObject *type)
    {
        PyTypeObject *base;
    
        if (type->tp_base)
            base = solid_base(type->tp_base);
        else
            base = &PyBaseObject_Type;
        if (extra_ivars(type, base))
            return type;
        else
            return base;
    }
    
    Reacted by Aaron Gokaslan
  7. vstinner commented on May 11, 2022

    @vstinner
    vstinner
    on May 11, 2022
    Member
    More actions

    extra_ivars() used by solid_base() is different in Python 3.11, the following code path was removed in 3.11:

        if (type->tp_dictoffset && base->tp_dictoffset == 0 &&                      
            type->tp_dictoffset + sizeof(PyObject *) == t_size &&    
            type->tp_flags & Py_TPFLAGS_HEAPTYPE)
            t_size -= sizeof(PyObject *);
    

    Python 3.11 changes:

    • commit 8319114
    • commit a8b9350

    I don't understand well the purpose of the extra_ivars(type, base) function.

  8. vstinner commented on May 11, 2022

    @vstinner
    vstinner
    on May 11, 2022
    Member
    More actions

    this issue occurs when the first base does not have dynamic attributes enabled, but another base class does.

    pybind11 code:

    
    /// Give instances of this type a `__dict__` and opt into garbage collection.
    inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
        auto *type = &heap_type->ht_type;
        type->tp_flags |= Py_TPFLAGS_HAVE_GC;
        type->tp_dictoffset = type->tp_basicsize;           // place dict at the end
        type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
        type->tp_traverse = pybind11_traverse;
        type->tp_clear = pybind11_clear;
    
        static PyGetSetDef getset[] = {
            {const_cast<char *>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr},
            {nullptr, nullptr, nullptr, nullptr, nullptr}};
        type->tp_getset = getset;
    }
    

    It sets type->tp_dictoffset: it may change extra_ivars() result.

  9. Skylion007 commented on May 12, 2022

    @Skylion007
    Skylion007
    on May 12, 2022
    Author
    More actions

    @vstinner Hmm, any idea how to ensure the ivars is happy then?

  10. vstinner commented on May 12, 2022

    @vstinner
    vstinner
    on May 12, 2022
    Member
    More actions

    @vstinner Hmm, any idea how to ensure the ivars is happy then?

    I have no idea. It would help to have a simpler reproducer than pybind11 test suite.

  11. Skylion007 commented on May 14, 2022

    @Skylion007
    Skylion007
    on May 14, 2022
    Author
    More actions

    @JelleZijlstra I have a failing portion of PyBind11 test suite now. Any ideas how to debug this further?

  12. JelleZijlstra commented on May 16, 2022

    @JelleZijlstra
    JelleZijlstra
    on May 16, 2022
    Member
    More actions

    Sorry, I am not familiar with this area of the code so I don't think I can be of more help than Victor. As Victor said, it would be helpful to have a self-contained example that doesn't rely on pybind11.

    Also, would be good to know how big the impact is on pybind11: is this something a lot of users are likely to run into, or just an obscure edge case that your test suite happens to cover?

  13. henryiii commented on May 16, 2022

    @henryiii
    henryiii
    on May 16, 2022
    Contributor
    More actions

    Here's a pybind11 reproducer:

    #include <pybind11/pybind11.h>
    
    namespace py = pybind11;
    
    struct Vanilla {};
    
    struct WithDict {};
    struct VanillaDictMix2 : WithDict, Vanilla {};
    struct VanillaDictMix1 : Vanilla, WithDict {};
    
    PYBIND11_MODULE(example, m) {
        py::class_<Vanilla>(m, "Vanilla").def(py::init<>());
        py::class_<WithDict>(m, "WithDict", py::dynamic_attr()).def(py::init<>());
        py::class_<VanillaDictMix2, WithDict, Vanilla>(m, "VanillaDictMix2").def(py::init<>()); // OK
        py::class_<VanillaDictMix1, Vanilla, WithDict>(m, "VanillaDictMix1").def(py::init<>()); // Broken in 3.11
    }

    (Names chosen to match the test suite, which is why 2 comes before 1 - 2 works, 1 doesn't)

    Compile and run (fish syntax):

    $ clang++ -Wall -shared -std=c++14 -undefined dynamic_lookup (pipx run --python=~/.pyenv/versions/3.11.0b1/bin/python pybind11 --includes | string split " ") -g example.cpp -o example(~/.pyenv/versions/3.11.0b1/bin/python3-config --extension-suffix)
    $ ~/.pyenv/versions/3.11.0b1/bin/python -c "import example"
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    ImportError: VanillaDictMix1: PyType_Ready failed (TypeError: mro() returned base with unsuitable layout ('example.WithDict'))!

    Note that WithDict has a __dict__, and Vanilla does not. Both DictMix's should.

    Reacted by Aaron Gokaslan
  14. vstinner commented on May 17, 2022

    @vstinner
    vstinner
    on May 17, 2022
    Member
    More actions

    @markshannon: You made the two commits changing extra_ivars(). Do you have any idea why Python 3.11 behaves differently? Is it a deliberate choice?

  15. markshannon
    removed
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on May 17, 2022
  16. 63 remaining items

  17. rwgk commented on Aug 4, 2022

    @rwgk
    rwgk
    on Aug 4, 2022
    More actions

    @Skylion007 could you confirm that #95596 is enough for pybind11 to work with 3.11 before the better API is provided in 3.12?

    TL;DR: Yes, we're good (pybind11).

    Details:

    I built Python 3.11 from scratch (configure; make install) for two versions:

    • "latest" = 5ac3d0f (happened to be HEAD of 3.11 when I ran git pull)
    • "back" = d8df7e0 (just before GH-92678: Fix tp_dictoffset inheritance. (GH-95596) (GH-95604))

    I also backed out the Py_TPFLAGS_MANAGED_DICT changes from pybind11 (based on pybind11 master @ pybind/pybind11@ba5ccd8), diff below.

    With that:

    • "latest": all unit pybind11 unit tests pass.
    • "back": the PyType_Ready error is back, see below.

    For completeness:

    • WithOUT the diff below, both "latest" and "back" pass the pybind11 unit tests.
    • I'm seeing a test_embed ModuleNotFoundError for both "latest" and "back", with and without the diff below, but that's clearly unrelated to this MRO issue. Need to drill down.
    diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h
    index db7cd8ef..65e223a3 100644
    --- a/include/pybind11/attr.h
    +++ b/include/pybind11/attr.h
    @@ -345,11 +345,7 @@ struct type_record {
    
             bases.append((PyObject *) base_info->type);
    
    -#if PY_VERSION_HEX < 0x030B0000
             dynamic_attr |= base_info->type->tp_dictoffset != 0;
    -#else
    -        dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
    -#endif
    
             if (caster) {
                 base_info->implicit_casts.emplace_back(type, caster);
    diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h
    index 42720f84..3db8429b 100644
    --- a/include/pybind11/detail/class.h
    +++ b/include/pybind11/detail/class.h
    @@ -549,12 +549,8 @@ extern "C" inline int pybind11_clear(PyObject *self) {
     inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
         auto *type = &heap_type->ht_type;
         type->tp_flags |= Py_TPFLAGS_HAVE_GC;
    -#if PY_VERSION_HEX < 0x030B0000
         type->tp_dictoffset = type->tp_basicsize;           // place dict at the end
         type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
    -#else
    -    type->tp_flags |= Py_TPFLAGS_MANAGED_DICT;
    -#endif
         type->tp_traverse = pybind11_traverse;
         type->tp_clear = pybind11_clear;
    

    With "back" version of 3.11:

    Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
    ImportError while loading conftest '/usr/local/google/home/rwgk/forked/pybind11/tests/conftest.py'.
    conftest.py:16: in <module>
        import pybind11_tests
    E   ImportError: VanillaDictMix1: PyType_Ready failed: TypeError: mro() returned base with unsuitable layout ('pybind11_tests.multiple_inheritance.WithDict')
    
    Reacted by Gregory P. Smith
    Reacted by Gregory P. Smith
  18. pablogsal
    removed
    release-blocker
    on Aug 4, 2022
  19. pablogsal commented on Aug 4, 2022

    @pablogsal
    pablogsal
    on Aug 4, 2022
    Member
    More actions

    Thanks a lot for checking!

    I'm closing this issue, let's reopen if we discover that we missed anything

  20. pablogsal
    closed this as completedon Aug 4, 2022
  21. Repository owner moved this from Todo to Done in Release and Deferred blockers 🚫on Aug 4, 2022
  22. pablogsal
    added a commit that references this issue on Aug 4, 2022

    [3.11] gh-92678: Correct return values for errors in PyInit__testcapi (…

    cb02dc0
  23. vstinner commented on Aug 5, 2022

    @vstinner
    vstinner
    on Aug 5, 2022
    Member
    More actions

    "latest": all unit pybind11 unit tests pass.

    Yeah! That's cool! Thanks for fixing pybind11 ;-)

  24. rwgk commented on Aug 5, 2022

    @rwgk
    rwgk
    on Aug 5, 2022
    More actions

    "latest": all unit pybind11 unit tests pass.

    Yeah! That's cool! Thanks for fixing pybind11 ;-)

    To clarify (especially for people looking here later):

    • I don't have a full understanding, I only jumped in to help with the last minute testing.
    • The diff under MRO: Behavior change from 3.10 to 3.11: multiple inheritance issue with C-API (pybind11) #92678 (comment) is not even in a PR yet (but we will take care of it).
    • @Skylion007 is driving this in general: question for him, did we actually make a net change after applying that diff?
    • I believe we still have to make changes for 3.12 compatibility, with fairly high priority, so that we don't have a last minute situation again next year. Is that correct @Skylion007?
    Reacted by Gregory P. Smith
  25. rwgk
    added a commit that references this issue on Aug 5, 2022

    Undo attr.h, detail/class.h changes made under PR pybind#3923. See py…

    790e44e
  26. miss-islington
    added a commit that references this issue on Aug 9, 2022

    pythonGH-92678: Document that you shouldn't be doing your own diction…

    e88e32d
  27. markshannon
    added 2 commits that reference this issue on Aug 9, 2022

    GH-92678: Document that you shouldn't be doing your own dictionary of…

    8d37c62

    GH-92678: Document that you shouldn't be doing your own dictionary of…

    c96b26c
  28. iritkatriel
    added a commit that references this issue on Aug 11, 2022

    pythonGH-92678: Document that you shouldn't be doing your own diction…

    8feb148
  29. vstinner commented on Aug 17, 2022

    @vstinner
    vstinner
    on Aug 17, 2022
    Member
    More actions

    See also issue #96046 which might be related.

  30. rwgk
    added a commit that references this issue on Aug 17, 2022

    Undo attr.h, detail/class.h changes made under PR pybind#3923. See py…

    f10c2fd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @Fidget-Spinner
    Fidget-Spinner
  • @markshannon
    markshannon

Labels

3.11only security fixesonly security fixestype-featureA feature request or enhancementA feature request or enhancement

Projects

  • Release and Deferred blockers 🚫
    Status
    Done
    Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.