Skip to content

Conversation

@LindaSummer
Copy link
Contributor

@LindaSummer LindaSummer commented Oct 24, 2025

Issue

#133467

Proposed Changes

  • Fix the race of PyTypeObject::tp_base between typemember __base__ and set of __bases__.
  • Add test case in unit test.

Comment

Root cause

Since the __bases__'s getter and setter are protetcted by TYPE_LOCK.

ASSERT_TYPE_LOCK_HELD();

_PyType_GetBases(PyTypeObject *self)
{
PyObject *res;
BEGIN_TYPE_LOCK();
res = lookup_tp_bases(self);
Py_INCREF(res);
END_TYPE_LOCK();
return res;
}

So it is safe for concurrency.

But the update of bases will re-evaluate the tp_base again and would be in race with the direct access of __base__.

{"__base__", _Py_T_OBJECT, offsetof(PyTypeObject, tp_base), Py_READONLY},

How to fix

At the beginning I try to use the TYPE_LOCK to protect the tb_base for __base__ access.

But it's a directly memory access by offset and I have no more idea except StopTheWorld.

So I add a STW for the updating of tp_base in setter.

Please let me know if we have a better way for it. 😊

TSAN output for this race

Here is the TSAN core output of this race.

WARNING: ThreadSanitizer: data race (pid=1754505)
  Write of size 8 at 0x7fb1ed269910 by thread T27:
    #0 type_set_bases_unlocked /home/someuser/project/cpython/Objects/typeobject.c:1924:19 (python+0x6b265a) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #1 type_set_bases /home/someuser/project/cpython/Objects/typeobject.c:2009:15 (python+0x6b265a)
    #2 getset_set /home/someuser/project/cpython/Objects/descrobject.c:250:16 (python+0x5a8fe6) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #3 type_setattro /home/someuser/project/cpython/Objects/typeobject.c:6532:19 (python+0x6a2ac5) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #4 PyObject_SetAttr /home/someuser/project/cpython/Objects/object.c:1476:15 (python+0x649360) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #5 _PyEval_EvalFrameDefault /home/someuser/project/cpython/Python/generated_cases.c.h:10750:27 (python+0x7bd953) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #6 _PyEval_EvalFrame /home/someuser/project/cpython/./Include/internal/pycore_ceval.h:121:16 (python+0x79c3a0) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #7 _PyEval_Vector /home/someuser/project/cpython/Python/ceval.c:2005:12 (python+0x79c3a0)
    #8 _PyFunction_Vectorcall /home/someuser/project/cpython/Objects/call.c (python+0x590def) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #9 _PyObject_VectorcallTstate /home/someuser/project/cpython/./Include/internal/pycore_call.h:169:11 (python+0x5956b6) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #10 method_vectorcall /home/someuser/project/cpython/Objects/classobject.c:73:20 (python+0x5956b6)
    #11 _PyObject_VectorcallTstate /home/someuser/project/cpython/./Include/internal/pycore_call.h:169:11 (python+0x7f4a21) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #12 context_run /home/someuser/project/cpython/Python/context.c:728:29 (python+0x7f4a21)
    #13 _PyEval_EvalFrameDefault /home/someuser/project/cpython/Python/generated_cases.c.h:3710:35 (python+0x7a9401) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #14 _PyEval_EvalFrame /home/someuser/project/cpython/./Include/internal/pycore_ceval.h:121:16 (python+0x79c3a0) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #15 _PyEval_Vector /home/someuser/project/cpython/Python/ceval.c:2005:12 (python+0x79c3a0)
    #16 _PyFunction_Vectorcall /home/someuser/project/cpython/Objects/call.c (python+0x590def) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #17 _PyObject_VectorcallTstate /home/someuser/project/cpython/./Include/internal/pycore_call.h:169:11 (python+0x5956b6) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #18 method_vectorcall /home/someuser/project/cpython/Objects/classobject.c:73:20 (python+0x5956b6)
    #19 _PyVectorcall_Call /home/someuser/project/cpython/Objects/call.c:273:16 (python+0x590a87) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #20 _PyObject_Call /home/someuser/project/cpython/Objects/call.c:348:16 (python+0x590a87)
    #21 PyObject_Call /home/someuser/project/cpython/Objects/call.c:373:12 (python+0x590af5) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #22 thread_run /home/someuser/project/cpython/./Modules/_threadmodule.c:387:21 (python+0x96c152) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #23 pythread_wrapper /home/someuser/project/cpython/Python/thread_pthread.h:234:5 (python+0x8a0317) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)

  Previous atomic read of size 8 at 0x7fb1ed269910 by thread T22:
    #0 _Py_atomic_load_ptr /home/someuser/project/cpython/./Include/cpython/pyatomic_gcc.h:300:18 (python+0x886ef1) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #1 _Py_TryIncrefCompare /home/someuser/project/cpython/./Include/internal/pycore_object.h:603:15 (python+0x886ef1)
    #2 PyMember_GetOne /home/someuser/project/cpython/Python/structmember.c:91:18 (python+0x8869ca) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #3 member_get /home/someuser/project/cpython/Objects/descrobject.c:180:12 (python+0x5a8a06) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #4 _Py_type_getattro_impl /home/someuser/project/cpython/Objects/typeobject.c:6363:19 (python+0x6a0992) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #5 _Py_type_getattro /home/someuser/project/cpython/Objects/typeobject.c:6424:12 (python+0x6a0ca0) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #6 PyObject_GetAttr /home/someuser/project/cpython/Objects/object.c:1313:18 (python+0x648a3e) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #7 _PyObject_GetMethodStackRef /home/someuser/project/cpython/Objects/object.c:1706:25 (python+0x64b746) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #8 _PyEval_EvalFrameDefault /home/someuser/project/cpython/Python/generated_cases.c.h:7844:35 (python+0x7b55b4) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #9 _PyEval_EvalFrame /home/someuser/project/cpython/./Include/internal/pycore_ceval.h:121:16 (python+0x79c3a0) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #10 _PyEval_Vector /home/someuser/project/cpython/Python/ceval.c:2005:12 (python+0x79c3a0)
    #11 _PyFunction_Vectorcall /home/someuser/project/cpython/Objects/call.c (python+0x590def) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #12 _PyObject_VectorcallTstate /home/someuser/project/cpython/./Include/internal/pycore_call.h:169:11 (python+0x5956b6) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #13 method_vectorcall /home/someuser/project/cpython/Objects/classobject.c:73:20 (python+0x5956b6)
    #14 _PyObject_VectorcallTstate /home/someuser/project/cpython/./Include/internal/pycore_call.h:169:11 (python+0x7f4a21) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #15 context_run /home/someuser/project/cpython/Python/context.c:728:29 (python+0x7f4a21)
    #16 _PyEval_EvalFrameDefault /home/someuser/project/cpython/Python/generated_cases.c.h:3710:35 (python+0x7a9401) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #17 _PyEval_EvalFrame /home/someuser/project/cpython/./Include/internal/pycore_ceval.h:121:16 (python+0x79c3a0) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #18 _PyEval_Vector /home/someuser/project/cpython/Python/ceval.c:2005:12 (python+0x79c3a0)
    #19 _PyFunction_Vectorcall /home/someuser/project/cpython/Objects/call.c (python+0x590def) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #20 _PyObject_VectorcallTstate /home/someuser/project/cpython/./Include/internal/pycore_call.h:169:11 (python+0x5956b6) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #21 method_vectorcall /home/someuser/project/cpython/Objects/classobject.c:73:20 (python+0x5956b6)
    #22 _PyVectorcall_Call /home/someuser/project/cpython/Objects/call.c:273:16 (python+0x590a87) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #23 _PyObject_Call /home/someuser/project/cpython/Objects/call.c:348:16 (python+0x590a87)
    #24 PyObject_Call /home/someuser/project/cpython/Objects/call.c:373:12 (python+0x590af5) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #25 thread_run /home/someuser/project/cpython/./Modules/_threadmodule.c:387:21 (python+0x96c152) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)
    #26 pythread_wrapper /home/someuser/project/cpython/Python/thread_pthread.h:234:5 (python+0x8a0317) (BuildId: 111d1b338abaedccddf0af54b80c4dc6c30a747f)

@LindaSummer LindaSummer changed the title gh-133467: Fix tp_base race in free threading in typeobject gh-133467: Fix typeobject tp_base race in free threading Oct 24, 2025
@LindaSummer
Copy link
Contributor Author

Hi @nascheme,

Thansk very much for your guidance. 😊

I have created a case for the race of tb_base.

Please help take a look.

Best Regards,
Edward

@LindaSummer LindaSummer force-pushed the bugfix/type-race branch 2 times, most recently from ba3206e to 0df5ffa Compare October 24, 2025 15:06
@LindaSummer LindaSummer changed the title gh-133467: Fix typeobject tp_base race in free threading gh-133465: Fix typeobject tp_base race in free threading Oct 25, 2025
@LindaSummer LindaSummer changed the title gh-133465: Fix typeobject tp_base race in free threading gh-133467: Fix typeobject tp_base race in free threading Oct 25, 2025
@LindaSummer
Copy link
Contributor Author

Hi @nascheme and @colesbury ,

Sorry to bother you. 😊
Could we take a review on this PR?

Hope to get suggestions from you!

Best Regards,
Edward

@nascheme
Copy link
Member

LGTM. Using types_stop_world() and types_start_world() would be a little cleaner, IMHO but that's minor.

@colesbury
Copy link
Contributor

