@@ -2336,63 +2336,52 @@ def get_trace_with_opcodes(pid):
23362336 def test_tlbc_cache_refresh_after_growth (self ):
23372337 # Reproducer from gh-157660.
23382338 script = textwrap .dedent ("""\
2339- import os, sys, threading
2339+ import os, threading
23402340 from _remote_debugging import RemoteUnwinder
2341+ from _queue import SimpleQueue
23412342 from test import support
23422343
23432344 go = threading.Lock()
23442345 stop = threading.Lock()
23452346 go.acquire()
23462347 stop.acquire()
2348+ ready = SimpleQueue()
23472349
23482350 def leaf():
2351+ ready.put(None)
23492352 stop.acquire()
23502353
2351- def wait_for_leaf_frames(u, expected_count):
2352- for _ in support.sleeping_retry(
2353- support.SHORT_TIMEOUT,
2354- f"Expected {expected_count} leaf frames",
2355- ):
2356- # Wait until the workers have entered their native lock
2357- # calls, so sampling cannot race with Python frame changes.
2358- frames = sys._current_frames()
2359- frames.pop(threading.get_ident())
2360- if len(frames) != 18:
2361- continue
2362- leaves = [f for f in frames.values()
2363- if f.f_code is leaf.__code__]
2364- if (len(leaves) != expected_count or
2365- any(f.f_lineno != leaf.__code__.co_firstlineno + 1
2366- for f in leaves)):
2367- continue
2368- if any(f.f_code not in (leaf.__code__, start_leaf.__code__,
2369- threading.Thread.run.__code__)
2370- for f in frames.values()):
2371- continue
2372- try:
2373- traces = u.get_stack_trace()
2374- except RuntimeError as exc:
2375- if str(exc) != "Failed to parse initial frame in chain":
2376- raise
2377- continue
2378- count = sum(
2379- f.funcname == "leaf"
2380- for i in traces
2381- for t in i.threads for f in t.frame_info
2382- )
2383- if count == expected_count:
2384- return
2354+ def start_leaf():
2355+ ready.put(None)
2356+ go.acquire()
2357+ leaf()
23852358
2359+ def park():
2360+ ready.put(None)
2361+ stop.acquire()
2362+
2363+ def leaf_count(u):
2364+ return sum(
2365+ f.funcname == "leaf"
2366+ for i in u.get_stack_trace()
2367+ for t in i.threads for f in t.frame_info
2368+ )
2369+
2370+ # SimpleQueue.put() and Lock.acquire() do not push Python frames.
2371+ # Once notified, the worker's stack stays stable until go is released.
23862372 threading.Thread(target=leaf, daemon=True).start()
2373+ ready.get(timeout=support.SHORT_TIMEOUT)
23872374 for _ in range(16):
2388- threading.Thread(target=stop.acquire , daemon=True).start()
2389- start_leaf = lambda: (go.acquire(), leaf() )
2375+ threading.Thread(target=park , daemon=True).start()
2376+ ready.get(timeout=support.SHORT_TIMEOUT )
23902377 threading.Thread(target=start_leaf, daemon=True).start()
2378+ ready.get(timeout=support.SHORT_TIMEOUT)
23912379
23922380 u = RemoteUnwinder(os.getpid(), all_threads=True, cache_frames=False)
2393- wait_for_leaf_frames(u, 1)
2381+ assert leaf_count(u) == 1
23942382 go.release()
2395- wait_for_leaf_frames(u, 2)
2383+ ready.get(timeout=support.SHORT_TIMEOUT)
2384+ assert leaf_count(u) == 2
23962385 """ )
23972386 result = subprocess .run (
23982387 [sys .executable , "-X" , "gil=0" , "-X" , "tlbc=1" , "-c" , script ],
@@ -2414,66 +2403,51 @@ def wait_for_leaf_frames(u, expected_count):
24142403 def test_tlbc_cache_refresh_after_slot_fill (self ):
24152404 # Reproducer from gh-157660.
24162405 script = textwrap .dedent ("""\
2417- import os, sys, threading
2406+ import os, threading
24182407 from _remote_debugging import RemoteUnwinder
2408+ from _queue import SimpleQueue
24192409
24202410 go = threading.Lock()
24212411 stop = threading.Lock()
24222412 go.acquire()
24232413 stop.acquire()
2414+ ready = SimpleQueue()
24242415
24252416 def leaf():
2417+ ready.put(None)
24262418 stop.acquire()
24272419
2420+ def start_leaf():
2421+ ready.put(None)
2422+ go.acquire()
2423+ leaf()
2424+
24282425 from test import support
24292426
2430- def lines(u, expected_count):
2431- for _ in support.sleeping_retry(
2432- support.SHORT_TIMEOUT,
2433- f"Expected {expected_count} leaf frames",
2434- ):
2435- # Wait until the workers have entered their native lock
2436- # calls, so sampling cannot race with Python frame changes.
2437- frames = sys._current_frames()
2438- frames.pop(threading.get_ident())
2439- if len(frames) != 2:
2440- continue
2441- leaves = [f for f in frames.values()
2442- if f.f_code is leaf.__code__]
2443- if (len(leaves) != expected_count or
2444- any(f.f_lineno != leaf.__code__.co_firstlineno + 1
2445- for f in leaves)):
2446- continue
2447- if any(f.f_code not in (leaf.__code__, start_leaf.__code__,
2448- threading.Thread.run.__code__)
2449- for f in frames.values()):
2450- continue
2451- try:
2452- traces = u.get_stack_trace()
2453- except RuntimeError as exc:
2454- if str(exc) != "Failed to parse initial frame in chain":
2455- raise
2456- continue
2457- result = sorted(
2458- f.location.lineno
2459- for i in traces
2460- for t in i.threads for f in t.frame_info
2461- if f.funcname == "leaf"
2462- )
2463- # A new frame can still point at the function definition.
2464- if (len(result) == expected_count and
2465- leaf.__code__.co_firstlineno not in result):
2466- return result
2427+ def lines(u):
2428+ return sorted(
2429+ f.location.lineno
2430+ for i in u.get_stack_trace()
2431+ for t in i.threads for f in t.frame_info
2432+ if f.funcname == "leaf"
2433+ )
24672434
2435+ # SimpleQueue.put() and Lock.acquire() do not push Python frames.
2436+ # Once notified, the worker's stack stays stable until go is released.
24682437 threading.Thread(target=leaf, daemon=True).start()
2469- start_leaf = lambda: (go.acquire(), leaf() )
2438+ ready.get(timeout=support.SHORT_TIMEOUT )
24702439 threading.Thread(target=start_leaf, daemon=True).start()
2440+ ready.get(timeout=support.SHORT_TIMEOUT)
24712441 u = RemoteUnwinder(os.getpid(), all_threads=True, cache_frames=False)
2472- before = lines(u, 1)
2473- assert before == [10], before
2442+ before = lines(u)
2443+ # The notification can be observed before put() returns, so either
2444+ # line in leaf() is a valid sample.
2445+ assert before in ([12], [13]), before
24742446 go.release()
2475- cached = lines(u, 2)
2476- assert cached == [10, 10], cached
2447+ ready.get(timeout=support.SHORT_TIMEOUT)
2448+ cached = lines(u)
2449+ assert len(cached) == 2, cached
2450+ assert all(line in (12, 13) for line in cached), cached
24772451 """ )
24782452 result = subprocess .run (
24792453 [sys .executable , "-X" , "gil=0" , "-X" , "tlbc=1" , "-c" , script ],
0 commit comments