qasync icon indicating copy to clipboard operation
qasync copied to clipboard

TypeError: arguments did not match any overloaded call QMutexLocker

Open blluv opened this issue 1 year ago • 10 comments

Exception in callback _ProactorReadPipeTransport._loop_reading(<_OverlappedF...hed result=10>)
handle: <Handle _ProactorReadPipeTransport._loop_reading(<_OverlappedF...hed result=10>)>
Traceback (most recent call last):
  File "asyncio\events.py", line 80, in _run
  File "asyncio\proactor_events.py", line 301, in _loop_reading
  File "qasync\_windows.py", line 86, in recv_into
TypeError: arguments did not match any overloaded call:
  QMutexLocker(mutex: Optional[QMutex]): argument 1 has unexpected type 'QMutexLocker'
  QMutexLocker(mutex: Optional[QRecursiveMutex]): argument 1 has unexpected type 'QMutexLocker'
Traceback (most recent call last):
  File "qasync\_windows.py", line 196, in run
  File "qasync\_windows.py", line 69, in select
  File "qasync\_windows.py", line 149, in _poll
TypeError: arguments did not match any overloaded call:
  QMutexLocker(mutex: Optional[QMutex]): argument 1 has unexpected type 'QMutexLocker'
  QMutexLocker(mutex: Optional[QRecursiveMutex]): argument 1 has unexpected type 'QMutexLocker'

blluv avatar Jan 01 '25 11:01 blluv

Is this project dead?

HubKing avatar Jan 26 '25 12:01 HubKing

I think the maintainers typically reply to say it's not dead but nothing ever gets merged so 🤷

See:

https://github.com/CabbageDevelopment/qasync/issues/119#issuecomment-2139643654

st-walker avatar Jan 26 '25 12:01 st-walker

