]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-125541: Make Ctrl-C interrupt `threading.Lock.acquire()` on Windows (#125546)
authorSam Gross <colesbury@gmail.com>
Thu, 17 Oct 2024 18:10:55 +0000 (14:10 -0400)
committerGitHub <noreply@github.com>
Thu, 17 Oct 2024 18:10:55 +0000 (20:10 +0200)
Doc/library/_thread.rst
Doc/library/threading.rst
Misc/NEWS.d/next/Library/2024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst [new file with mode: 0644]
Python/parking_lot.c

index 6a66fc4c64bc450fc6e5b7a2a401f5edbc89eb63..ed29ac70035597cc6152235688adc2bec20f738e 100644 (file)
@@ -187,6 +187,9 @@ Lock objects have the following methods:
    .. versionchanged:: 3.2
       Lock acquires can now be interrupted by signals on POSIX.
 
+   .. versionchanged:: 3.14
+      Lock acquires can now be interrupted by signals on Windows.
+
 
 .. method:: lock.release()
 
@@ -219,12 +222,6 @@ In addition to these methods, lock objects can also be used via the
 * Calling :func:`sys.exit` or raising the :exc:`SystemExit` exception is
   equivalent to calling :func:`_thread.exit`.
 
-* It is platform-dependent whether the :meth:`~threading.Lock.acquire` method
-  on a lock can be interrupted (so that the :exc:`KeyboardInterrupt` exception
-  will happen immediately, rather than only after the lock has been acquired or
-  the operation has timed out). It can be interrupted on POSIX, but not on
-  Windows.
-
 * When the main thread exits, it is system defined whether the other threads
   survive.  On most systems, they are killed without executing
   :keyword:`try` ... :keyword:`finally` clauses or executing object
index cb82fea377697b587855aa36514c691e37f032cc..d4b343db36efb302a93f76fb9e922ec7559d6326 100644 (file)
@@ -567,6 +567,9 @@ All methods are executed atomically.
          Lock acquisition can now be interrupted by signals on POSIX if the
          underlying threading implementation supports it.
 
+      .. versionchanged:: 3.14
+         Lock acquisition can now be interrupted by signals on Windows.
+
 
    .. method:: release()
 
diff --git a/Misc/NEWS.d/next/Library/2024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst b/Misc/NEWS.d/next/Library/2024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst
new file mode 100644 (file)
index 0000000..7a20bca
--- /dev/null
@@ -0,0 +1,4 @@
+Pressing :kbd:`Ctrl-C` while blocked in :meth:`threading.Lock.acquire`,
+:meth:`threading.RLock.acquire`, and :meth:`threading.Thread.join` now
+interrupts the function call and raises a :exc:`KeyboardInterrupt` exception
+on Windows, similar to how those functions behave on macOS and Linux.
index a7e9760e35d87a806f7b4e3b8865cefd56e2e3c2..bffc959e5d09784c92a73243d9ca1c1939877dfd 100644 (file)
@@ -111,15 +111,28 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout)
             millis = (DWORD) div;
         }
     }
-    wait = WaitForSingleObjectEx(sema->platform_sem, millis, FALSE);
+
+    // NOTE: we wait on the sigint event even in non-main threads to match the
+    // behavior of the other platforms. Non-main threads will ignore the
+    // Py_PARK_INTR result.
+    HANDLE sigint_event = _PyOS_SigintEvent();
+    HANDLE handles[2] = { sema->platform_sem, sigint_event };
+    DWORD count = sigint_event != NULL ? 2 : 1;
+    wait = WaitForMultipleObjects(count, handles, FALSE, millis);
     if (wait == WAIT_OBJECT_0) {
         res = Py_PARK_OK;
     }
+    else if (wait == WAIT_OBJECT_0 + 1) {
+        ResetEvent(sigint_event);
+        res = Py_PARK_INTR;
+    }
     else if (wait == WAIT_TIMEOUT) {
         res = Py_PARK_TIMEOUT;
     }
     else {
-        res = Py_PARK_INTR;
+        _Py_FatalErrorFormat(__func__,
+            "unexpected error from semaphore: %u (error: %u)",
+            wait, GetLastError());
     }
 #elif defined(_Py_USE_SEMAPHORES)
     int err;