Nuitka: Access violation/segmentation fault when disconnecting PySide6 signals

I am aware that you do not recommend bundling pytest, but I explained why I am doing it, and I wondered if “Windows fatal exception: access violation” sounds serious enough to take the off-chance of finding and fixing a more general issue.

This is my current environment - the same thing happens on Python 3.10.10:

C:\code\project>python -m nuitka --version
1.5rc12
Commercial: None
Python: 3.11.2 (tags/v3.11.2:878ead1, Feb  7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)]
Flavor: Unknown
Executable: C:\Users\bers\.pyenv-win-venv\envs\project_3.11\Scripts\python.exe
OS: Windows
Arch: x86_64
WindowsRelease: 10
Version C compiler: C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\bin\HostX64\x64\cl.exe (cl 14.3).

C:\Code\bug>pip freeze
attrs==22.2.0
colorama==0.4.6
iniconfig==2.0.0
Nuitka @ git+https://github.com/Nuitka/Nuitka@ab59f9bd0c25a39aaab870b6570d477a46731130
ordered-set==4.1.0
packaging==23.0
pluggy==1.0.0
PySide6-Essentials==6.4.2
pytest==7.2.1
pytest-qt==4.2.0
shiboken6==6.4.2
zstandard==0.20.0

Installed Nuitka from pip and GitHub.

bug.py

import pytest
pytest.main([__file__ + "/.."], ["pytestqt.plugin"])

test_bug.py (could potentially be minimized further)

def test_bug(qtbot):
    qtbot.waitUntil(lambda: False)

bug.bat

@echo off
python -m pip install PySide6-Essentials pytest-qt git+https://github.com/Nuitka/Nuitka
python -m nuitka ^
    --standalone ^
    --enable-plugin=pyside6 ^
    --include-data-file=test_bug.py=./ ^
    --include-module=PySide6.QtTest ^
    --include-module=pytest ^
    --include-module=_pytest ^
    --include-module=pytestqt ^
    bug.py

bug.dist\bug.exe

Output:

[...]
Nuitka:INFO: Successfully created 'bug.dist\bug.exe'.
=================================================================================== test session starts ===================================================================================
platform win32 -- Python 3.11.2, pytest-7.2.1, pluggy-1.0.0
PySide6 6.4.2 -- Qt runtime 6.4.2 -- Qt compiled 6.4.2
rootdir: C:\Code\bug
collected 1 item

BUG~1.DIS\test_bug.py Windows fatal exception: access violation

Current thread 0x00005fc4 (most recent call first):
  File "C:\Code\bug\BUG~1.DIS\pytestqt\wait_signal.py", line 34 in wait
  File "C:\Code\bug\BUG~1.DIS\pytestqt\qtbot.py", line 440 in wait
  File "C:\Code\bug\BUG~1.DIS\pytestqt\qtbot.py", line 474 in waitUntil
  File "C:\Code\bug\BUG~1.DIS\test_bug.py", line 2 in test_bug
  File "C:\Code\bug\BUG~1.DIS\_pytest\python.py", line 195 in pytest_pyfunc_call
  File "C:\Code\bug\BUG~1.DIS\pluggy\_callers.py", line 9 in _multicall
  File "C:\Code\bug\BUG~1.DIS\pluggy\_manager.py", line 77 in _hookexec
  File "C:\Code\bug\BUG~1.DIS\pluggy\_hooks.py", line 244 in __call__
  File "C:\Code\bug\BUG~1.DIS\_pytest\python.py", line 1789 in runtest
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 167 in pytest_runtest_call
  File "C:\Code\bug\BUG~1.DIS\pluggy\_callers.py", line 9 in _multicall
  File "C:\Code\bug\BUG~1.DIS\pluggy\_manager.py", line 77 in _hookexec
  File "C:\Code\bug\BUG~1.DIS\pluggy\_hooks.py", line 244 in __call__
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 260 in <lambda>
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 339 in from_call
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 259 in call_runtest_hook
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 220 in call_and_report
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 131 in runtestprotocol
  File "C:\Code\bug\BUG~1.DIS\_pytest\runner.py", line 112 in pytest_runtest_protocol
  File "C:\Code\bug\BUG~1.DIS\pluggy\_callers.py", line 9 in _multicall
  File "C:\Code\bug\BUG~1.DIS\pluggy\_manager.py", line 77 in _hookexec
  File "C:\Code\bug\BUG~1.DIS\pluggy\_hooks.py", line 244 in __call__
  File "C:\Code\bug\BUG~1.DIS\_pytest\main.py", line 349 in pytest_runtestloop
  File "C:\Code\bug\BUG~1.DIS\pluggy\_callers.py", line 9 in _multicall
  File "C:\Code\bug\BUG~1.DIS\pluggy\_manager.py", line 77 in _hookexec
  File "C:\Code\bug\BUG~1.DIS\pluggy\_hooks.py", line 244 in __call__
  File "C:\Code\bug\BUG~1.DIS\_pytest\main.py", line 324 in _main
  File "C:\Code\bug\BUG~1.DIS\_pytest\main.py", line 270 in wrap_session
  File "C:\Code\bug\BUG~1.DIS\_pytest\main.py", line 317 in pytest_cmdline_main
  File "C:\Code\bug\BUG~1.DIS\pluggy\_callers.py", line 9 in _multicall
  File "C:\Code\bug\BUG~1.DIS\pluggy\_manager.py", line 77 in _hookexec
  File "C:\Code\bug\BUG~1.DIS\pluggy\_hooks.py", line 244 in __call__
  File "C:\Code\bug\BUG~1.DIS\_pytest\config\__init__.py", line 167 in main
  File "C:\Code\bug\BUG~1.DIS\bug.py", line 1 in <module>

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 50 (18 by maintainers)

