Skip to content

Commit 75bb4cb

Browse files
committed
[3.14] gh-145566: Skip stop-the-world when reassigning __class__ on newly created objects (gh-145567)
(cherry picked from commit 1d091a3) Co-authored-by: Sam Gross <colesbury@gmail.com>
1 parent 7b9508f commit 75bb4cb

File tree

5 files changed

+98
-5
lines changed

5 files changed

+98
-5
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
In the free threading build, skip the stop-the-world pause when reassigning
2+
``__class__`` on a newly created object.

Objects/mimalloc/alloc.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ bool _mi_free_delayed_block(mi_block_t* block) {
625625
}
626626

627627
// collect all other non-local frees to ensure up-to-date `used` count
628+
if (page->qsbr_node.next != NULL && (page->local_free != NULL || mi_page_thread_free(page) != NULL)) {
629+
static _Atomic(int) _c; int _n = 1+atomic_fetch_add(&_c,1);
630+
if (_n%100==0||_n<=3) printf("QSBR CLEAR from _mi_free_delayed_block page=%p all_free=%d used=%d (%d)\n",(void*)page,(int)mi_page_all_free(page),(int)page->used,_n);
631+
}
628632
_mi_page_free_collect(page, false);
629633

630634
// and free the block (possibly freeing the page as well since used is updated)

Objects/mimalloc/page.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,13 @@ void _mi_page_free_collect(mi_page_t* page, bool force) {
226226
// and the local free list
227227
if (page->local_free != NULL) {
228228
// any previous QSBR goals are no longer valid because we reused the page
229+
if (page->qsbr_node.next != NULL) {
230+
extern _Atomic(int) _debug_qsbr_clear_in_collect;
231+
int n = 1 + atomic_fetch_add(&_debug_qsbr_clear_in_collect, 1);
232+
if (n%100==0||n<=3) printf("QSBR CLEAR generic page=%p all_free=%d used=%d xfree=%d lfree=%d (%d)\n",
233+
(void*)page,(int)mi_page_all_free(page),(int)page->used,
234+
(mi_page_thread_free(page)!=NULL),(page->local_free!=NULL),n);
235+
}
229236
_PyMem_mi_page_clear_qsbr(page);
230237

231238
if mi_likely(page->free == NULL) {
@@ -371,6 +378,10 @@ static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) {
371378

372379
if (mi_page_is_in_full(page)) return;
373380
mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page);
381+
if (page->qsbr_node.next != NULL && (page->local_free != NULL || mi_page_thread_free(page) != NULL)) {
382+
static _Atomic(int) _c; int _n = 1+atomic_fetch_add(&_c,1);
383+
if (_n%100==0||_n<=3) printf("QSBR CLEAR from mi_page_to_full page=%p all_free=%d used=%d (%d)\n",(void*)page,(int)mi_page_all_free(page),(int)page->used,_n);
384+
}
374385
_mi_page_free_collect(page,false); // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set
375386
}
376387

@@ -752,6 +763,10 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
752763
#endif
753764

754765
// 0. collect freed blocks by us and other threads
766+
if (page->qsbr_node.next != NULL && (page->local_free != NULL || mi_page_thread_free(page) != NULL)) {
767+
static _Atomic(int) _c; int _n = 1+atomic_fetch_add(&_c,1);
768+
if (_n%100==0||_n<=3) printf("QSBR CLEAR from find_free_ex page=%p all_free=%d used=%d (%d)\n",(void*)page,(int)mi_page_all_free(page),(int)page->used,_n);
769+
}
755770
_mi_page_free_collect(page, false);
756771

757772
// 1. if the page contains free blocks, we are done
@@ -777,6 +792,15 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p
777792
mi_heap_stat_counter_increase(heap, searches, count);
778793

779794
if (page == NULL) {
795+
{
796+
static _Atomic(int) null_page_count;
797+
int n = 1 + atomic_fetch_add(&null_page_count, 1);
798+
if (n % 50 == 0 || n <= 5) {
799+
printf("find_free_ex: page==NULL tid=%zu heap_tid=%zu use_qsbr=%d (call #%d)\n",
800+
(size_t)_mi_thread_id(), (size_t)heap->thread_id,
801+
heap->page_use_qsbr, n);
802+
}
803+
}
780804
_PyMem_mi_heap_collect_qsbr(heap); // some pages might be safe to free now
781805
_mi_heap_collect_retired(heap, false); // perhaps make a page available?
782806
page = mi_page_fresh(heap, pq);
@@ -809,6 +833,10 @@ static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
809833
else
810834
#endif
811835
{
836+
if (page->qsbr_node.next != NULL && (page->local_free != NULL || mi_page_thread_free(page) != NULL)) {
837+
static _Atomic(int) _c; int _n = 1+atomic_fetch_add(&_c,1);
838+
if (_n%100==0||_n<=3) printf("QSBR CLEAR from mi_page_fresh_alloc page=%p all_free=%d used=%d (%d)\n",(void*)page,(int)mi_page_all_free(page),(int)page->used,_n);
839+
}
812840
_mi_page_free_collect(page,false);
813841
}
814842

Objects/obmalloc.c

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ should_advance_qsbr_for_page(struct _qsbr_thread_state *qsbr, mi_page_t *page)
151151
}
152152
#endif
153153

154+
_Atomic(int) _debug_qsbr_clear_in_collect;
155+
154156
static bool
155157
_PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force)
156158
{
@@ -174,7 +176,19 @@ _PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force)
174176
page->qsbr_goal = _Py_qsbr_shared_next(tstate->qsbr->shared);
175177
}
176178

