GitHub CopilotWrite better code with AI | MCP RegistryIntegrate external tools | ActionsAutomate any workflow | CodespacesInstant dev environments | IssuesPlan and track work | Code ReviewManage code changes | Code QualityEnforce quality at merge | Why GitHub | Marketplace | View all features | Enterprises | Small and medium teams | Startups | View all use cases | View all industries | View all solutions | AI | Software Development | DevOps | Security | View all topics | Customer stories | Events & webinars | Ebooks & reports | Business insights | Trust center | Partners | View all resources
@@ -1,7 +1,7 @@
1-importcontextlib
21importunittest
32importos
43importtextwrap
4+importcontextlib
55importimportlib
66importsys
77importsocket
@@ -216,33 +216,13 @@ def requires_subinterpreters(meth):
216216# Simple wrapper functions for RemoteUnwinder
217217# ============================================================================
218218219-# Errors that can occur transiently when reading process memory without synchronization
220-RETRIABLE_ERRORS= (
221-"Task list appears corrupted",
222-"Invalid linked list structure reading remote memory",
223-"Unknown error reading memory",
224-"Unhandled frame owner",
225-"Failed to parse initial frame",
226-"Failed to process frame chain",
227-"Failed to unwind stack",
228-)
229-230-231-def_is_retriable_error(exc):
232-"""Check if an exception is a transient error that should be retried."""
233-msg=str(exc)
234-returnany(msg.startswith(err) orerrinmsgforerrinRETRIABLE_ERRORS)
235-236-237219defget_stack_trace(pid):
238220for_inbusy_retry(SHORT_TIMEOUT):
239221try:
240222unwinder=RemoteUnwinder(pid, all_threads=True, debug=True)
241223returnunwinder.get_stack_trace()
242224exceptRuntimeErrorase:
243-if_is_retriable_error(e):
244-continue
245-raise
225+continue
246226raiseRuntimeError("Failed to get stack trace after retries")
247227248228@@ -252,9 +232,7 @@ def get_async_stack_trace(pid):
252232unwinder=RemoteUnwinder(pid, debug=True)
253233returnunwinder.get_async_stack_trace()
254234exceptRuntimeErrorase:
255-if_is_retriable_error(e):
256-continue
257-raise
235+continue
258236raiseRuntimeError("Failed to get async stack trace after retries")
259237260238@@ -264,9 +242,7 @@ def get_all_awaited_by(pid):
264242unwinder=RemoteUnwinder(pid, debug=True)
265243returnunwinder.get_all_awaited_by()
266244exceptRuntimeErrorase:
267-if_is_retriable_error(e):
268-continue
269-raise
245+continue
270246raiseRuntimeError("Failed to get all awaited_by after retries")
271247272248@@ -2268,18 +2244,13 @@ def make_unwinder(cache_frames=True):
22682244def_get_frames_with_retry(self, unwinder, required_funcs):
22692245"""Get frames containing required_funcs, with retry for transient errors."""
22702246for_inrange(MAX_TRIES):
2271-try:
2247+withcontextlib.suppress(OSError, RuntimeError):
22722248traces=unwinder.get_stack_trace()
22732249forinterpintraces:
22742250forthreadininterp.threads:
22752251funcs= {f.funcnameforfinthread.frame_info}
22762252ifrequired_funcs.issubset(funcs):
22772253returnthread.frame_info
2278-exceptRuntimeErrorase:
2279-if_is_retriable_error(e):
2280-pass
2281-else:
2282-raise
22832254time.sleep(0.1)
22842255returnNone
22852256@@ -2802,70 +2773,39 @@ def foo2():
28022773make_unwinder,
28032774 ):
28042775unwinder=make_unwinder(cache_frames=True)
2805-buffer=b""
2806-2807-defrecv_msg():
2808-"""Receive a single message from socket."""
2809-nonlocalbuffer
2810-whileb"\n"notinbuffer:
2811-chunk=client_socket.recv(256)
2812-ifnotchunk:
2813-returnNone
2814-buffer+=chunk
2815-msg, buffer=buffer.split(b"\n", 1)
2816-returnmsg
2817-2818-defget_thread_frames(target_funcs):
2819-"""Get frames for thread matching target functions."""
2820-retries=0
2821-for_inbusy_retry(SHORT_TIMEOUT):
2822-ifretries>=5:
2823-break
2824-retries+=1
2825-# On Windows, ReadProcessMemory can fail with OSError
2826-# (WinError 299) when frame pointers are in flux
2827-withcontextlib.suppress(RuntimeError, OSError):
2828-traces=unwinder.get_stack_trace()
2829-forinterpintraces:
2830-forthreadininterp.threads:
2831-funcs= [f.funcnameforfinthread.frame_info]
2832-ifany(finfuncsforfintarget_funcs):
2833-returnfuncs
2834-returnNone
2776+2777+# Message dispatch table: signal -> required functions for that thread
2778+dispatch= {
2779+b"t1:baz1": {"baz1", "bar1", "foo1"},
2780+b"t2:baz2": {"baz2", "bar2", "foo2"},
2781+b"t1:blech1": {"blech1", "foo1"},
2782+b"t2:blech2": {"blech2", "foo2"},
2783+ }
2835278428362785# Track results for each sync point
28372786results= {}
283827872839-# Process 4 sync points: baz1, baz2, blech1, blech2
2840-# With the lock, threads are serialized - handle one at a time
2841-for_inrange(4):
2842-msg=recv_msg()
2843-self.assertIsNotNone(msg, "Expected message from subprocess")
2844-2845-# Determine which thread/function and take snapshot
2846-ifmsg==b"t1:baz1":
2847-funcs=get_thread_frames(["baz1", "bar1", "foo1"])
2848-self.assertIsNotNone(funcs, "Thread 1 not found at baz1")
2849-results["t1:baz1"] =funcs
2850-elifmsg==b"t2:baz2":
2851-funcs=get_thread_frames(["baz2", "bar2", "foo2"])
2852-self.assertIsNotNone(funcs, "Thread 2 not found at baz2")
2853-results["t2:baz2"] =funcs
2854-elifmsg==b"t1:blech1":
2855-funcs=get_thread_frames(["blech1", "foo1"])
2856-self.assertIsNotNone(funcs, "Thread 1 not found at blech1")
2857-results["t1:blech1"] =funcs
2858-elifmsg==b"t2:blech2":
2859-funcs=get_thread_frames(["blech2", "foo2"])
2860-self.assertIsNotNone(funcs, "Thread 2 not found at blech2")
2861-results["t2:blech2"] =funcs
2862-2863-# Release thread to continue
2788+# Process 4 sync points (order depends on thread scheduling)
2789+buffer=_wait_for_signal(client_socket, b"\n")
2790+foriinrange(4):
2791+# Extract first message from buffer
2792+msg, sep, buffer=buffer.partition(b"\n")
2793+self.assertIn(msg, dispatch, f"Unexpected message: {msg!r}")
2794+2795+# Sample frames for the thread at this sync point
2796+required_funcs=dispatch[msg]
2797+frames=self._get_frames_with_retry(unwinder, required_funcs)
2798+self.assertIsNotNone(frames, f"Thread not found for {msg!r}")
2799+results[msg] = [f.funcnameforfinframes]
2800+2801+# Release thread and wait for next message (if not last)
28642802client_socket.sendall(b"k")
2803+ifi<3:
2804+buffer+=_wait_for_signal(client_socket, b"\n")
2865280528662806# Validate Phase 1: baz snapshots
2867-t1_baz=results.get("t1:baz1")
2868-t2_baz=results.get("t2:baz2")
2807+t1_baz=results.get(b"t1:baz1")
2808+t2_baz=results.get(b"t2:baz2")
28692809self.assertIsNotNone(t1_baz, "Missing t1:baz1 snapshot")
28702810self.assertIsNotNone(t2_baz, "Missing t2:baz2 snapshot")
28712811@@ -2890,8 +2830,8 @@ def get_thread_frames(target_funcs):
28902830self.assertNotIn("foo1", t2_baz)
2891283128922832# Validate Phase 2: blech snapshots (cache invalidation test)
2893-t1_blech=results.get("t1:blech1")
2894-t2_blech=results.get("t2:blech2")
2833+t1_blech=results.get(b"t1:blech1")
2834+t2_blech=results.get(b"t2:blech2")
28952835self.assertIsNotNone(t1_blech, "Missing t1:blech1 snapshot")
28962836self.assertIsNotNone(t2_blech, "Missing t2:blech2 snapshot")
28972837