Skip to content
Open
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
105 changes: 105 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,111 @@ def main_work():
"GIL holder should be among all threads",
)

@skip_if_not_supported
@unittest.skipIf(sys._is_gil_enabled(), "Requires free-threading")
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Requires process_vm_readv",
)
def test_tlbc_cache_refresh_after_growth(self):
# Reproducer from gh-157660.
script = textwrap.dedent("""\
import os, threading
from _remote_debugging import RemoteUnwinder
from test import support

go = threading.Event()
stop = threading.Event()

def leaf():
stop.wait()

def wait_for_leaf_frames(u, expected_count):
for _ in support.sleeping_retry(
support.SHORT_TIMEOUT,
f"Expected {expected_count} leaf frames",
):
count = sum(
f.funcname == "leaf"
for t in u.get_stack_trace() for f in t.frame_info
)
if count == expected_count:
return

threading.Thread(target=leaf, daemon=True).start()
for _ in range(16):
threading.Thread(target=stop.wait, daemon=True).start()
threading.Thread(target=lambda: (go.wait(), leaf()), daemon=True).start()

u = RemoteUnwinder(os.getpid(), all_threads=True)
wait_for_leaf_frames(u, 1)
go.set()
wait_for_leaf_frames(u, 2)
""")
result = subprocess.run(
[sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script],
capture_output=True,
text=True,
timeout=SHORT_TIMEOUT,
)
self.assertEqual(
result.returncode, 0,
f"stdout: {result.stdout}\nstderr: {result.stderr}",
)

@skip_if_not_supported
@unittest.skipIf(sys._is_gil_enabled(), "Requires free-threading")
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Requires process_vm_readv",
)
def test_tlbc_cache_refresh_after_slot_fill(self):
# Reproducer from gh-157660.
script = textwrap.dedent("""\
import os, threading
from _remote_debugging import RemoteUnwinder

go = threading.Event()
stop = threading.Event()

def leaf():
stop.wait()

from test import support

def lines(u, expected_count):
for _ in support.sleeping_retry(
support.SHORT_TIMEOUT,
f"Expected {expected_count} leaf frames",
):
result = sorted(
f.lineno
for t in u.get_stack_trace() for f in t.frame_info
if f.funcname == "leaf"
)
if len(result) == expected_count:
return result

threading.Thread(target=leaf, daemon=True).start()
threading.Thread(target=lambda: (go.wait(), leaf()), daemon=True).start()
u = RemoteUnwinder(os.getpid(), all_threads=True)
before = lines(u, 1)
assert before == [8], before
go.set()
cached = lines(u, 2)
assert cached == [8, 8], cached
""")
result = subprocess.run(
[sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script],
capture_output=True,
text=True,
timeout=SHORT_TIMEOUT,
)
self.assertEqual(
result.returncode, 0,
f"stdout: {result.stdout}\nstderr: {result.stderr}",
)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``_remote_debugging`` reporting errors or incorrect line numbers in free-threaded
builds when a thread-local bytecode array grows or gains entries after being cached.
25 changes: 22 additions & 3 deletions Modules/_remote_debugging_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -2086,9 +2086,12 @@ get_tlbc_cache_entry(RemoteUnwinderObject *self, uintptr_t code_addr, uint32_t c
TLBCCacheEntry *entry = _Py_hashtable_get(self->tlbc_cache, key);

if (entry && entry->generation != current_generation) {
// Entry is stale, remove it by setting to NULL
_Py_hashtable_set(self->tlbc_cache, key, NULL);
entry = NULL;
// Entry is stale, remove it from the cache and destroy it
TLBCCacheEntry *old = _Py_hashtable_steal(self->tlbc_cache, key);
if (old != NULL) {
tlbc_cache_entry_destroy(old);
}
return NULL;
}

return entry;
Expand Down Expand Up @@ -2361,6 +2364,22 @@ parse_code_object(RemoteUnwinderObject *unwinder,
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
}

if (tlbc_entry && tlbc_index >= 0) {
uintptr_t *entries = (uintptr_t *)((char *)tlbc_entry->tlbc_array + sizeof(Py_ssize_t));
if (tlbc_index >= tlbc_entry->tlbc_array_size ||
entries[tlbc_index] == 0) {
TLBCCacheEntry *old = _Py_hashtable_steal(unwinder->tlbc_cache, (void *)real_address);
if (old != NULL) {
tlbc_cache_entry_destroy(old);
}
if (!cache_tlbc_array(unwinder, real_address, real_address + unwinder->debug_offsets.code_object.co_tlbc,
unwinder->tlbc_generation)) {
goto error;
}
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
}
}

if (tlbc_entry) {
if (tlbc_index < 0 || tlbc_index >= tlbc_entry->tlbc_array_size) {
PyErr_Format(PyExc_RuntimeError,
Expand Down
Loading