177-
llist_insert_tail(&tstate->mimalloc.page_list, &page->qsbr_node);
179+
mi_heap_t *page_heap = mi_page_heap(page);
180+
_PyThreadStateImpl *heap_tstate = _Py_CONTAINER_OF(page_heap->tld, _PyThreadStateImpl, mimalloc.tld);
181+
if (page_heap->thread_id != _mi_thread_id()) {
182+
static _Atomic(int) cross_thread_qsbr_count;
183+
int n = 1 + atomic_fetch_add(&cross_thread_qsbr_count, 1);
184+
if (n % 100 == 0) {
185+
_PyThreadStateImpl *cur_tstate = (_PyThreadStateImpl *)PyThreadState_GET();
186+
printf("cross-thread QSBR page count: %d (page_tid=%zu cur_tid=%zu heap_tstate=%p cur_tstate=%p)\n",
187+
n, (size_t)page_heap->thread_id, (size_t)_mi_thread_id(),
188+
(void*)heap_tstate, (void*)cur_tstate);
189+
}
190+
}
191+
llist_insert_tail(&heap_tstate->mimalloc.page_list, &page->qsbr_node);
178192
return false;
179193
}
180194
#endif
@@ -212,25 +226,61 @@ _PyMem_mi_heap_collect_qsbr(mi_heap_t *heap)
212226
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
213227
struct llist_node *head = &tstate->mimalloc.page_list;
214228
if (llist_empty(head)) {
229+
static _Atomic(int) empty_qsbr_count;
230+
int n = 1 + atomic_fetch_add(&empty_qsbr_count, 1);
231+
if (n % 50 == 0 || n <= 5) {
232+
_PyThreadStateImpl *heap_ts = _Py_CONTAINER_OF(heap->tld, _PyThreadStateImpl, mimalloc.tld);
233+
printf("qsbr_collect EMPTY tid=%zu heap_tid=%zu tstate=%p heap_tstate=%p (call #%d)\n",
234+
(size_t)_mi_thread_id(), (size_t)heap->thread_id,
235+
(void*)tstate, (void*)heap_ts, n);
236+
}
215237
return;
216238
}
217239

