Skip to content

Commit 5c33a3b

Browse files
authored
[3.14] gh-158574: Stabilize TLBC cache refresh tests (GH-158801) (#158818)
[3.14] gh-158574: Stabilize TLBC cache refresh tests (GH-158801) Wait for workers to reach stable Python frames before sampling their stacks. Adapt the backport to the 3.14 RemoteUnwinder API. (cherry picked from commit 0e75a05)
1 parent 4a11a2e commit 5c33a3b

1 file changed

Lines changed: 67 additions & 55 deletions

File tree

‎Lib/test/test_external_inspection.py‎

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,41 +1329,49 @@ def test_tlbc_cache_refresh_after_growth(self):
13291329
script = textwrap.dedent("""\
13301330
import os, threading
13311331
from _remote_debugging import RemoteUnwinder
1332+
from _queue import SimpleQueue
13321333
from test import support
13331334
1334-
go = threading.Event()
1335-
stop = threading.Event()
1335+
go = threading.Lock()
1336+
stop = threading.Lock()
1337+
go.acquire()
1338+
stop.acquire()
1339+
ready = SimpleQueue()
13361340
13371341
def leaf():
1338-
stop.wait()
1339-
1340-
def wait_for_leaf_frames(u, expected_count):
1341-
for _ in support.sleeping_retry(
1342-
support.SHORT_TIMEOUT,
1343-
f"Expected {expected_count} leaf frames",
1344-
):
1345-
try:
1346-
traces = u.get_stack_trace()
1347-
except RuntimeError as exc:
1348-
if str(exc) != "Failed to parse initial frame in chain":
1349-
raise
1350-
continue
1351-
count = sum(
1352-
f.funcname == "leaf"
1353-
for t in traces for f in t.frame_info
1354-
)
1355-
if count == expected_count:
1356-
return
1342+
ready.put(None)
1343+
stop.acquire()
1344+
1345+
def start_leaf():
1346+
ready.put(None)
1347+
go.acquire()
1348+
leaf()
1349+
1350+
def park():
1351+
ready.put(None)
1352+
stop.acquire()
1353+
1354+
def leaf_count(u):
1355+
return sum(
1356+
f.funcname == "leaf"
1357+
for t in u.get_stack_trace() for f in t.frame_info
1358+
)
13571359
1360+
# SimpleQueue.put() and Lock.acquire() do not push Python frames.
1361+
# Once notified, the worker's stack stays stable until go is released.
13581362
threading.Thread(target=leaf, daemon=True).start()
1363+
ready.get(timeout=support.SHORT_TIMEOUT)
13591364
for _ in range(16):
1360-
threading.Thread(target=stop.wait, daemon=True).start()
1361-
threading.Thread(target=lambda: (go.wait(), leaf()), daemon=True).start()
1365+
threading.Thread(target=park, daemon=True).start()
1366+
ready.get(timeout=support.SHORT_TIMEOUT)
1367+
threading.Thread(target=start_leaf, daemon=True).start()
1368+
ready.get(timeout=support.SHORT_TIMEOUT)
13621369
13631370
u = RemoteUnwinder(os.getpid(), all_threads=True)
1364-
wait_for_leaf_frames(u, 1)
1365-
go.set()
1366-
wait_for_leaf_frames(u, 2)
1371+
assert leaf_count(u) == 1
1372+
go.release()
1373+
ready.get(timeout=support.SHORT_TIMEOUT)
1374+
assert leaf_count(u) == 2
13671375
""")
13681376
result = subprocess.run(
13691377
[sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script],
@@ -1387,44 +1395,48 @@ def test_tlbc_cache_refresh_after_slot_fill(self):
13871395
script = textwrap.dedent("""\
13881396
import os, threading
13891397
from _remote_debugging import RemoteUnwinder
1398+
from _queue import SimpleQueue
13901399
1391-
go = threading.Event()
1392-
stop = threading.Event()
1400+
go = threading.Lock()
1401+
stop = threading.Lock()
1402+
go.acquire()
1403+
stop.acquire()
1404+
ready = SimpleQueue()
13931405
13941406
def leaf():
1395-
stop.wait()
1407+
ready.put(None)
1408+
stop.acquire()
1409+
1410+
def start_leaf():
1411+
ready.put(None)
1412+
go.acquire()
1413+
leaf()
13961414
13971415
from test import support
13981416
1399-
def lines(u, expected_count):
1400-
for _ in support.sleeping_retry(
1401-
support.SHORT_TIMEOUT,
1402-
f"Expected {expected_count} leaf frames",
1403-
):
1404-
try:
1405-
traces = u.get_stack_trace()
1406-
except RuntimeError as exc:
1407-
if str(exc) != "Failed to parse initial frame in chain":
1408-
raise
1409-
continue
1410-
result = sorted(
1411-
f.lineno
1412-
for t in traces for f in t.frame_info
1413-
if f.funcname == "leaf"
1414-
)
1415-
# A new frame can still point at the function definition.
1416-
if (len(result) == expected_count and
1417-
leaf.__code__.co_firstlineno not in result):
1418-
return result
1417+
def lines(u):
1418+
return sorted(
1419+
f.lineno
1420+
for t in u.get_stack_trace() for f in t.frame_info
1421+
if f.funcname == "leaf"
1422+
)
14191423
1424+
# SimpleQueue.put() and Lock.acquire() do not push Python frames.
1425+
# Once notified, the worker's stack stays stable until go is released.
14201426
threading.Thread(target=leaf, daemon=True).start()
1421-
threading.Thread(target=lambda: (go.wait(), leaf()), daemon=True).start()
1427+
ready.get(timeout=support.SHORT_TIMEOUT)
1428+
threading.Thread(target=start_leaf, daemon=True).start()
1429+
ready.get(timeout=support.SHORT_TIMEOUT)
14221430
u = RemoteUnwinder(os.getpid(), all_threads=True)
1423-
before = lines(u, 1)
1424-
assert before == [8], before
1425-
go.set()
1426-
cached = lines(u, 2)
1427-
assert cached == [8, 8], cached
1431+
before = lines(u)
1432+
# The notification can be observed before put() returns, so either
1433+
# line in leaf() is a valid sample.
1434+
assert before in ([12], [13]), before
1435+
go.release()
1436+
ready.get(timeout=support.SHORT_TIMEOUT)
1437+
cached = lines(u)
1438+
assert len(cached) == 2, cached
1439+
assert all(line in (12, 13) for line in cached), cached
14281440
""")
14291441
result = subprocess.run(
14301442
[sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script],

0 commit comments

Comments
 (0)