Most upvoted comments

I think the workaround for a corruption bug of PySide6 might actually help. Please checkout develop or 1.5.7

Here’s another funny variant that shows that you can reuse the same Blocker instance, and don’t need to connect the same kind of signal twice:

# Windows: python -m nuitka bug2.py && bug2.dist\bug2.exe
# Linux: python3 -m nuitka bug2.py && bug2.dist/bug2.bin
# nuitka-project: --quiet
# nuitka-project: --standalone
# nuitka-project: --enable-plugin=pyside6
# nuitka-project: --static-libpython=no
from PySide6.QtCore import QThread, QTimer

class Blocker:
    def __init__(self):
        self.connect_thread()
        self.connect_timer()

    def connect_thread(self):
        thread = QThread()
        thread.finished.connect(self.pass_method)

    def connect_timer(self):
        timer = QTimer()
        timer.timeout.connect(self.pass_method)

    def pass_method(self):
        pass

Blocker()
print("Done")

Minimized further - this is fully independent of pytest now:

bug2.py:

# nuitka-project: --quiet
# nuitka-project: --standalone
# nuitka-project: --enable-plugin=pyside6
# nuitka-project: --include-module=PySide6.QtGui
from PySide6.QtCore import QCoreApplication, QEventLoop, QTimer

class Blocker:
    def __init__(self):
        self.loop = QEventLoop()
        timer = QTimer(self.loop)
        timer.setSingleShot(True)
        timer.setInterval(10)
        timer.timeout.connect(self.quit)
        timer.start()
        self.loop.exec()

    def quit(self):
        self.loop.quit()

application = QCoreApplication()
Blocker()
Blocker()
print("Done.")

bug2.bat:

@echo off
python -m pip install PySide6-Essentials git+https://github.com/Nuitka/Nuitka
python -m nuitka bug2.py && bug2.dist\bug2.exe

Output:

Nuitka:WARNING: The Python version '3.11' is not officially supported by Nuitka '1.5rc12', but an upcoming release will change that. In the mean time use Python version '3.10' instead or newer Nuitka.

(And then the process ends without Done.)

Saying WSL is not very informative generally, as that is like telling me the virtualization technique, but we do not care for the kernel interactions, do we. So a --version output from there would also be nice.

Right, sorry for that. Version output on 3.11:

$ python -m nuitka --version
1.5rc12
Commercial: None
Python: 3.11.2 (main, Feb 12 2023, 13:27:53) [GCC 11.3.0]
Flavor: pyenv
Executable: /home/bers/.pyenv/versions/project_3.11/bin/python3
OS: Linux
Arch: x86_64
Distribution: Ubuntu (based on Debian) 22.04.1
Version C compiler: /usr/bin/gcc (gcc).

I am not sure what qtbot is in all of this, can you explain a bit what this is and how it knows to pass that arg.

qtbot is what pytest calls a fixture, and it is provided by pytest-qt through the pytestqt.qtbot.QtBot class. It is instantiated by pytest which passes some request, and which I believe is the reason that

Instances of this class should be accessed only by using a qtbot fixture, never instantiated directly.

(https://pytest-qt.readthedocs.io/en/latest/reference.html)

That makes debugging a bit more tricky. Ultimately, I expect that the failure in pytestqt/wait_signal.py is independent of qtbot, but the reproduction is tricky without it as everything is required to use some QThread.

Update: here’s a variant of the issue that does not use qtbot at all.

bug.py:

import time
from PySide6.QtCore import QCoreApplication
from pytestqt.wait_signal import MultiSignalBlocker, qt_api

def wait(ms):
    # from https://github.com/pytest-dev/pytest-qt/blob/f166fa765e251322d54f7277a1a59909035ed772/src/pytestqt/qtbot.py#L451-L452
    blocker = MultiSignalBlocker(timeout=ms, raising=False)
    blocker.wait()

def waitUntil(callback, *, timeout=5000):
    # from https://github.com/pytest-dev/pytest-qt/blob/f166fa765e251322d54f7277a1a59909035ed772/src/pytestqt/qtbot.py#L513-L545
    start = time.time()

    def timed_out():
        elapsed = time.time() - start
        elapsed_ms = elapsed * 1000
        return elapsed_ms > timeout

    timeout_msg = f"waitUntil timed out in {timeout} milliseconds"

    while True:
        try:
            result = callback()
        except AssertionError as e:
            if timed_out():
                raise TimeoutError(timeout_msg) from e
        else:
            if result not in (None, True, False):
                msg = (
                    "waitUntil() callback must return None, True or False, returned %r"
                )
                raise ValueError(msg % result)

            # 'assert' form
            if result is None:
                return

            # 'True/False' form
            if result:
                return
            if timed_out():
                raise TimeoutError(timeout_msg)
        wait(10)

application = QCoreApplication()
qt_api.set_qt_api("pyside6")
waitUntil(lambda: False)

python bug.py gives this expected output:

Traceback (most recent call last):
File "/mnt/c/Code/bug/bug.py", line 51, in <module>
    waitUntil(lambda: False)
  File "/mnt/c/Code/bug/bug.py", line 45, in waitUntil
    raise TimeoutError(timeout_msg)
TimeoutError: waitUntil timed out in 5000 milliseconds

Compiling and running this variant on Linux leads to a Segmentation fault. On Windows, there is no obvious error, but the process finishes pretty much instantly, rather than timing out after 5 seconds as it should. So something is wrong on both OSes.