@@ -2596,6 +2596,109 @@ def test_unsupported_platform_error(self):
25962596
25972597@requires_remote_subprocess_debugging ()
25982598class TestDetectionOfThreadStatus (RemoteInspectionTestBase ):
2599+ def _check_cpu_status_with_multiple_thread_states (
2600+ self , * , native = False , subinterpreter = False ,
2601+ ):
2602+ import_module ("_testinternalcapi" )
2603+ import_module ("_interpreters" )
2604+ if native :
2605+ import_module ("_hashlib" )
2606+ work = "_hashlib.pbkdf2_hmac('sha256', b'password', b'salt', 10_000_000)"
2607+ else :
2608+ work = "pass"
2609+ code = textwrap .dedent (f"""
2610+ import socket
2611+ { "import _hashlib" if native else "" }
2612+ active_sock = socket.socket(fileno={{fd}})
2613+
2614+ def active():
2615+ active_sock.sendall(b"ready")
2616+ while True:
2617+ { work }
2618+
2619+ active()
2620+ """ )
2621+ script = f"""
2622+ import _interpreters, _testinternalcapi
2623+
2624+ def suspended():
2625+ if { subinterpreter } :
2626+ interpid = _interpreters.create()
2627+ else:
2628+ interpid = _interpreters.get_current()[0]
2629+ # Create another state on this OS thread.
2630+ code = { code !r} .format(fd=sock.fileno())
2631+ _testinternalcapi.exec_interpreter(interpid, code)
2632+
2633+ suspended()
2634+ """
2635+ with self ._target_process (script ) as (p , sock , _ ):
2636+ _wait_for_signal (sock , b"ready" )
2637+ for mode in (PROFILING_MODE_CPU , PROFILING_MODE_ALL ):
2638+ with self .subTest (mode = mode ):
2639+ unwinder = RemoteUnwinder (
2640+ p .pid , all_threads = True , mode = mode ,
2641+ skip_non_matching_threads = (mode == PROFILING_MODE_CPU ),
2642+ )
2643+ for _ in busy_retry (SHORT_TIMEOUT ):
2644+ try :
2645+ trace = unwinder .get_stack_trace ()
2646+ except TRANSIENT_ERRORS :
2647+ continue
2648+ active = self ._find_thread_with_frame (
2649+ trace , lambda f : f .funcname == "active" )
2650+ if active is None or not active .status & THREAD_STATUS_ON_CPU :
2651+ continue
2652+ if native and active .status & THREAD_STATUS_HAS_GIL :
2653+ continue
2654+ suspended = self ._find_thread_with_frame (
2655+ trace , lambda f : f .funcname == "suspended" )
2656+ if mode == PROFILING_MODE_CPU :
2657+ self .assertIsNone (suspended )
2658+ else :
2659+ self .assertIsNotNone (suspended )
2660+ self .assertEqual (suspended .thread_id , active .thread_id )
2661+ self .assertFalse (suspended .status & THREAD_STATUS_ON_CPU )
2662+ break
2663+
2664+ @skip_if_not_supported
2665+ def test_cpu_status_with_multiple_thread_states (self ):
2666+ for native in (False , True ):
2667+ for subinterpreter in (False , True ):
2668+ with self .subTest (native = native , subinterpreter = subinterpreter ):
2669+ self ._check_cpu_status_with_multiple_thread_states (
2670+ native = native , subinterpreter = subinterpreter )
2671+
2672+ @skip_if_not_supported
2673+ def test_cpu_status_after_tstate_callback (self ):
2674+ import_module ("_testinternalcapi" )
2675+ for retain in (False , True ):
2676+ with self .subTest (retain = retain ):
2677+ script = f"""
2678+ import _testinternalcapi
2679+
2680+ def ready():
2681+ sock.sendall(b"ready")
2682+
2683+ def native_work():
2684+ _testinternalcapi.native_work_after_tstate_callback(
2685+ ready, { SHORT_TIMEOUT * 3 } , { retain } )
2686+
2687+ native_work()
2688+ """
2689+ with self ._target_process (script ) as (p , sock , _ ):
2690+ _wait_for_signal (sock , b"ready" )
2691+ for mode in (PROFILING_MODE_CPU , PROFILING_MODE_ALL ):
2692+ with self .subTest (mode = mode ):
2693+ unwinder = RemoteUnwinder (p .pid , all_threads = True , mode = mode )
2694+ for _ in busy_retry (SHORT_TIMEOUT ):
2695+ trace = _get_stack_trace_with_retry (unwinder )
2696+ thread = self ._find_thread_with_frame (
2697+ trace , lambda f : f .funcname == "native_work" )
2698+ if thread and thread .status & THREAD_STATUS_ON_CPU :
2699+ self .assertFalse (thread .status & THREAD_STATUS_HAS_GIL )
2700+ break
2701+
25992702 def _run_thread_status_test (self , mode , check_condition ):
26002703 """
26012704 Common pattern for thread status detection tests.
0 commit comments