@@ -828,6 +828,89 @@ _Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug
828828
829829#elif defined(MS_WINDOWS )
830830
831+ static int
832+ wait_for_threads_to_stop (RemoteUnwinderObject * unwinder )
833+ {
834+ typedef NTSTATUS (NTAPI * NtGetNextThreadFunc )(
835+ HANDLE , HANDLE , ACCESS_MASK , ULONG , ULONG , PHANDLE );
836+ static NtGetNextThreadFunc pNtGetNextThread = NULL ;
837+ static int tried_load = 0 ;
838+
839+ if (!tried_load ) {
840+ HMODULE hNtdll = GetModuleHandleW (L"ntdll.dll" );
841+ if (hNtdll ) {
842+ pNtGetNextThread = (NtGetNextThreadFunc )GetProcAddress (
843+ hNtdll , "NtGetNextThread" );
844+ }
845+ tried_load = 1 ;
846+ }
847+ if (pNtGetNextThread == NULL ) {
848+ PyErr_SetString (PyExc_RuntimeError , "NtGetNextThread not available" );
849+ return -1 ;
850+ }
851+
852+ HANDLE previous = NULL ;
853+ int result = -1 ;
854+ for (;;) {
855+ HANDLE next = NULL ;
856+ // Enumerate with the available access, then obtain context access
857+ // separately so threads that deny it are not silently skipped.
858+ NTSTATUS status = pNtGetNextThread (unwinder -> handle .hProcess ,
859+ previous , MAXIMUM_ALLOWED , 0 , 0 , & next );
860+ if (previous != NULL ) {
861+ CloseHandle (previous );
862+ }
863+ previous = next ;
864+ if (status == STATUS_NO_MORE_ENTRIES ) {
865+ break ;
866+ }
867+ if (status < 0 ) {
868+ if (!is_process_alive (unwinder -> handle .hProcess )) {
869+ PyErr_Format (PyExc_ProcessLookupError ,
870+ "Process %d has terminated" , unwinder -> handle .pid );
871+ }
872+ else {
873+ PyErr_Format (PyExc_RuntimeError ,
874+ "NtGetNextThread failed: 0x%lx" , status );
875+ }
876+ goto done ;
877+ }
878+ HANDLE thread ;
879+ if (!DuplicateHandle (GetCurrentProcess (), next , GetCurrentProcess (),
880+ & thread , THREAD_GET_CONTEXT | SYNCHRONIZE ,
881+ FALSE, 0 )) {
882+ PyErr_SetFromWindowsErr (GetLastError ());
883+ goto done ;
884+ }
885+ // Suspension is asynchronous. Reading the context waits for the
886+ // thread to stop before we start reading the target's memory.
887+ CONTEXT context = {.ContextFlags = CONTEXT_CONTROL };
888+ if (!GetThreadContext (thread , & context )) {
889+ DWORD error = GetLastError ();
890+ int exited = WaitForSingleObject (thread , 0 ) == WAIT_OBJECT_0 ;
891+ CloseHandle (thread );
892+ if (exited ) {
893+ continue ;
894+ }
895+ PyErr_SetFromWindowsErr (error );
896+ goto done ;
897+ }
898+ CloseHandle (thread );
899+ }
900+ if (!is_process_alive (unwinder -> handle .hProcess )) {
901+ PyErr_Format (PyExc_ProcessLookupError ,
902+ "Process %d has terminated" , unwinder -> handle .pid );
903+ goto done ;
904+ }
905+ result = 0 ;
906+
907+ done :
908+ if (previous != NULL ) {
909+ CloseHandle (previous );
910+ }
911+ return result ;
912+ }
913+
831914void
832915_Py_RemoteDebug_InitThreadsState (RemoteUnwinderObject * unwinder , _Py_RemoteDebug_ThreadsState * st )
833916{
@@ -858,6 +941,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_T
858941 if (status >= 0 ) {
859942 st -> hProcess = unwinder -> handle .hProcess ;
860943 st -> suspended = 1 ;
944+ if (wait_for_threads_to_stop (unwinder ) < 0 ) {
945+ // pause_threads() has not yet set threads_stopped, so its caller
946+ // will not resume the process when we return an error.
947+ _Py_RemoteDebug_ResumeAllThreads (unwinder , st );
948+ return -1 ;
949+ }
861950 _Py_RemoteDebug_ClearCache (& unwinder -> handle );
862951 return 0 ;
863952 }
0 commit comments