pyinstaller icon indicating copy to clipboard operation
pyinstaller copied to clipboard

PySide6 6.4.1 app periodically hangs straight after launch on macOS Ventura 13.0.1

Open jamesbcd opened this issue 3 years ago • 12 comments

Description of the issue

My PySide6-based app will periodically hang straight after launch on macOS Ventura 13.0.1. It doesn't happen every time - probably 1 in 10 or so launches hang. When I run the code unfrozen, it never hangs. I've reproduced the bug using a really minimal example below: just displaying a single message dialog.

Context information (for bug reports)

  • Output of pyinstaller --version: 5.7.0

  • Version of Python: Python 3.10.9 (v3.10.9:1dd9be6584, Dec 6 2022, 14:37:36) [Clang 13.0.0 (clang-1300.0.29.30)]

  • Platform: macOS Ventura 13.0.1

  • How you installed Python: From python.org (macOS 64-bit universal2 installer)

  • Did you also try this on another platform? Does it work there? Haven't tried yet.

  • try the latest development version: Tried latest devel version - hangs still occur.

    • [x] start with clean installation
    • [x] use the latest development version
    • [x] Run your frozen program from a command window (shell) — instead of double-clicking on it

When I run the frozen app from the command line (./dist/HangTest.app/Contents/MacOS/HangTest) it never seems to hang. The hang only occurs when launched from the Finder.

  • [x] Package your program in --onedir mode
  • [x] Package without UPX, say: use the option --noupx or set upx=False in your .spec-file
  • [x] Repackage you application in verbose/debug mode. For this, pass the option --debug to pyi-makespec or pyinstaller or use EXE(..., debug=1, ...) in your .spec file.

A minimal example program which shows the error

hang_test.py

from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import Qt


def main():
    app = QtWidgets.QApplication()
    QtWidgets.QMessageBox.information(None, "Test", "Hello World!")


if __name__ == "__main__":
    main()

hang_test.spec

# -*- mode: python ; coding: utf-8 -*-


#
# Set various parameters
#
app_version = "0.1.0"
onefile = False
block_cipher = None


#
# Main spec file elements
#
a = Analysis(['hang_test.py'],
        binaries=[],
        datas=[],
        hiddenimports=[],
        hookspath=[],
        runtime_hooks=[],
        excludes=[],
        win_no_prefer_redirects=False,
        win_private_assemblies=False,
        cipher=block_cipher,
        noarchive=False
    )

pyz = PYZ(a.pure, a.zipped_data,
            cipher=block_cipher)

exe = EXE(pyz,
        a.scripts,
        a.binaries if onefile else [],
        a.zipfiles if onefile else [],
        a.datas if onefile else [],
        exclude_binaries=not onefile,
        name='HangTest',
        debug=True,
        bootloader_ignore_signals=False,
        strip=False,
        upx=False,
        upx_exclude=[],
        runtime_tmpdir=None,
        console=False,
        disable_windowed_traceback=False,
        argv_emulation=True,
        target_arch='universal2',
        codesign_identity=None,
        entitlements_file=None,
    )
bundle_obj = exe

if not onefile:
    coll = COLLECT(
            exe,
            a.binaries,
            a.zipfiles,
            a.datas,
            strip=False,
            upx=True,
            upx_exclude=[],
            name='HangTest',
        )
    bundle_obj = coll

app = BUNDLE(bundle_obj,
        name='HangTest.app',
        bundle_identifier="app.hangtest",
        info_plist={
            'CFBundleShortVersionString': app_version,
        }
    )

Stacktrace / full error message

Diagnostic Report with stacktrace attached: HangTest_2022-12-27-225244.hang.txt

jamesbcd avatar Dec 27 '22 13:12 jamesbcd

When I run the frozen app from the command line (./dist/HangTest.app/Contents/MacOS/HangTest) it never seems to hang. The hang only occurs when launched from the Finder.

I suppose the OS might treat applications that are ran from command-line differently than when they are launched as apps via Finder, in terms of permissible unresponsiveness intervals (for example, classic POSIX applications might be expected to have busy main loop, while GUI apps are supposed to have a running event loop).


That said, I'm not sure running Qt dialogs without explicitly starting the application's event loop first is a good idea (the dialogs do run local event loops, but you never know...)

Does the hang also occur with the following example (displaying regular window in application's event loop):

import sys

from PySide6 import QtCore, QtWidgets

def main():
    app = QtWidgets.QApplication(sys.argv)
    window = QtWidgets.QWidget()
    window.setWindowTitle("Hello world!")
    window.show()
    app.exec()

if __name__ == '__main__':
    main()

or this one (displaying the message box from within the event loop):

import sys

from PySide6 import QtCore, QtWidgets

def show_dialog():
    QtWidgets.QMessageBox.information(None, "Test", "Hello World!")
    QtWidgets.QApplication.quit()

def main():
    app = QtWidgets.QApplication(sys.argv)
    
    QtCore.QTimer.singleShot(0, show_dialog)
    app.exec()

if __name__ == '__main__':
    main()

Also, do you have a way to check if the hang also occurs on older macOS versions (e.g., Monterey) or is it specific to Ventura? (For example, I cannot seem to reproduce it on x86_64 Monterey...)

rokm avatar Dec 27 '22 14:12 rokm

Yep, tried both those examples, and still get the periodic hangs (still about 1 in 10 launches). (Your first example is closer to my actual app.)

I should mention this is running on Apple Silicon (M2 cpu), with the python.org universal2 build, producing a universal2 app. The only other mac I have access to is an old machine running Mojave (10.14), which I could try tomorrow.

jamesbcd avatar Dec 27 '22 14:12 jamesbcd

Hmmm.... This will be a pain, because we don't really have a baseline to compare the behavior to. I.e., anything ran directly from command-line seems to behave differently (so the non-PyInstaller baseline would be a C++-built Qt6 app)...

The hang report you attached seems to imply that the hang is happening in the Qt's event loop, which is well beyond our control, too (unless the problem is somehow tied to our bootloader).

Some additional questions/things to try:

  • does switching to thin builds make any difference? I.e., making arm64 build and running it natively, or making an x86_64 build and running it under Rosetta?
  • in the example spec file, you are using debug=True; does switching to release bootloader (debug=False) make any difference?
  • does it make any difference if you disable argv_emulation (unless you explicitly need it)?
  • does the problem (in the test examples) occur only with PySide6, or also with PyQt6, PySide2, and/or PyQt5 bindings?

rokm avatar Dec 27 '22 16:12 rokm

I can't reproduce this even on my M1 mac. I'm using your example code and spec file from your initial post, universal2 Python 3.10.8 from python.org/downloads, PyInstaller 5.7.0 and the latest versions of PySide6 and its dependencies (6.4.1) and I'm running on macOS 12.6. I really hope that the distinguishing factor isn't either the M1 vs M2 or macOS 12 vs 13.

bwoodsend avatar Dec 27 '22 17:12 bwoodsend

The culprit seems to be argv_emulation. When I set this to False as @rokm suggested I cannot reproduce the hang. This is true both for the minimal examples and also for my actual application. Good tip – thankyou!

(Prior to this, I could still reproduce the hang using both arm64 and x86_64 builds (interestingly, hangs were much more common under Rosetta), using debug=False or debug=True, with both PySide6 (6.4.1) and PyQt6 (6.4.0) and also trying rolling back to an earlier version of PySide6 (6.3.2).)

Perhaps Ventura has some breaking applescript-related changes?

jamesbcd avatar Dec 29 '22 14:12 jamesbcd

I've been meaning to upgrade my mac to 13 (just waiting to get to a good stopping point so I can close everything on it and reboot it) so if you don't mind waiting a few days, I can put the macOS 12 vs 13 theory to test.

bwoodsend avatar Dec 29 '22 15:12 bwoodsend

On macOS 13.1, running the app (without rebuilding since the OS upgrade) about 20 times gave one dialog which opened but won't respond. Neither the OK, Close or Minimise buttons work.

bwoodsend avatar Jan 07 '23 22:01 bwoodsend

interestingly, hangs were much more common under Rosetta

This smells like some kind of race condition in our argv emulation code then - where Rosetta is merely slowing something down towards the unhappy path. There are some magic timeouts dotted around in that code.

> git grep --perl-regexp 'pyi_apple_.*\([\d.]+\)' bootloader/src/
bootloader/src/pyi_main.c:                pyi_apple_process_events(0.25);  /* short_timeout (250 ms) */
bootloader/src/pyi_utils.c:        pyi_apple_process_events(0.25);  /* short timeout (250 ms) */
bootloader/src/pyi_utils.c:                if (pyi_apple_send_pending_event(0.5) != 0) {
bootloader/src/pyi_utils.c:            pyi_apple_process_events(1.0);  /* long timeout (1 sec) */

bwoodsend avatar Jan 07 '23 22:01 bwoodsend

On macOS 13.1, running the app (without rebuilding since the OS upgrade) about 20 times gave one dialog which opened but won't respond. Neither the OK, Close or Minimise buttons work.

Could be because the event loop is stuck, although I would expect the OS to report the application as hanged, as in the original report.

This smells like some kind of race condition in our argv emulation code then - where Rosetta is merely slowing something down towards the unhappy path. There are some magic timeouts dotted around in that code.

The relevant call is the one in pyi_main.c; the ones in pyi_utils.c are for onefile codepath, and are not applicable here.

But yes, the non-deterministic nature of the problem does imply that the problem is timing related. I think either:

  • we fail to catch the initial oapp event here and the submission of the "fake" one here results in Qt seeing two oapp events
  • we catch the initial oapp event, but the "fake" one we submit does not make it to the Qt in time, which results in Qt seeing no oapp events

@bwoodsend Can you try modifying the bootloader by either

and see if either case increases the frequency of the problem?

rokm avatar Jan 08 '23 12:01 rokm

FWIW, our argv emulation documentation does warn against using it with onedir bundles that make use of UI toolkits.

Similarly, the py2app documentation warns against using their argv emulation with UI toolkits as well, probably due to the similar issues with initial oapp events...

rokm avatar Jan 08 '23 12:01 rokm

FWIW, our argv emulation documentation does warn against using it with onedir bundles that make use of UI toolkits.

Ah, I'd missed these updated warnings in the docs, sorry. Thanks for pointing them out. I was only using argv-emulation with Qt as an easy way of determining how the application was launched (i.e. with or without an open document request). Hopefully I can do this another way.

Thanks for all the help - you guys are terrific.

jamesbcd avatar Jan 08 '23 14:01 jamesbcd

disabling this part of the event handling

Problem goes away completely. Just removing the pyi_apple_process_events() and leaving the install/uninstall is enough.

disabling the fake oapp submission

I think it's more frequent. Maybe 1 in 5 instead of about 1 in 10. It's hard to get reproducible statistics.

bwoodsend avatar Jan 08 '23 15:01 bwoodsend