colesbury commented Oct 29, 2025 via email

@nascheme
Copy link
Member

I think we might need the helper that avoids releasing the critical section
here.

We have this code in apply_type_slot_updates(), which I think could be used here too:

    type_lock_prevent_release();
    types_stop_world();
    apply_slot_updates(updates);
    types_start_world();
    type_lock_allow_release();

#endif
type->tp_base = old_base;
#ifdef Py_GIL_DISABLED
types_start_world();
Copy link
Contributor

Choose a reason for hiding this comment

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

these functions are no-op in gil enabled builds so ifdefs are not needed

@LindaSummer LindaSummer force-pushed the bugfix/type-race branch 3 times, most recently from f494440 to 9836fd7 Compare November 3, 2025 01:02
@LindaSummer
Copy link
Contributor Author

Hi @nascheme , @colesbury and @kumaraditya303 ,

I have updated the PR as suggested in the review comment. 😄

Please help take a review.

Wish you a good day!

Best Regards,
Edward

@colesbury
Copy link
Contributor

You'll need to update the calls to use type_lock_prevent_release() / type_lock_allow_release() because we do not want to release the type lock when stopping the world. See the pattern and comment in apply_type_slot_updates.

Also, please remove the associated suppression in Tools/tsan/suppressions_free_threading.txt

@LindaSummer
Copy link
Contributor Author

You'll need to update the calls to use type_lock_prevent_release() / type_lock_allow_release() because we do not want to release the type lock when stopping the world. See the pattern and comment in apply_type_slot_updates.

Also, please remove the associated suppression in Tools/tsan/suppressions_free_threading.txt

Hi @colesbury ,

Thanks very much for your help and suggestion! 😊

I have added the type_lock_prevent_release() / type_lock_allow_release() to guard the STW.

Please correct me if I misunderstand.

Best Regards,
Edward

type->tp_base = old_base;
types_start_world();
#ifdef Py_GIL_DISABLED
type_lock_allow_release();
Copy link
Contributor

Choose a reason for hiding this comment

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

it would be better to add an empty macro for type_lock_allow_release similar to how it is done for types_start_world in gil-enabled build then you won't these ifdefs

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.

4 participants