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.