diff options
author | Dino Viehland <dinoviehland@meta.com> | 2024-11-21 16:41:19 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-21 16:41:19 (GMT) |
commit | bf542f8bb9f12f0df9481f2222b21545806dd9e1 (patch) | |
tree | 41db15281aa5a0e25e63a46e80cbe50ef80cb7f6 /Objects/obmalloc.c | |
parent | 3926842117feffe5d2c9727e1899bea5ae2adb28 (diff) | |
download | cpython-bf542f8bb9f12f0df9481f2222b21545806dd9e1.zip cpython-bf542f8bb9f12f0df9481f2222b21545806dd9e1.tar.gz cpython-bf542f8bb9f12f0df9481f2222b21545806dd9e1.tar.bz2 |
gh-124470: Fix crash when reading from object instance dictionary while replacing it (#122489)
Delay free a dictionary when replacing it
Diffstat (limited to 'Objects/obmalloc.c')
-rw-r--r-- | Objects/obmalloc.c | 62 |
1 files changed, 48 insertions, 14 deletions
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index dfeccfa..3d6b1ab 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -1093,10 +1093,24 @@ struct _mem_work_chunk { }; static void -free_work_item(uintptr_t ptr) +free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state) { if (ptr & 0x01) { - PyObject_Free((char *)(ptr - 1)); + PyObject *obj = (PyObject *)(ptr - 1); +#ifdef Py_GIL_DISABLED + if (cb == NULL) { + assert(!_PyInterpreterState_GET()->stoptheworld.world_stopped); + Py_DECREF(obj); + return; + } + + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1); + if (refcount == 0) { + cb(obj, state); + } +#else + Py_DECREF(obj); +#endif } else { PyMem_Free((void *)ptr); @@ -1107,7 +1121,7 @@ static void free_delayed(uintptr_t ptr) { #ifndef Py_GIL_DISABLED - free_work_item(ptr); + free_work_item(ptr, NULL, NULL); #else PyInterpreterState *interp = _PyInterpreterState_GET(); if (_PyInterpreterState_GetFinalizing(interp) != NULL || @@ -1115,7 +1129,8 @@ free_delayed(uintptr_t ptr) { // Free immediately during interpreter shutdown or if the world is // stopped. - free_work_item(ptr); + assert(!interp->stoptheworld.world_stopped || !(ptr & 0x01)); + free_work_item(ptr, NULL, NULL); return; } @@ -1142,7 +1157,8 @@ free_delayed(uintptr_t ptr) if (buf == NULL) { // failed to allocate a buffer, free immediately _PyEval_StopTheWorld(tstate->base.interp); - free_work_item(ptr); + // TODO: Fix me + free_work_item(ptr, NULL, NULL); _PyEval_StartTheWorld(tstate->base.interp); return; } @@ -1166,12 +1182,16 @@ _PyMem_FreeDelayed(void *ptr) free_delayed((uintptr_t)ptr); } +#ifdef Py_GIL_DISABLED void -_PyObject_FreeDelayed(void *ptr) +_PyObject_XDecRefDelayed(PyObject *ptr) { assert(!((uintptr_t)ptr & 0x01)); - free_delayed(((uintptr_t)ptr)|0x01); + if (ptr != NULL) { + free_delayed(((uintptr_t)ptr)|0x01); + } } +#endif static struct _mem_work_chunk * work_queue_first(struct llist_node *head) @@ -1181,7 +1201,7 @@ work_queue_first(struct llist_node *head) static void process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr, - bool keep_empty) + bool keep_empty, delayed_dealloc_cb cb, void *state) { while (!llist_empty(head)) { struct _mem_work_chunk *buf = work_queue_first(head); @@ -1192,7 +1212,7 @@ process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr, return; } - free_work_item(item->ptr); + free_work_item(item->ptr, cb, state); buf->rd_idx++; } @@ -1210,7 +1230,8 @@ process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr, static void process_interp_queue(struct _Py_mem_interp_free_queue *queue, - struct _qsbr_thread_state *qsbr) + struct _qsbr_thread_state *qsbr, delayed_dealloc_cb cb, + void *state) { if (!_Py_atomic_load_int_relaxed(&queue->has_work)) { return; @@ -1218,7 +1239,7 @@ process_interp_queue(struct _Py_mem_interp_free_queue *queue, // Try to acquire the lock, but don't block if it's already held. if (_PyMutex_LockTimed(&queue->mutex, 0, 0) == PY_LOCK_ACQUIRED) { - process_queue(&queue->head, qsbr, false); + process_queue(&queue->head, qsbr, false, cb, state); int more_work = !llist_empty(&queue->head); _Py_atomic_store_int_relaxed(&queue->has_work, more_work); @@ -1234,10 +1255,23 @@ _PyMem_ProcessDelayed(PyThreadState *tstate) _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; // Process thread-local work - process_queue(&tstate_impl->mem_free_queue, tstate_impl->qsbr, true); + process_queue(&tstate_impl->mem_free_queue, tstate_impl->qsbr, true, NULL, NULL); + + // Process shared interpreter work + process_interp_queue(&interp->mem_free_queue, tstate_impl->qsbr, NULL, NULL); +} + +void +_PyMem_ProcessDelayedNoDealloc(PyThreadState *tstate, delayed_dealloc_cb cb, void *state) +{ + PyInterpreterState *interp = tstate->interp; + _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; + + // Process thread-local work + process_queue(&tstate_impl->mem_free_queue, tstate_impl->qsbr, true, cb, state); // Process shared interpreter work - process_interp_queue(&interp->mem_free_queue, tstate_impl->qsbr); + process_interp_queue(&interp->mem_free_queue, tstate_impl->qsbr, cb, state); } void @@ -1279,7 +1313,7 @@ _PyMem_FiniDelayed(PyInterpreterState *interp) // Free the remaining items immediately. There should be no other // threads accessing the memory at this point during shutdown. struct _mem_work_item *item = &buf->array[buf->rd_idx]; - free_work_item(item->ptr); + free_work_item(item->ptr, NULL, NULL); buf->rd_idx++; } |