GUI popup for uncaught asyncio loop exceptions
This commit is contained in:
parent
511681ae4d
commit
f977d9acf2
4
TODO.md
4
TODO.md
|
@ -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
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user