@@ -1670,6 +1670,103 @@ def test_unsupported_platform_error(self):
16701670 str (cm .exception )
16711671 )
16721672
1673+ class TestDetectionOfThreadStatus (unittest .TestCase ):
1674+ @unittest .skipIf (
1675+ sys .platform not in ("linux" , "darwin" , "win32" ),
1676+ "Test only runs on unsupported platforms (not Linux, macOS, or Windows)" ,
1677+ )
1678+ @unittest .skipIf (sys .platform == "android" , "Android raises Linux-specific exception" )
1679+ def test_thread_status_detection (self ):
1680+ port = find_unused_port ()
1681+ script = textwrap .dedent (
1682+ f"""\
1683+ import time, sys, socket, threading
1684+ import os
1685+
1686+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1687+ sock.connect(('localhost', { port } ))
1688+
1689+ def sleeper():
1690+ tid = threading.get_native_id()
1691+ sock.sendall(f'ready:sleeper:{{tid}}\\ n'.encode())
1692+ time.sleep(10000)
1693+
1694+ def busy():
1695+ tid = threading.get_native_id()
1696+ sock.sendall(f'ready:busy:{{tid}}\\ n'.encode())
1697+ x = 0
1698+ while True:
1699+ x = x + 1
1700+ time.sleep(0.5)
1701+
1702+ t1 = threading.Thread(target=sleeper)
1703+ t2 = threading.Thread(target=busy)
1704+ t1.start()
1705+ t2.start()
1706+ sock.sendall(b'ready:main\\ n')
1707+ t1.join()
1708+ t2.join()
1709+ sock.close()
1710+ """
1711+ )
1712+ with os_helper .temp_dir () as work_dir :
1713+ script_dir = os .path .join (work_dir , "script_pkg" )
1714+ os .mkdir (script_dir )
1715+ server_socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
1716+ server_socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
1717+ server_socket .bind (("localhost" , port ))
1718+ server_socket .settimeout (SHORT_TIMEOUT )
1719+ server_socket .listen (1 )
1720+
1721+ script_name = _make_test_script (script_dir , "thread_status_script" , script )
1722+ client_socket = None
1723+ try :
1724+ p = subprocess .Popen ([sys .executable , script_name ])
1725+ client_socket , _ = server_socket .accept ()
1726+ server_socket .close ()
1727+ response = b""
1728+ sleeper_tid = None
1729+ busy_tid = None
1730+ while True :
1731+ chunk = client_socket .recv (1024 )
1732+ response += chunk
1733+ if b"ready:main" in response and b"ready:sleeper" in response and b"ready:busy" in response :
1734+ # Parse TIDs from the response
1735+ for line in response .split (b"\n " ):
1736+ if line .startswith (b"ready:sleeper:" ):
1737+ try :
1738+ sleeper_tid = int (line .split (b":" )[- 1 ])
1739+ except Exception :
1740+ pass
1741+ elif line .startswith (b"ready:busy:" ):
1742+ try :
1743+ busy_tid = int (line .split (b":" )[- 1 ])
1744+ except Exception :
1745+ pass
1746+ break
1747+
1748+ unwinder = RemoteUnwinder (p .pid , all_threads = True )
1749+ time .sleep (0.2 ) # Give a bit of time to let threads settle
1750+ traces = unwinder .get_stack_trace ()
1751+
1752+ # Find threads and their statuses
1753+ statuses = {}
1754+ for interpreter_info in traces :
1755+ for thread_info in interpreter_info .threads :
1756+ statuses [thread_info .thread_id ] = thread_info .status
1757+
1758+ self .assertIsNotNone (sleeper_tid , "Sleeper thread id not received" )
1759+ self .assertIsNotNone (busy_tid , "Busy thread id not received" )
1760+ self .assertIn (sleeper_tid , statuses , "Sleeper tid not found in sampled threads" )
1761+ self .assertIn (busy_tid , statuses , "Busy tid not found in sampled threads" )
1762+ self .assertEqual (statuses [sleeper_tid ], 1 , "Sleeper thread should be idle (1)" )
1763+ self .assertEqual (statuses [busy_tid ], 0 , "Busy thread should be running (0)" )
1764+
1765+ finally :
1766+ if client_socket is not None :
1767+ client_socket .close ()
1768+ p .terminate ()
1769+ p .wait (timeout = SHORT_TIMEOUT )
16731770
16741771if __name__ == "__main__" :
16751772 unittest .main ()
0 commit comments