Skip to content

gh-154044: Fix data race on descriptor __qualname__ in FT build#154691

Open
deadlovelll wants to merge 1 commit into
python:mainfrom
deadlovelll:gh-154044-descr-race
Open

gh-154044: Fix data race on descriptor __qualname__ in FT build#154691
deadlovelll wants to merge 1 commit into
python:mainfrom
deadlovelll:gh-154044-descr-race

Conversation

@deadlovelll

@deadlovelll deadlovelll commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Fix data race on descriptor __qualname__ in FT build

For more details see gh-154044

Comment thread Objects/descrobject.c
#include "pycore_modsupport.h" // _PyArg_UnpackStack()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount()
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_ACQUIRE()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not needed

Suggested change
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_ACQUIRE()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it's implicit, the rest of the codebase includes it explicitly when using FT_ATOMIC_*, so Its better leave it

Comment thread Objects/descrobject.c
return Py_NewRef(qualname);
}

Py_BEGIN_CRITICAL_SECTION(self);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think you can get away without using a critical section. Something like this:

static PyObject *
descr_get_qualname(PyObject *self, void *Py_UNUSED(ignored))
{
    PyDescrObject *descr = (PyDescrObject *)self;
    PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(descr->d_qualname);
    if (qualname == NULL) {
        qualname = calculate_qualname(descr);
#ifdef Py_GIL_DISABLED
        PyObject *expected = NULL;
        if (!_Py_atomic_compare_exchange_ptr(&descr->d_qualname, &expected, qualname)) {
            Py_DECREF(qualname);
            qualname = expected;
        }
#else
        descr->d_qualname = qualname;
#endif
    }
    return Py_NewRef(qualname);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

CAS is a valid solution, but id prefer CS to avoid #ifdef and keep a single compute.

But your snippet contains UB, because if calculate_qualname fails it will return NULL and code will try to:

1 Py_DECREF the NULL after CAS
2 Py_NewRef on NULL

@deadlovelll deadlovelll Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

BTW thanks for the review! Means a lot

kinds = {"method_descriptor", "getset_descriptor", "wrapper_descriptor"}
descrs = [
v
for tp in (str, bytes, list, dict, set, int, float, tuple,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do wee need to test all of these types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do wee need to test all of these types?

Yes. These mirror the builtin types from the issue's reproducer, so the test reproduces the exact reported case

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants