GUI popup for uncaught asyncio loop exceptions

This commit is contained in:
miruka 2019-12-26 10:05:01 -04:00
parent 511681ae4d
commit f977d9acf2
5 changed files with 47 additions and 8 deletions

View File

@ -3,9 +3,6 @@
- Make dark bar extend down pane - Make dark bar extend down pane
- Verify default size - Verify default size
- catch py unretrieved exception
- call default handler from signin & others
## Media ## Media
- nio ClientTimeout - nio ClientTimeout
@ -55,7 +52,6 @@
- `EventImage`s for `m.image` sometimes appear broken, can be made normal - `EventImage`s for `m.image` sometimes appear broken, can be made normal
by switching to another room and coming back by switching to another room and coming back
- First sent message in E2E room is sometimes undecryptable - First sent message in E2E room is sometimes undecryptable
- Handle matrix errors for accept/decline invite buttons and other
- Pause upload, switch to other room, then come back → wrong state displayed - Pause upload, switch to other room, then come back → wrong state displayed
- Pausing uploads doesn't work well, servers end up dropping the connection - Pausing uploads doesn't work well, servers end up dropping the connection

View File

@ -49,6 +49,15 @@ class CoroutineDone(PyOtherSideEvent):
traceback: Optional[str] = None traceback: Optional[str] = None
@dataclass
class LoopException(PyOtherSideEvent):
"""Indicate that an uncaught exception occured in the asyncio loop."""
message: str = field()
exception: Optional[Exception] = field()
traceback: Optional[str] = None
@dataclass @dataclass
class ModelUpdated(PyOtherSideEvent): class ModelUpdated(PyOtherSideEvent):
"""Indicate that a backend `Model`'s data changed.""" """Indicate that a backend `Model`'s data changed."""

View File

@ -12,7 +12,7 @@ from threading import Thread
from typing import Coroutine, Sequence from typing import Coroutine, Sequence
from .backend import Backend from .backend import Backend
from .pyotherside_events import CoroutineDone from .pyotherside_events import CoroutineDone, LoopException
try: try:
import uvloop import uvloop
@ -39,9 +39,24 @@ class QMLBridge:
self.backend: Backend = Backend() self.backend: Backend = Backend()
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self._loop.set_exception_handler(self._loop_exception_handler)
Thread(target=self._start_asyncio_loop).start() Thread(target=self._start_asyncio_loop).start()
def _loop_exception_handler(
self, loop: asyncio.AbstractEventLoop, context: dict,
) -> None:
if "exception" in context:
err = context["exception"]
trace = "".join(
traceback.format_exception(type(err), err, err.__traceback__),
)
LoopException(context["message"], err, trace)
loop.default_exception_handler(context)
def _start_asyncio_loop(self) -> None: def _start_asyncio_loop(self) -> None:
asyncio.set_event_loop(self._loop) asyncio.set_event_loop(self._loop)
self._loop.run_forever() self._loop.run_forever()

View File

@ -16,12 +16,12 @@ BoxPopup {
property string errorType property string errorType
property var errorArguments: [] property string message: ""
property string traceback: "" property string traceback: ""
HScrollableTextArea { HScrollableTextArea {
text: traceback || qsTr("No traceback available") text: [message, traceback].join("\n\n") || qsTr("No info available")
area.readOnly: true area.readOnly: true
Layout.fillWidth: true Layout.fillWidth: true

View File

@ -47,7 +47,7 @@ QtObject {
utils.makePopup( utils.makePopup(
"Popups/UnexpectedErrorPopup.qml", "Popups/UnexpectedErrorPopup.qml",
window, window,
{ errorType: type, errorArguments: args, traceback }, { errorType: type, traceback },
) )
} }
@ -55,6 +55,25 @@ QtObject {
} }
function onLoopException(message, error, traceback) {
// No need to log these here, the asyncio exception handler does it
const type = py.getattr(py.getattr(error, "__class__"), "__name__")
if (window.hideErrorTypes.has(type)) {
console.warn(
"Not showing error popup for this type due to user choice"
)
return
}
utils.makePopup(
"Popups/UnexpectedErrorPopup.qml",
window,
{ errorType: type, message, traceback },
)
}
function onModelUpdated(syncId, data, serializedSyncId) { function onModelUpdated(syncId, data, serializedSyncId) {
if (serializedSyncId === "Account" || serializedSyncId[0] === "Room") { if (serializedSyncId === "Account" || serializedSyncId[0] === "Room") {
py.callCoro("get_flat_mainpane_data", [], data => { py.callCoro("get_flat_mainpane_data", [], data => {