Eryk Sun <eryk...@gmail.com> added the comment:

> But on this particular issue, making the unconditional wait be 
> interruptable by signals shouldn't be impossible.

PyThread_acquire_lock_timed() in Python/thread_nt.h currently ignores 
intr_flag. The current implementation calls EnterNonRecursiveMutex(), which in 
turn calls PyCOND_WAIT() / PyCOND_TIMEDWAIT() in Python/condvar.h. 
EnterNonRecursiveMutex() needs to support intr_flag and support a return value 
that indicates the wait was interrupted. PyThread_acquire_lock_timed() needs to 
handle this value by returning PY_LOCK_INTR.

When using emulated condition variables, a lock combines a semaphore and a 
critical section. Waiting on the semaphore can be integrated with the SIGINT 
event via WaitForMultipleObjects(). 

Here's a replacement for WaitForSingleObject() that integrates the SIGINT 
event, and supports long waits passed as a PY_TIMEOUT_T in microseconds (just 
for the sake of discussion; it's not rigorously tested code):

    unsigned long
    _Py_WaitForSingleObject(void *handle, PY_TIMEOUT_T microseconds,
                            int intr_flag)
    {
        DWORD result;
        DWORD handle_count;
        HANDLE handle_array[2];
        HANDLE sigint_event = NULL;

        LONGLONG timeout = -1;
        ULONGLONG deadline = 0;

        /* Store timeout in system time units of 100 ns. */
        if (microseconds >= 0) {
            QueryUnbiasedInterruptTime(&deadline);
            timeout = microseconds * 10;
            deadline += timeout;
        }

        handle_count = 1;
        handle_array[0] = (HANDLE)handle;

        if (intr_flag) {
            sigint_event = _PyOS_SigintEvent();
            if (sigint_event) {
                handle_array[handle_count++] = sigint_event;
                ResetEvent(sigint_event);
            }
        }

        do {
            ULONGLONG now;
            DWORD milliseconds;

            if (timeout < 0) {
                milliseconds = INFINITE;
            } else if (timeout < INFINITE * 10000) {
                milliseconds = timeout / 10000;
            } else {
                milliseconds = INFINITE - 1;
            }

            result = WaitForMultipleObjectsEx(
                        handle_count, handle_array, FALSE,
                        milliseconds, FALSE);

            if (sigint_event && result == WAIT_OBJECT_0 + 1) {
                /* Pretend that this was an alertable wait that
                   was interrupted by a user-mode APC queued to
                   the main thread by the C signal handler. It's
                   not implemented that way, but it could be. */
                result = STATUS_USER_APC;
            }

            if (result != WAIT_TIMEOUT) {
                break;
            }

            QueryUnbiasedInterruptTime(&now);
            timeout = deadline - now;
        } while (timeout >= 0);

        return result;
    }

If the wait returns STATUS_USER_APC, then the caller should call 
PyErr_CheckSignals(). intr_flag would presumably only be true when called from 
a thread that can handle signals, i.e. when _PyOS_IsMainThread() is true.

That said, if actual Windows condition variables are used (an alternate 
implementation in Python/condvar.h), then waiting is implemented via 
SleepConditionVariableSRW(). There's no way to integrate the SIGINT event with 
this wait, nor any documented way to cancel the wait from the console control 
thread. If this implementation is adopted, then maybe the few cases that 
require locks that support an interruptible wait can be implemented as a 
separate thread API.

----------
versions: +Python 3.10, Python 3.8, Python 3.9 -Python 2.7, Python 3.7

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue35935>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to