We are also seeing this on the latest release (in the context of https://github.com/m-labs/artiq on Windows):

  File "C:\Users\LabUser\AppData\Local\Programs\Python\Python311\Lib\asyncio\streams.py", line 332, in write
    self._transport.write(data)
  File "C:\Users\LabUser\AppData\Local\Programs\Python\Python311\Lib\asyncio\proactor_events.py", line 365, in write
    self._loop_writing(data=bytes(data))
  File "C:\Users\LabUser\AppData\Local\Programs\Python\Python311\Lib\asyncio\proactor_events.py", line 401, in _loop_writing
    self._write_fut = self._loop._proactor.send(self._sock, data)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\LabUser\AppData\Local\pypoetry\Cache\virtualenvs\artiq-oitg-UIB2HjkM-py3.11\Lib\site-packages\qasync\_windows.py", line 102, in send
    with QtCore.QMutexLocker(self._lock):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: arguments did not match any overloaded call:
  QMutexLocker(mutex: Optional[QMutex]): argument 1 has unexpected type 'QMutexLocker'
  QMutexLocker(mutex: Optional[QRecursiveMutex]): argument 1 has unexpected type 'QMutexLocker'

I strongly suspect that this involves memory corruption or lifetime management issues, as self._lock is obviously not itself a QMutexLocker.

@blluv: Do you have a stand-alone reproducer for this?

dnadlinger avatar Jan 26 '25 16:01 dnadlinger

It would be interesting to see whether this also occurs with PySide instead of PyQt.

dnadlinger avatar Jan 26 '25 19:01 dnadlinger

May not be totally related, but I was getting errors like this when using the QApplication from Pyside instead of qasync. Switching to the qasync one fixed the async crashes I was getting.

halkony avatar Jan 27 '25 00:01 halkony

This was after determining that these crashes were memory faults by looking at the windows event viewer. For future readers, if you get unexplainable crashes from PyQT / Pyside, add this to your code to get the traceback.

import faulthandler
faulthandler.enable()

halkony avatar Jan 27 '25 00:01 halkony

May not be totally related, but I was getting errors like this when using the QApplication from Pyside instead of qasync. Switching to the qasync one fixed the async crashes I was getting.

Huh – isn't that just a re-exported version of the underlying PyQt/PySide one, though?

dnadlinger avatar Jan 27 '25 01:01 dnadlinger

It seems you are correct. Nevertheless my application hasn't had a random crash from a thread since I made that change. Seems preposterous.

halkony avatar Jan 27 '25 07:01 halkony

And it wasn't maybe the auto-selelction causing you to switch to a different (Py)Qt library?

dnadlinger avatar Jan 27 '25 08:01 dnadlinger

I got the similar exception as above: (It was just some simple asyncio task running at background that uses aiohttp and updates the UI.)

Traceback (most recent call last):
  File "{proj_path}\.venv\Lib\site-packages\qasync\_windows.py", line 196, in run
    events = self.__proactor.select(0.01)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "{proj_path}\.venv\Lib\site-packages\qasync\_windows.py", line 69, in select
    self._poll(timeout)
  File "{proj_path}\.venv\Lib\site-packages\qasync\_windows.py", line 149, in _poll
    with QtCore.QMutexLocker(self._lock):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: arguments did not match any overloaded call:
  QMutexLocker(mutex: QMutex): argument 1 has unexpected type 'QMutexLocker'
  QMutexLocker(mutex: QRecursiveMutex): argument 1 has unexpected type 'QMutexLocker'

The exception appears to be a type mismatch caught at runtime ? The exception happens out of nowhere, cannot determine if my code or qasync code or other triggers that.

The following "Application Error" found from windows event viewer seems related:

Faulting application name: python.exe,version: 3.11.4150.1013,time stamp: 0x64801a28 Faulting module name: Qt6Core.dll,version: 6.4.3.0,time stamp: 0x640c8ac4 exception code: 0xc0000409 Fault offset: 0x0000000000011918 Faulting application path: {user path}\AppData\Local\Programs\Python\Python311\python.exe Faulting module path : {proj_path}.venv\Lib\site-packages\PyQt6\Qt6\bin\Qt6Core.dll

from ms community or a stackoverflow question, the exception code seems to be a memory fault

The error code 0xc0000409 indicates a stack buffer overflow, which can occur due to various reasons such as a corrupt file or memory issue.

0xc0000409 means STATUS_STACK_BUFFER_OVERRUN.

In other words, something in your program is writing past the current stack frame, corrupting data on the stack. The program has detected this and rather than let it continue, has thrown an exception.

looks like "memory fault in official Qt6Core.dll" leads to "Type mismatch exception in qasync _windows.py", but self._lock is QtCore.QMutex() instance, how comes that memory fault (in c++'s view?) interpreted as a type mismatch exception (in python's view?) ?

RichardReallyHard avatar Mar 01 '25 09:03 RichardReallyHard

This is not a qasync bug. I was able to reproduce it on linux by pattern matching what Qasync was doing

import threading
import time
from PyQt6.QtCore import QMutex, QMutexLocker, QCoreApplication
import sys


class SharedResource:
    def __init__(self):
        self._lock = QMutex()
        print(f"Initial: self._lock is {type(self._lock).__name__} at {id(self._lock)}")
    
    def method_a(self):
        """Called from thread A"""
        for i in range(10000):
            try:
                # This is the pattern used in qasync
                with QMutexLocker(self._lock):
                    pass
            except TypeError as e:
                print(f"Thread A failed at iteration {i}: {e}")
                print(f"self._lock is now {type(self._lock).__name__} at {id(self._lock)}")
                return False
        return True
    
    def method_b(self):
        """Called from thread B (main thread)"""
        for i in range(10000):
            try:
                with QMutexLocker(self._lock):
                    pass
            except TypeError as e:
                print(f"Thread B failed at iteration {i}: {e}")
                print(f"self._lock is now {type(self._lock).__name__} at {id(self._lock)}")
                return False
        return True


def main():
    app = QCoreApplication(sys.argv)
    resource = SharedResource()
    
    # Start thread A
    thread_a = threading.Thread(target=resource.method_a)
    thread_a.start()
    
    # Run method_b in main thread
    success_b = resource.method_b()
    
    # Wait for thread A
    thread_a.join()
    
    print(f"\nFinal: self._lock is {type(resource._lock).__name__} at {id(resource._lock)}")
    
    if not success_b:
        print("\nBUG REPRODUCED: QMutex was replaced with QMutexLocker!")
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())

gives

python min.py
Initial: self._lock is QMutex at 139328410447568
Exception in thread Thread-1 (method_a):
Traceback (most recent call last):
  File "/usr/lib/python3.12/threading.py", line 1073, in bootstrapinner
    self.run()
  File "/usr/lib/python3.12/threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "/home/danielrh/dev/pyqtbug/min.py", line 22, in method_a
    with QMutexLocker(self._lock):
         ^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: wrapped C/C++ object of type QMutex has been deleted
Segmentation fault (core dumped)

This was PyQt6 version: 6.6.1 or 6.9.1 Python version: 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0]

the same script has more trouble on Python 3.11.9 with PyQt6 version 6.9.1 for some reason

danielrh avatar Aug 16 '25 06:08 danielrh

The stack trace is

Thread 3 "python" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff31fe6c0 (LWP 340823)]
Downloading source file /usr/src/python3.12-3.12.3-1ubuntu0.7/build-static/../Objects/weakrefobject.c
clear_weakref (self=0x7ffff7398e30) at ../Objects/weakrefobject.c:60            
warning: 60	../Objects/weakrefobject.c: No such file or directory
--Type <RET> for more, q to quit, c to continue without paging--
#0  clear_weakref (self=0x7ffff7398e30) at ../Objects/weakrefobject.c:60
#1  0x00000000005c8ce4 in PyObject_ClearWeakRefs (object=<optimized out>)
    at ../Objects/weakrefobject.c:952
#2  0x000000000059ea58 in subtype_dealloc (self=0x7ffff7398cd0)
    at ../Objects/typeobject.c:1974
#3  0x00000000005e0105 in _PyEval_EvalFrameDefault (tstate=<optimized out>, 
    frame=<optimized out>, throwflag=<optimized out>)
    at Python/bytecodes.c:2731
#4  0x000000000054cb32 in _PyObject_VectorcallTstate (kwnames=0x0, nargsf=1, 
    args=0x7ffff31fde18, callable=0x7ffff749d580, tstate=0xd8f0b0)
    at ../Include/internal/pycore_call.h:92
#5  method_vectorcall (method=<optimized out>, 
    args=0xb49140 <_PyRuntime+76288>, nargsf=<optimized out>, kwnames=0x0)
    at ../Objects/classobject.c:69
#6  0x00000000006f80dc in thread_run (boot_raw=0xd8f080)
    at ../Modules/_threadmodule.c:1114
#7  0x00000000006b931c in pythread_wrapper (arg=<optimized out>)
    at ../Python/thread_pthread.h:237
#8  0x00007ffff7c9caa4 in start_thread (arg=<optimized out>)
    at ./nptl/pthread_create.c:447
#9  0x00007ffff7d29c3c in clone3 ()

danielrh avatar Aug 16 '25 07:08 danielrh

Possibly this sequence of events:

Thread 1                          Thread 2
--------                          --------
with QMutexLocker(mutex):         with QMutexLocker(mutex):
  # Creates C++ QMutexLocker        # Creates C++ QMutexLocker
  # Increments wrapper refcount     # Race condition here!
  ...                               ...
# __exit__ called                 # __exit__ called
# Decrements refcount             # Decrements refcount
# INCORRECTLY frees QMutex!       # Tries to use freed memory
                                  # Python GC → SEGFAULT

danielrh avatar Aug 16 '25 07:08 danielrh

I have a much smaller repro case:

import threading
from PyQt6.QtCore import QMutex, QMutexLocker, QCoreApplication
import sys
import faulthandler
faulthandler.enable()

app = QCoreApplication(sys.argv)
mutex = QMutex()

def worker():
    for i in range(10000):
        with QMutexLocker(mutex):  # Crashes here
            pass

thread1 = threading.Thread(target=worker)
thread2 = threading.Thread(target=worker)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

danielrh avatar Aug 16 '25 08:08 danielrh

You can import this file at the beginning of your main and it should fix the problem by patching in native python threading.Lock() instead of using PyQt6's broken lock.

pyqt6_mutex_patch.py

danielrh avatar Aug 16 '25 08:08 danielrh

Perhaps developers of qasync can consider patching this in until Riverbank Computing fixes PyQt6

or avoiding the QMutexLocker by merging https://github.com/danielrh/qasync/commit/b497a153378bc948458574e3f595711c49347f31

danielrh avatar Aug 16 '25 08:08 danielrh

@danielrh Thanks, this is great! I did need to significantly increase the loop iteration count in your reproduction case (Python 3.12/Windows 11), but that's pretty cut and dry. When starting to look into this previously, avoiding QMutexLocker did seem to fix it, but I was never quite sure since I didn't have a self-contained test case yet. Sadly, I don't have commit access here, but I'll immediately roll out https://github.com/danielrh/qasync/commit/b497a153378bc948458574e3f595711c49347f31 in the application used across my research group.

dnadlinger avatar Aug 16 '25 12:08 dnadlinger

Fixed in https://github.com/CabbageDevelopment/qasync/pull/151

hosaka avatar Aug 28 '25 05:08 hosaka