| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a225520 commit a3af3cb
21 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,74 @@ | |||
| 1 | + #ifndef Py_INTERNAL_BRC_H | ||
| 2 | + #define Py_INTERNAL_BRC_H | ||
| 3 | + | ||
| 4 | + #include <stdint.h> | ||
| 5 | + #include "pycore_llist.h" // struct llist_node | ||
| 6 | + #include "pycore_lock.h" // PyMutex | ||
| 7 | + #include "pycore_object_stack.h" // _PyObjectStack | ||
| 8 | + | ||
| 9 | + #ifdef __cplusplus | ||
| 10 | + extern "C" { | ||
| 11 | + #endif | ||
| 12 | + | ||
| 13 | + #ifndef Py_BUILD_CORE | ||
| 14 | + # error "this header requires Py_BUILD_CORE define" | ||
| 15 | + #endif | ||
| 16 | + | ||
| 17 | + #ifdef Py_GIL_DISABLED | ||
| 18 | + | ||
| 19 | + // Prime number to avoid correlations with memory addresses. | ||
| 20 | + #define _Py_BRC_NUM_BUCKETS 257 | ||
| 21 | + | ||
| 22 | + // Hash table bucket | ||
| 23 | + struct _brc_bucket { | ||
| 24 | + // Mutex protects both the bucket and thread state queues in this bucket. | ||
| 25 | + PyMutex mutex; | ||
| 26 | + | ||
| 27 | + // Linked list of _PyThreadStateImpl objects hashed to this bucket. | ||
| 28 | + struct llist_node root; | ||
| 29 | + }; | ||
| 30 | + | ||
| 31 | + // Per-interpreter biased reference counting state | ||
| 32 | + struct _brc_state { | ||
| 33 | + // Hash table of thread states by thread-id. Thread states within a bucket | ||
| 34 | + // are chained using a doubly-linked list. | ||
| 35 | + struct _brc_bucket table[_Py_BRC_NUM_BUCKETS]; | ||
| 36 | + }; | ||
| 37 | + | ||
| 38 | + // Per-thread biased reference counting state | ||
| 39 | + struct _brc_thread_state { | ||
| 40 | + // Linked-list of thread states per hash bucket | ||
| 41 | + struct llist_node bucket_node; | ||
| 42 | + | ||
| 43 | + // Thread-id as determined by _PyThread_Id() | ||
| 44 | + uintptr_t tid; | ||
| 45 | + | ||
| 46 | + // Objects with refcounts to be merged (protected by bucket mutex) | ||
| 47 | + _PyObjectStack objects_to_merge; | ||
| 48 | + | ||
| 49 | + // Local stack of objects to be merged (not accessed by other threads) | ||
| 50 | + _PyObjectStack local_objects_to_merge; | ||
| 51 | + }; | ||
| 52 | + | ||
| 53 | + // Initialize/finalize the per-thread biased reference counting state | ||
| 54 | + void _Py_brc_init_thread(PyThreadState *tstate); | ||
| 55 | + void _Py_brc_remove_thread(PyThreadState *tstate); | ||
| 56 | + | ||
| 57 | + // Initialize per-interpreter state | ||
| 58 | + void _Py_brc_init_state(PyInterpreterState *interp); | ||
| 59 | + | ||
| 60 | + void _Py_brc_after_fork(PyInterpreterState *interp); | ||
| 61 | + | ||
| 62 | + // Enqueues an object to be merged by it's owning thread (tid). This | ||
| 63 | + // steals a reference to the object. | ||
| 64 | + void _Py_brc_queue_object(PyObject *ob); | ||
| 65 | + | ||
| 66 | + // Merge the refcounts of queued objects for the current thread. | ||
| 67 | + void _Py_brc_merge_refcounts(PyThreadState *tstate); | ||
| 68 | + | ||
| 69 | + #endif /* Py_GIL_DISABLED */ | ||
| 70 | + | ||
| 71 | + #ifdef __cplusplus | ||
| 72 | + } | ||
| 73 | + #endif | ||
| 74 | + #endif /* !Py_INTERNAL_BRC_H */ | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -206,6 +206,7 @@ void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame) | |||
| 206 | 206 | #define _PY_ASYNC_EXCEPTION_BIT 3 | |
| 207 | 207 | #define _PY_GC_SCHEDULED_BIT 4 | |
| 208 | 208 | #define _PY_EVAL_PLEASE_STOP_BIT 5 | |
| 209 | + #define _PY_EVAL_EXPLICIT_MERGE_BIT 6 | ||
| 209 | 210 | ||
| 210 | 211 | /* Reserve a few bits for future use */ | |
| 211 | 212 | #define _PY_EVAL_EVENTS_BITS 8 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -201,6 +201,7 @@ struct _is { | |||
| 201 | 201 | ||
| 202 | 202 | #if defined(Py_GIL_DISABLED) | |
| 203 | 203 | struct _mimalloc_interp_state mimalloc; | |
| 204 | + struct _brc_state brc; // biased reference counting state | ||
| 204 | 205 | #endif | |
| 205 | 206 | ||
| 206 | 207 | // Per-interpreter state for the obmalloc allocator. For the main | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,8 @@ | |||
| 1 | 1 | #ifndef Py_INTERNAL_OBJECT_STACK_H | |
| 2 | 2 | #define Py_INTERNAL_OBJECT_STACK_H | |
| 3 | 3 | ||
| 4 | + #include "pycore_freelist.h" // _PyFreeListState | ||
| 5 | + | ||
| 4 | 6 | #ifdef __cplusplus | |
| 5 | 7 | extern "C" { | |
| 6 | 8 | #endif | |
@@ -74,6 +76,10 @@ _PyObjectStack_Pop(_PyObjectStack *stack) | |||
| 74 | 76 | return obj; | |
| 75 | 77 | } | |
| 76 | 78 | ||
| 79 | + // Merge src into dst, leaving src empty | ||
| 80 | + extern void | ||
| 81 | + _PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src); | ||
| 82 | + | ||
| 77 | 83 | // Remove all items from the stack | |
| 78 | 84 | extern void | |
| 79 | 85 | _PyObjectStack_Clear(_PyObjectStack *stack); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ extern "C" { | |||
| 10 | 10 | ||
| 11 | 11 | #include "pycore_freelist.h" // struct _Py_freelist_state | |
| 12 | 12 | #include "pycore_mimalloc.h" // struct _mimalloc_thread_state | |
| 13 | + #include "pycore_brc.h" // struct _brc_thread_state | ||
| 13 | 14 | ||
| 14 | 15 | ||
| 15 | 16 | // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The | |
@@ -22,6 +23,7 @@ typedef struct _PyThreadStateImpl { | |||
| 22 | 23 | #ifdef Py_GIL_DISABLED | |
| 23 | 24 | struct _mimalloc_thread_state mimalloc; | |
| 24 | 25 | struct _Py_freelist_state freelist_state; | |
| 26 | + struct _brc_thread_state brc; | ||
| 25 | 27 | #endif | |
| 26 | 28 | ||
| 27 | 29 | } _PyThreadStateImpl; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -865,6 +865,7 @@ def __init__(self, f, test): | |||
| 865 | 865 | self.test = test | |
| 866 | 866 | def run(self): | |
| 867 | 867 | del self.f | |
| 868 | + gc_collect() | ||
| 868 | 869 | self.test.assertEqual(LAST_FREED, 500) | |
| 869 | 870 | ||
| 870 | 871 | SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500)) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,8 +1,10 @@ | |||
| 1 | 1 | import threading | |
| 2 | 2 | import time | |
| 3 | + import unittest | ||
| 3 | 4 | import weakref | |
| 4 | 5 | from concurrent import futures | |
| 5 | 6 | from test import support | |
| 7 | + from test.support import Py_GIL_DISABLED | ||
| 6 | 8 | ||
| 7 | 9 | ||
| 8 | 10 | def mul(x, y): | |
@@ -83,10 +85,21 @@ def test_no_stale_references(self): | |||
| 83 | 85 | my_object_collected = threading.Event() | |
| 84 | 86 | my_object_callback = weakref.ref( | |
| 85 | 87 | my_object, lambda obj: my_object_collected.set()) | |
| 86 | - # Deliberately discarding the future. | ||
| 87 | - self.executor.submit(my_object.my_method) | ||
| 88 | + fut = self.executor.submit(my_object.my_method) | ||
| 88 | 89 | del my_object | |
| 89 | 90 | ||
| 91 | + if Py_GIL_DISABLED: | ||
| 92 | + # Due to biased reference counting, my_object might only be | ||
| 93 | + # deallocated while the thread that created it runs -- if the | ||
| 94 | + # thread is paused waiting on an event, it may not merge the | ||
| 95 | + # refcount of the queued object. For that reason, we wait for the | ||
| 96 | + # task to finish (so that it's no longer referenced) and force a | ||
| 97 | + # GC to ensure that it is collected. | ||
| 98 | + fut.result() # Wait for the task to finish. | ||
| 99 | + support.gc_collect() | ||
| 100 | + else: | ||
| 101 | + del fut # Deliberately discard the future. | ||
| 102 | + | ||
| 90 | 103 | collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT) | |
| 91 | 104 | self.assertTrue(collected, | |
| 92 | 105 | "Stale reference not collected within timeout.") | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -98,6 +98,7 @@ def test_ressources_gced_in_workers(self): | |||
| 98 | 98 | ||
| 99 | 99 | # explicitly destroy the object to ensure that EventfulGCObj.__del__() | |
| 100 | 100 | # is called while manager is still running. | |
| 101 | + support.gc_collect() | ||
| 101 | 102 | obj = None | |
| 102 | 103 | support.gc_collect() | |
| 103 | 104 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -405,6 +405,7 @@ PYTHON_OBJS= \ | |||
| 405 | 405 | Python/ast_opt.o \ | |
| 406 | 406 | Python/ast_unparse.o \ | |
| 407 | 407 | Python/bltinmodule.o \ | |
| 408 | + Python/brc.o \ | ||
| 408 | 409 | Python/ceval.o \ | |
| 409 | 410 | Python/codecs.o \ | |
| 410 | 411 | Python/compile.o \ | |
@@ -1081,6 +1082,7 @@ PYTHON_HEADERS= \ | |||
| 1081 | 1082 | $(srcdir)/Include/internal/pycore_atexit.h \ | |
| 1082 | 1083 | $(srcdir)/Include/internal/pycore_bitutils.h \ | |
| 1083 | 1084 | $(srcdir)/Include/internal/pycore_blocks_output_buffer.h \ | |
| 1085 | + $(srcdir)/Include/internal/pycore_brc.h \ | ||
| 1084 | 1086 | $(srcdir)/Include/internal/pycore_bytes_methods.h \ | |
| 1085 | 1087 | $(srcdir)/Include/internal/pycore_bytesobject.h \ | |
| 1086 | 1088 | $(srcdir)/Include/internal/pycore_call.h \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -637,6 +637,10 @@ PyOS_AfterFork_Child(void) | |||
| 637 | 637 | tstate->native_thread_id = PyThread_get_thread_native_id(); | |
| 638 | 638 | #endif | |
| 639 | 639 | ||
| 640 | + #ifdef Py_GIL_DISABLED | ||
| 641 | + _Py_brc_after_fork(tstate->interp); | ||
| 642 | + #endif | ||
| 643 | + | ||
| 640 | 644 | status = _PyEval_ReInitThreads(tstate); | |
| 641 | 645 | if (_PyStatus_EXCEPTION(status)) { | |
| 642 | 646 | goto fatal_error; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments