Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 92d314d

Browse files
committed
Fix race between _Py_MergeZeroRefcount and _Py_DecRefShared
The object's thread-id may be zero by the time _Py_queue_object is called. Get the thread-id before marking the object as queued in _Py_DecRefShared. Fixes #88
1 parent 083382d commit 92d314d

File tree

3 files changed

+27
-11
lines changed

3 files changed

+27
-11
lines changed

Include/internal/pycore_refcnt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extern "C" {
1111
# error "this header requires Py_BUILD_CORE define"
1212
#endif
1313

14-
void _Py_queue_object(PyObject *ob);
14+
void _Py_queue_object(PyObject *ob, uintptr_t tid);
1515
void _Py_queue_process(PyThreadState *tstate);
1616
void _Py_queue_create(PyThreadState *tstate);
1717
void _Py_queue_destroy(PyThreadState *tstate);

Objects/object.c

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,8 +2225,6 @@ _Py_MergeZeroRefcount(PyObject *op)
22252225
{
22262226
assert(_Py_atomic_load_uint32_relaxed(&op->ob_ref_local) == 0);
22272227

2228-
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0);
2229-
22302228
Py_ssize_t refcount;
22312229
for (;;) {
22322230
uint32_t shared = _Py_atomic_load_uint32_relaxed(&op->ob_ref_shared);
@@ -2245,6 +2243,9 @@ _Py_MergeZeroRefcount(PyObject *op)
22452243
// count was temporarily negative and hasn't been proceessed yet.
22462244
// We don't want to merge it yet because that might result in the
22472245
// object being freed while it's still in the queue.
2246+
// We still need to zero the thread-id so that subsequent decrements
2247+
// from this thread do not push the ob_ref_local negative.
2248+
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0);
22482249
break;
22492250
}
22502251

@@ -2257,6 +2258,7 @@ _Py_MergeZeroRefcount(PyObject *op)
22572258
* a) weak references
22582259
* b) dangling pointers (e.g. loading from a list or dict)
22592260
*/
2261+
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0);
22602262
op->ob_ref_local = 0;
22612263
PyObject_Del(op);
22622264
return;
@@ -2272,6 +2274,7 @@ _Py_MergeZeroRefcount(PyObject *op)
22722274
}
22732275
}
22742276

2277+
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0);
22752278
if (refcount == 0) {
22762279
_Py_Dealloc(op);
22772280
}
@@ -2355,12 +2358,15 @@ _Py_TryIncRefShared(PyObject *op)
23552358
void
23562359
_Py_DecRefShared(PyObject *op)
23572360
{
2358-
// TODO: fixme
23592361
uint32_t old_shared;
23602362
uint32_t new_shared;
2361-
int ok;
23622363

2363-
do {
2364+
// We need to grab the thread-id before modifying the refcount
2365+
// because the owning thread may set it to zero if we mark the
2366+
// object as queued.
2367+
uintptr_t tid = _PyObject_ThreadId(op);
2368+
2369+
for (;;) {
23642370
old_shared = _Py_atomic_load_uint32_relaxed(&op->ob_ref_shared);
23652371

23662372
new_shared = old_shared;
@@ -2369,15 +2375,22 @@ _Py_DecRefShared(PyObject *op)
23692375
}
23702376
new_shared -= (1 << _Py_REF_SHARED_SHIFT);
23712377

2372-
ok = _Py_atomic_compare_exchange_uint32(
2378+
int ok = _Py_atomic_compare_exchange_uint32(
23732379
&op->ob_ref_shared,
23742380
old_shared,
23752381
new_shared);
2376-
} while (!ok);
2382+
2383+
if (ok) {
2384+
break;
2385+
}
2386+
}
23772387

23782388
if (_Py_REF_IS_MERGED(new_shared)) {
23792389
// TOOD(sgross): implementation defined behavior
23802390
assert(((int32_t)new_shared) >= 0);
2391+
if (((int32_t)new_shared) < 0) {
2392+
Py_FatalError("negative refcount on merged object");
2393+
}
23812394
}
23822395

23832396
if (_Py_REF_IS_QUEUED(new_shared) != _Py_REF_IS_QUEUED(old_shared)) {
@@ -2389,7 +2402,7 @@ _Py_DecRefShared(PyObject *op)
23892402
}
23902403
}
23912404
else {
2392-
_Py_queue_object(op);
2405+
_Py_queue_object(op, tid);
23932406
}
23942407
}
23952408
else if (_Py_REF_IS_MERGED(new_shared) &&

Python/pyrefcnt.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ find_brc_state(Bucket *bucket, uintptr_t thread_id)
3434
}
3535

3636
void
37-
_Py_queue_object(PyObject *ob)
37+
_Py_queue_object(PyObject *ob, uintptr_t tid)
3838
{
39-
uintptr_t tid = _PyObject_ThreadId(ob);
4039
Bucket *bucket = &buckets[tid % NUM_BUCKETS];
4140

41+
if (tid == 0) {
42+
Py_FatalError("_Py_queue_object called with unowned object");
43+
}
44+
4245
_PyMutex_lock(&bucket->mutex);
4346
struct _PyBrcState *brc = find_brc_state(bucket, tid);
4447
if (!brc) {

0 commit comments

Comments
 (0)