240+
int freed = 0, not_free = 0, not_reached = 0;
218241
struct llist_node *node;
219242
llist_for_each_safe(node, head) {
220243
mi_page_t *page = llist_data(node, mi_page_t, qsbr_node);
221244
if (!mi_page_all_free(page)) {
222245
// We allocated from this page some point after the delayed free
246+
not_free++;
223247
_PyMem_mi_page_clear_qsbr(page);
224248
continue;
225249
}
226250

227251
if (!_Py_qsbr_poll(tstate->qsbr, page->qsbr_goal)) {
228-
return;
252+
not_reached++;
253+
// On first failure, log the details
254+
if (not_reached == 1) {
255+
struct _qsbr_shared *shared = tstate->qsbr->shared;
256+
printf(" qsbr FAIL: goal=%llu rd_seq=%llu wr_seq=%llu my_seq=%llu\n",
257+
(unsigned long long)page->qsbr_goal,
258+
(unsigned long long)_Py_atomic_load_uint64(&shared->rd_seq),
259+
(unsigned long long)_Py_atomic_load_uint64(&shared->wr_seq),
260+
(unsigned long long)_Py_atomic_load_uint64(&tstate->qsbr->seq));
261+
// scan threads to find the blocker
262+
struct _qsbr_pad *array = shared->array;
263+
for (Py_ssize_t ii = 0; ii < shared->size; ii++) {
264+
uint64_t s = _Py_atomic_load_uint64(&array[ii].qsbr.seq);
265+
if (s != QSBR_OFFLINE && s < page->qsbr_goal) {
266+
printf(" blocker slot %zd: seq=%llu\n", ii, (unsigned long long)s);
267+
}
268+
}
269+
}
270+
// count remaining
271+
while (node->next != head) { not_reached++; node = node->next; }
272+
break;
229273
}
230274

275+
freed++;
231276
_PyMem_mi_page_clear_qsbr(page);
232277
_mi_page_free(page, mi_page_queue_of(page), false);
233278
}
279+
if (freed || not_free || not_reached) {
280+
printf("qsbr_collect tid=%zu: freed=%d not_free=%d not_reached=%d heap_tid=%zu\n",
281+
(size_t)_mi_thread_id(), freed, not_free, not_reached,
282+
(size_t)heap->thread_id);
283+
}
234284
#endif
235285
}
236286

Objects/typeobject.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7120,7 +7120,11 @@ object_set_class_world_stopped(PyObject *self, PyTypeObject *newto)
71207120

71217121
assert(_PyObject_GetManagedDict(self) == dict);
71227122

7123-
if (_PyDict_DetachFromObject(dict, self) < 0) {
7123+
int err;
7124+
Py_BEGIN_CRITICAL_SECTION(dict);
7125+
err = _PyDict_DetachFromObject(dict, self);
7126+
Py_END_CRITICAL_SECTION();
7127+
if (err < 0) {
71247128
return -1;
71257129
}
71267130

@@ -7161,13 +7165,18 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
71617165
}
71627166

71637167
#ifdef Py_GIL_DISABLED
7168+
int unique = _PyObject_IsUniquelyReferenced(self);
71647169
PyInterpreterState *interp = _PyInterpreterState_GET();
7165-
_PyEval_StopTheWorld(interp);
7170+
if (!unique) {
7171+
_PyEval_StopTheWorld(interp);
7172+
}
71667173
#endif
71677174
PyTypeObject *oldto = Py_TYPE(self);
71687175
int res = object_set_class_world_stopped(self, newto);
71697176
#ifdef Py_GIL_DISABLED
7170-
_PyEval_StartTheWorld(interp);
7177+
if (!unique) {
7178+
_PyEval_StartTheWorld(interp);
7179+
}
71717180
#endif
71727181
if (res == 0) {
71737182
if (oldto->tp_flags & Py_TPFLAGS_HEAPTYPE) {

0 commit comments

Comments
 (0)