From e04866c4a1bbf7d9b04a360a1bf435649f713396 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 15 Oct 2024 17:05:53 +0000 Subject: [PATCH 1/4] gh-125541: Make Ctrl-C interrupt `threading.Lock.acquire()` on Windows --- Doc/library/_thread.rst | 9 +++------ ...024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst | 3 +++ Python/parking_lot.c | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index 6a66fc4c64bc45..ed29ac70035597 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -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 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 index 00000000000000..db97c87f9a9fab --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst @@ -0,0 +1,3 @@ +Pressing :kbd:`Ctrl-C` while blocked in :meth:`threading.Lock.acquire` now +interrupts the function and raises a :exc:`KeyboardInterrupt` exception on +Windows, similar to how it behaves on macOS and Linux. diff --git a/Python/parking_lot.c b/Python/parking_lot.c index a7e9760e35d87a..eb9117a7b44656 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -111,15 +111,27 @@ _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 }; + wait = WaitForMultipleObjectsEx(2, handles, FALSE, millis, FALSE); 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; From 6744ee81001a99e03e6c72fc2f98dcf305ca2190 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 15 Oct 2024 18:35:49 +0000 Subject: [PATCH 2/4] sigint_event may be NULL early in CPython initialization --- Doc/library/threading.rst | 3 +++ Python/parking_lot.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index cb82fea377697b..d4b343db36efb3 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -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/Python/parking_lot.c b/Python/parking_lot.c index eb9117a7b44656..e0eb8ba0ed2646 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -117,7 +117,8 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout) // Py_PARK_INTR result. HANDLE sigint_event = _PyOS_SigintEvent(); HANDLE handles[2] = { sema->platform_sem, sigint_event }; - wait = WaitForMultipleObjectsEx(2, handles, FALSE, millis, FALSE); + DWORD count = sigint_event != NULL ? 2 : 1; + wait = WaitForMultipleObjectsEx(count, handles, FALSE, millis, FALSE); if (wait == WAIT_OBJECT_0) { res = Py_PARK_OK; } From 69e22e51db40c237dfb6e8b130847a21e69a3d8a Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 15 Oct 2024 21:08:54 +0000 Subject: [PATCH 3/4] Use WaitForMultipleObjects instead of WaitForMultipleObjectsEx --- Python/parking_lot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/parking_lot.c b/Python/parking_lot.c index e0eb8ba0ed2646..bffc959e5d0978 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -118,7 +118,7 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, PyTime_t timeout) HANDLE sigint_event = _PyOS_SigintEvent(); HANDLE handles[2] = { sema->platform_sem, sigint_event }; DWORD count = sigint_event != NULL ? 2 : 1; - wait = WaitForMultipleObjectsEx(count, handles, FALSE, millis, FALSE); + wait = WaitForMultipleObjects(count, handles, FALSE, millis); if (wait == WAIT_OBJECT_0) { res = Py_PARK_OK; } From fcfad7d1e26fb72437e148978d8902d558dd0a22 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 15 Oct 2024 21:10:58 +0000 Subject: [PATCH 4/4] Update blurb --- .../Library/2024-10-15-16-50-03.gh-issue-125541.FfhmWo.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index db97c87f9a9fab..7a20bca1739869 100644 --- 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 @@ -1,3 +1,4 @@ -Pressing :kbd:`Ctrl-C` while blocked in :meth:`threading.Lock.acquire` now -interrupts the function and raises a :exc:`KeyboardInterrupt` exception on -Windows, similar to how it behaves on macOS and Linux. +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.