Audit exception handling behavior (esp RuntimeError)
- Dominant language
- Python
- Stars
- 5.4k
- Forks
- 827
- Avg merge
- 9h 45m
- Merged PRs (30d)
- 58
Description
There are potentially some inconsistencies in exception handling behavior, especially as a result of WebView handling, and the handling of the Winforms asyncio event loop integration. We need to audit these interactions to ensure consistency across platforms.
_Originally posted by @rmartin16 in https://github.com/beeware/toga/issues/2781#issuecomment-2323553447_
> > > My original thought was to go non-async, with `app.loop.call_soon_threadsafe()`... not sure if that will change anything by ensuring that you're getting a method on the app's specific loop, rather than potentially spawning a thread-specific loop in the .NET context.
> >
> >
> > This did print an exception to the console; however, the app continued running.
>
> That's not _entirely_ unexpected. The Winforms asyncio integration is a bit weird, because of the winforms STA apartment state. The app itself runs in a thread; exceptions are caught and the stack trace is manually unwound for printing purposes, but won't necessarily terminate the app itself.
>
> I'm not 100% convinced this implementation is correct - the intention was that an exception in an event handler would be reported, but wouldn't kill the app; but it's possible this is being a little _too_ enthusiastic with surpassing exit conditions, and actual errors in the app mainline aren't being surfaced.
So, my interest remain piqued since you said this.
To recap, I had put a task on the event loop that would raise a `RuntimeError`....and all that happened was the stacktrace was printed to the console.
I tracked down this behavior to `BaseEventLoop.default_exception_handler()`. If I add a `default_exception_handler()` to `WinformsProactorEventLoop`, I see it run for the `RuntimeError`.
```python
def default_exception_handler(self, context):
print("this is your friendly exception handler")
super().default_exception_handler(context)
```
```
[webview] Starting in dev mode...
===========================================================================
this is your friendly exception handler
Exception in callback WebView.winforms_initialization_completed..raise_exception() at C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\widgets\webview.py:72
handle: .raise_exception() at C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\widgets\webview.py:72 created at C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\widgets\webview.py:75>
source_traceback: Object created at (most recent call last):
File "C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\app.py", line 167, in _run_app
self.loop.run_forever(self)
File "C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\libs\proactor.py", line 76, in run_forever
WinForms.Application.Run(self.app.app_context)
File "C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\libs\wrapper.py", line 22, in __call__
return function(*args, **kwargs)
File "C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\widgets\webview.py", line 75, in winforms_initialization_completed
self.interface.app.loop.call_soon_threadsafe(raise_exception)
Traceback (most recent call last):
File "C:\Users\user\.pyenv\pyenv-win\versions\3.12.1\Lib\asyncio\events.py", line 84, in _run
self._context.run(self._callback, *self._args)
File "C:\Users\user\github\beeware\toga\winforms\src\toga_winforms\widgets\webview.py", line 73, in raise_exception
raise RuntimeError("failed to load WebView")
RuntimeError: failed to load WebView
```
**So, given this, what is the desired behavior when Toga itself raises an exception?**
If I simulate raising the `RuntimeError` for WebView on Gtk, a console error is logged but the app window never renders.
```
[webview] Starting in dev mode...
===========================================================================
Traceback (most recent call last):
File "/home/russell/github/beeware/toga/gtk/src/toga_gtk/app.py", line 45, in gtk_startup
self.interface._startup()
File "/home/russell/.pyenv/versions/briefcase-3.10/lib/python3.10/site-packages/toga/app.py", line 619, in _startup
self.startup()
File "/home/russell/github/beeware/toga/examples/webview/webview/app.py", line 86, in startup
self.webview = toga.WebView(
File "/home/russell/.pyenv/versions/briefcase-3.10/lib/python3.10/site-packages/toga/widgets/webview.py", line 47, in __init__
self._impl = self.factory.WebView(interface=self)
File "/home/russell/github/beeware/toga/gtk/src/toga_gtk/widgets/base.py", line 16, in __init__
self.create()
File "/home/russell/github/beeware/toga/gtk/src/toga_gtk/widgets/webview.py", line 15, in create
raise RuntimeError(
RuntimeError: Unable to import WebKit2. Ensure that the system package providing WebKit2 and its GTK bindings have been installed. See https://toga.readthedocs.io/en/stable/reference/api/widgets/webview.html#system-requirements for details.
```
Contributor guide
Research direction
Start with the WinForms paths in winforms/src/toga_winforms/widgets/webview.py, winforms/src/toga_winforms/libs/proactor.py, and winforms/src/toga_winforms/app.py, then compare them with gtk/src/toga_gtk/app.py and the asyncio BaseEventLoop.default_exception_handler() behavior. Reproduce the WebView RuntimeError on WinForms and GTK, determine the intended handling for event-handler and app-mainline exceptions, and verify that behavior is consistent across platforms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100