About MacOS Crashes and SIGPIPE

This post is just a report, my issue has already been resolved so not a request.

In the new Briefcase version of Anki my prototype add-on causes macOS to crash when performing heavy processing (play webm wallpaper videos, it crashes about 80% of the time). This issue does not occur in Anki for uv Launcher (or on Windows) so it previously worked fine.

This problem was resolved by overriding SIGPIPE, like this:

import signal
if hasattr(signal, "SIGPIPE"):
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)

maybe cause of the crash:

Process 36265 exited with status = 13 (0x0000000d) Terminated due to signal 13

The old uv launcher sets SIGPIPE to SIG_IGN, but the new Briefcase launcher seems to set SIGPIPE to SIG_DFL.

print code

import signal
if hasattr(signal, "SIGPIPE"):
    handler = signal.getsignal(signal.SIGPIPE)

    if handler == signal.SIG_IGN:
        print("SIGPIPE: SIG_IGN")
    elif handler == signal.SIG_DFL:
        print("SIGPIPE: SIG_DFL")

    from anki.utils import pointVersion
    print(f"Anki: {pointVersion()}")

Anki26.08.1

SIGPIPE: SIG_DFL
Anki: 260801

Anki25.09.5

SIGPIPE: SIG_IGN
Anki: 250905
  • I think this may be making crashes more likely but I don’t know which one is more suitable, and I’ve not yet looked into whether setting it to signal.SIG_IGN has any drawbacks.
  • When Anki crashed due to this issue no logs or pop-up messages were displayed. I used lldb to record the crash.
  • This issue with the add-on is rare (it’s possible the code was poorly written to begin with) and has already been resolved with this code so no fix is necessary.

Some questions:

  • Can you reproduce this if you run Anki 26.05+ from PyPI instead of the Briefcase package? Install using something like pip install aqt[qt] and run import aqt; aqt.run() (or with a single command using uv: uvx --from aqt[qt] anki).
  • If you can reproduce it with the PyPI package, try installing again with Qt 6.9 (the version used in the launcher): pip install aqt[qt69] or uvx --from aqt[qt69] anki. Does this make a difference?
  • Is the add-on publicly available or is there a minimal reproducible example I can test with?

I basically don’t use macOS for development so it’s a bit difficult to do additional debugging. This add-on is a prototype so it hasn’t been released yet and there is no minimal reproducible code (the exact cause is unclear), I plan to look into the details later.

This is vague. Let us know if you find something - I suspect it’s a new Qt issue.

I think this problem is probably related to Briefcase or waitress, one similar issue has currently been reported in Briefcase (but I haven’t tested this code myself so I don’t know if it’s actually related to this.):

I wrote code to reproduce the issue:

  • The WebView will auto close while loading a webm video (dummy 10MB). If a crash occurs Anki and the main window will close. If no crash occurs the main window will be displayed. I intentionally added a delay to disconnect the connection.
import ctypes
import flask
import io
import logging
import signal
import sys
import threading
import time
import waitress

from PyQt6.QtCore import QTimer, QUrl
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QApplication, QMainWindow

from anki.utils import pointVersion
print(f"Anki: {pointVersion()}")

logging.basicConfig(level=logging.INFO)

def check_sigpipe():
    if hasattr(signal, "SIGPIPE"):
        handler = signal.getsignal(signal.SIGPIPE)

        if handler == signal.SIG_IGN:
            print("SIGPIPE: SIG_IGN")
        elif handler == signal.SIG_DFL:
            print("SIGPIPE: SIG_DFL")

    # signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    # signal.signal(signal.SIGPIPE, signal.SIG_IGN)

        if sys.platform == "darwin":
            class Sigaction(ctypes.Structure):
                _fields_ = [("sa_handler", ctypes.c_void_p),
                            ("sa_mask", ctypes.c_uint32),
                            ("sa_flags", ctypes.c_int)]
        else:  # glibc x86_64
            class Sigaction(ctypes.Structure):
                _fields_ = [("sa_handler", ctypes.c_void_p),
                            ("sa_mask", ctypes.c_ulong * 16),
                            ("sa_flags", ctypes.c_int),
                            ("sa_restorer", ctypes.c_void_p)]

        libc = ctypes.CDLL(None, use_errno=True)
        act = Sigaction()
        libc.sigaction(signal.SIGPIPE, None, ctypes.byref(act))
        print("Python:", signal.getsignal(signal.SIGPIPE))
        os_handler = {None: "SIG_DFL", 0: "SIG_DFL", 1: "SIG_IGN"}.get(act.sa_handler)
        if os_handler is None and act.sa_handler is not None:
            os_handler = hex(act.sa_handler)
        print("OS:", os_handler)


