GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — `_Py_Dealloc()` being unaware of separate stacks can cause memory leaks · Issue #157519 · python/cpython Open Labels type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionBug reportBug description:To avoid unbounded deallocations (which would cause stack overflows) This came up in practice when calling Python on a Julia task through PythonCall.jl, but here's a pure Python MWE that simulates the situation by running a workload under different stack situations: import ctypes import gc import sys import tracemalloc api = ctypes.pythonapi api.PyThreadState_Get.restype = ctypes.c_void_p api.PyUnstable_ThreadState_SetStackProtection.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t] api.PyUnstable_ThreadState_ResetStackProtection.argtypes = [ctypes.c_void_p] # Real bounds of the current stack stack libc = ctypes.CDLL(None, use_errno=True) libc.pthread_self.restype = ctypes.c_void_p attr = ctypes.create_string_buffer(1024) assert libc.pthread_getattr_np(ctypes.c_void_p(libc.pthread_self()), attr) == 0 stack_addr = ctypes.c_void_p() stack_size = ctypes.c_size_t() assert libc.pthread_attr_getstack(attr, ctypes.byref(stack_addr), ctypes.byref(stack_size)) == 0 libc.pthread_attr_destroy(attr) # Pretend the stack is 1 GiB above where it really is fake_size = 1 << 20 fake_start = stack_addr.value + stack_size.value + (1 << 30) deleted = 0 N = 1000 # Dummy class that counts how many times the destructor was called class Foo: def __init__(self): # Allocate some memory self.payload = bytes(100_000) def __del__(self): global deleted deleted += 1 def churn(): for _ in range(N): Foo() # refcount hits zero immediately def report(label): gc.collect() current, _ = tracemalloc.get_traced_memory() print(f"{label:<28} __del__ calls: {deleted:5d}/{N} " f"traced memory: {current / 2**20:7.1f} MiB") # Normal case tracemalloc.start() churn() report("real stack limits") # Simulate running under a different stack tstate = api.PyThreadState_Get() assert api.PyUnstable_ThreadState_SetStackProtection( tstate, fake_start, fake_size) == 0 deleted = 0 churn() report("stack limits far away") # Go back to the original stack and dealloc something to trigger cleanup of the # delete_later list. api.PyUnstable_ThreadState_ResetStackProtection(tstate) trigger = [] del trigger report("limits restored") On 3.14.2 this prints out: i.e. in the case of a userspace stack CPython versions tested on:3.14 Operating systems tested on:Linux Linked PRsActivity
MetadataMetadataAssigneesLabelstype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error ProjectsMilestoneRelationshipsDevelopmentIssue actions |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |