Fix segfault on coroutine cancelling from QML
Since 87fcb0a773f4855cdae7212fa9448a05de57be56, it was possible to call a Python coroutine, but cancel it (due to parent component destruction) before Python even has time to start it. The registred QML callbacks for this coro would then be called, potentially causing a segfault if that callback tried to access the parent component or its properties.
This commit is contained in:
parent
b94570f853
commit
da75568428
|
@ -21,7 +21,7 @@ import traceback
|
|||
from concurrent.futures import Future
|
||||
from operator import attrgetter
|
||||
from threading import Thread
|
||||
from typing import Coroutine, Dict, Sequence
|
||||
from typing import Coroutine, Dict, Sequence, Set
|
||||
|
||||
import pyotherside
|
||||
|
||||
|
@ -53,6 +53,7 @@ class QMLBridge:
|
|||
self.backend: Backend = Backend()
|
||||
|
||||
self._running_futures: Dict[str, Future] = {}
|
||||
self._cancelled_early: Set[str] = set()
|
||||
|
||||
Thread(target=self._start_asyncio_loop).start()
|
||||
|
||||
|
@ -78,6 +79,10 @@ class QMLBridge:
|
|||
def _call_coro(self, coro: Coroutine, uuid: str) -> None:
|
||||
"""Schedule a coroutine to run in our thread and return a `Future`."""
|
||||
|
||||
if uuid in self._cancelled_early:
|
||||
self._cancelled_early.remove(uuid)
|
||||
return
|
||||
|
||||
def on_done(future: Future) -> None:
|
||||
"""Send a PyOtherSide event with the coro's result/exception."""
|
||||
result = exception = trace = None
|
||||
|
@ -101,7 +106,10 @@ class QMLBridge:
|
|||
) -> None:
|
||||
"""Schedule a coroutine from the `QMLBridge.backend` object."""
|
||||
|
||||
self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
|
||||
if uuid in self._cancelled_early:
|
||||
self._cancelled_early.remove(uuid)
|
||||
else:
|
||||
self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
|
||||
|
||||
|
||||
def call_client_coro(
|
||||
|
@ -109,17 +117,20 @@ class QMLBridge:
|
|||
) -> None:
|
||||
"""Schedule a coroutine from a `QMLBridge.backend.clients` client."""
|
||||
|
||||
client = self.backend.clients[user_id]
|
||||
self._call_coro(attrgetter(name)(client)(*args), uuid)
|
||||
if uuid in self._cancelled_early:
|
||||
self._cancelled_early.remove(uuid)
|
||||
else:
|
||||
client = self.backend.clients[user_id]
|
||||
self._call_coro(attrgetter(name)(client)(*args), uuid)
|
||||
|
||||
|
||||
def cancel_coro(self, uuid: str) -> None:
|
||||
"""Cancel a couroutine scheduled by the `QMLBridge` methods."""
|
||||
|
||||
try:
|
||||
if uuid in self._running_futures:
|
||||
self._running_futures[uuid].cancel()
|
||||
except KeyError:
|
||||
log.warning("Couldn't cancel coroutine %s, future not found", uuid)
|
||||
else:
|
||||
self._cancelled_early.add(uuid)
|
||||
|
||||
|
||||
def pdb(self, additional_data: Sequence = ()) -> None:
|
||||
|
|
|
@ -35,6 +35,8 @@ QtObject {
|
|||
}
|
||||
|
||||
function onCoroutineDone(uuid, result, error, traceback) {
|
||||
if (! Globals.pendingCoroutines[uuid]) return
|
||||
|
||||
const onSuccess = Globals.pendingCoroutines[uuid].onSuccess
|
||||
const onError = Globals.pendingCoroutines[uuid].onError
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ Python {
|
|||
}
|
||||
|
||||
function cancelCoro(uuid) {
|
||||
delete Globals.pendingCoroutines[uuid]
|
||||
call("BRIDGE.cancel_coro", [uuid])
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user