check_sigpipe()

DUMMY_WEBM = b"x" * (10 * 1024 * 1024)
flask_app = flask.Flask(__name__)


class DelayMiddleware:
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        response = self.app(environ, start_response)

        def delayed_response():
            try:
                for chunk in response:
                    yield chunk
                    time.sleep(0.05)
            finally:
                if hasattr(response, "close"):
                    response.close()

        return delayed_response()


@flask_app.route("/sample.webm")
def sample_video():
    return flask.send_file(
        io.BytesIO(DUMMY_WEBM),
        mimetype="video/webm",
        download_name="sample.webm",
    )


app = DelayMiddleware(flask_app)


class DebugServer(threading.Thread):
    daemon = True

    def __init__(self):
        super().__init__(name="debug-server")
        self.ready = threading.Event()

    def run(self):
        self.server = waitress.create_server(app, host="127.0.0.1", port=0)
        self.ready.set()
        self.server.run()

    def port(self):
        self.ready.wait()
        return self.server.effective_port


def show_wallpaper(port):
    window = QMainWindow()
    view = QWebEngineView(window)
    view.setHtml(
        """
        <!doctype html>
        <html>
        <head><link rel="icon" href="data:,"></head>
        <body style="margin:0; overflow:hidden">
            <video autoplay loop muted playsinline style="width:100vw; height:100vh; object-fit:cover">
                <source src="/sample.webm" type="video/webm">
            </video>
        </body>
        </html>
        """,
        QUrl(f"http://127.0.0.1:{port}/"),
    )
    window.setCentralWidget(view)
    window.setWindowTitle("Live Wallpaper Sample")
    window.resize(426, 240)
    window.show()
    return window, view


def start_debug_sample():
    global debug_window
    server = DebugServer()
    server.start()
    debug_window, debug_view = show_wallpaper(server.port())
    QTimer.singleShot(500, debug_view.close)


if __name__ == "__main__":
    app_instance = QApplication(sys.argv)
    QTimer.singleShot(3000, start_debug_sample)
    app_instance.exec()
else:
    # anki
    QTimer.singleShot(3000, start_debug_sample)

If no crash occurs this error will occur. (waitress)

INFO:waitress: Client disconnected while serving /sample.webm

Here are the test results:

  • Anki 26.08.01 (Briefcase) macOS: Crash
  • Anki 26.08.01 (Python) macOS: fine
  • Anki 26.08.01 qt6.9 (Python) macOS: fine
  • Anki 25.09.05 (uv launcher) macOS: fine
  • Anki 25.02.06 (PyOxidizer) macOS: fine

So far crashes have only occurred in the MacOS version of Briefcase. Only in the Briefcase version is SIGPIPE set to SIG_DFL, in all other versions SIGPIPE is set to SIG_IGN. If SIG_DFL is set in SIGPIPE it seems normal for the program to exit without reporting any errors.

If you manually set it to SIG_IGN the crashes will stop. Or if you manually set it to SIG_DFL crashes will occur in the other versions as well.

This problem does not occur on Windows.

It works fine on Linux Anki 26.08.01 but it’s strange. When printed in Python it is set to SIG_DFL just like on macOS so it should crash but when I test it it doesn’t. Python is set to SIG_DFL but ctypes is set to SIG_IGN, I don’t know why this is happening.

Linux, Ubuntu:

Anki: 260801
SIGPIPE: SIG_DFL
Python: 0
OS: SIG_IGN

The reason it’s set to SIG_DFL might be because Briefcase runs Python in isolated mode. (PyOxidizer is also in isolation mode, but Rust is probably changing it automatically?) But I’m not familiar with the details of how that works so I don’t know the exact reason, these are just my guesses and I might be wrong.

So I think this issue is either something that Briefcase or waitress needs to address or it’s a specification. The native Anki probably wouldn’t send such large files and then disconnect midway through so I think this is an issue specific to the add-on and is of low priority. The add-on that caused the problem displayed multiple wallpapers each several dozen MB in size and frequently resized and refreshed them resulting in disconnections, this rarely happens with standard wallpapers or Anki.

Thank you, I can reproduce it. Logged here: