gh-112075: Support freeing object memory via QSBR by DinoV · Pull Request #116344 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions Include/internal/pycore_gc.h
3 changes: 3 additions & 0 deletions Include/internal/pycore_pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ extern int _PyMem_DebugEnabled(void);
// Enqueue a pointer to be freed possibly after some delay.
extern void _PyMem_FreeDelayed(void *ptr);

// Enqueue an object to be freed possibly after some delay
extern void _PyObject_FreeDelayed(void *ptr);

// Periodically process delayed free requests.
extern void _PyMem_ProcessDelayed(PyThreadState *tstate);

Expand Down
41 changes: 33 additions & 8 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,7 @@ _PyMem_Strdup(const char *str)

// A pointer to be freed once the QSBR read sequence reaches qsbr_goal.
struct _mem_work_item {
void *ptr;
uintptr_t ptr; // lowest bit tagged 1 for objects freed with PyObject_Free
uint64_t qsbr_goal;
};

Expand All @@ -971,16 +971,27 @@ struct _mem_work_chunk {
struct _mem_work_item array[WORK_ITEMS_PER_CHUNK];
};

void
_PyMem_FreeDelayed(void *ptr)
static void
free_work_item(uintptr_t ptr)
{
if (ptr & 0x01) {
PyObject_Free((char *)(ptr - 1));
}
else {
PyMem_Free((void *)ptr);
}
}

static void
free_delayed(uintptr_t ptr)
{
#ifndef Py_GIL_DISABLED
PyMem_Free(ptr);
free_work_item(ptr);
#else
if (_PyRuntime.stoptheworld.world_stopped) {
// Free immediately if the world is stopped, including during
// interpreter shutdown.
PyMem_Free(ptr);
free_work_item(ptr);
return;
}

Expand All @@ -1007,7 +1018,7 @@ _PyMem_FreeDelayed(void *ptr)
if (buf == NULL) {
// failed to allocate a buffer, free immediately
_PyEval_StopTheWorld(tstate->base.interp);
PyMem_Free(ptr);
free_work_item(ptr);
_PyEval_StartTheWorld(tstate->base.interp);
return;
}
Expand All @@ -1024,6 +1035,20 @@ _PyMem_FreeDelayed(void *ptr)
#endif
}

void
_PyMem_FreeDelayed(void *ptr)
{
assert(!((uintptr_t)ptr & 0x01));
free_delayed((uintptr_t)ptr);
}

void
_PyObject_FreeDelayed(void *ptr)
{
assert(!((uintptr_t)ptr & 0x01));
free_delayed(((uintptr_t)ptr)|0x01);
}

static struct _mem_work_chunk *
work_queue_first(struct llist_node *head)
{
Expand All @@ -1043,7 +1068,7 @@ process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr,
return;
}

PyMem_Free(item->ptr);
free_work_item(item->ptr);
buf->rd_idx++;
}

Expand Down Expand Up @@ -1130,7 +1155,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];
PyMem_Free(item->ptr);
free_work_item(item->ptr);
buf->rd_idx++;
}

Expand Down
10 changes: 8 additions & 2 deletions Python/gc_free_threading.c