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 concurrent.futures import Future
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Coroutine, Dict, Sequence
|
from typing import Coroutine, Dict, Sequence, Set
|
||||||
|
|
||||||
import pyotherside
|
import pyotherside
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ class QMLBridge:
|
||||||
self.backend: Backend = Backend()
|
self.backend: Backend = Backend()
|
||||||
|
|
||||||
self._running_futures: Dict[str, Future] = {}
|
self._running_futures: Dict[str, Future] = {}
|
||||||
|
self._cancelled_early: Set[str] = set()
|
||||||
|
|
||||||
Thread(target=self._start_asyncio_loop).start()
|
Thread(target=self._start_asyncio_loop).start()
|
||||||
|
|
||||||
|
@ -78,6 +79,10 @@ class QMLBridge:
|
||||||
def _call_coro(self, coro: Coroutine, uuid: str) -> None:
|
def _call_coro(self, coro: Coroutine, uuid: str) -> None:
|
||||||
"""Schedule a coroutine to run in our thread and return a `Future`."""
|
"""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:
|
def on_done(future: Future) -> None:
|
||||||
"""Send a PyOtherSide event with the coro's result/exception."""
|
"""Send a PyOtherSide event with the coro's result/exception."""
|
||||||
result = exception = trace = None
|
result = exception = trace = None
|
||||||
|
@ -101,7 +106,10 @@ class QMLBridge:
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Schedule a coroutine from the `QMLBridge.backend` object."""
|
"""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(
|
def call_client_coro(
|
||||||
|
@ -109,17 +117,20 @@ class QMLBridge:
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Schedule a coroutine from a `QMLBridge.backend.clients` client."""
|
"""Schedule a coroutine from a `QMLBridge.backend.clients` client."""
|
||||||
|
|
||||||
client = self.backend.clients[user_id]
|
if uuid in self._cancelled_early:
|
||||||
self._call_coro(attrgetter(name)(client)(*args), uuid)
|
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:
|
def cancel_coro(self, uuid: str) -> None:
|
||||||
"""Cancel a couroutine scheduled by the `QMLBridge` methods."""
|
"""Cancel a couroutine scheduled by the `QMLBridge` methods."""
|
||||||
|
|
||||||
try:
|
if uuid in self._running_futures:
|
||||||
self._running_futures[uuid].cancel()
|
self._running_futures[uuid].cancel()
|
||||||
except KeyError:
|
else:
|
||||||
log.warning("Couldn't cancel coroutine %s, future not found", uuid)
|
self._cancelled_early.add(uuid)
|
||||||
|
|
||||||
|
|
||||||
def pdb(self, additional_data: Sequence = ()) -> None:
|
def pdb(self, additional_data: Sequence = ()) -> None:
|
||||||
|
|
|
@ -35,6 +35,8 @@ QtObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCoroutineDone(uuid, result, error, traceback) {
|
function onCoroutineDone(uuid, result, error, traceback) {
|
||||||
|
if (! Globals.pendingCoroutines[uuid]) return
|
||||||
|
|
||||||
const onSuccess = Globals.pendingCoroutines[uuid].onSuccess
|
const onSuccess = Globals.pendingCoroutines[uuid].onSuccess
|
||||||
const onError = Globals.pendingCoroutines[uuid].onError
|
const onError = Globals.pendingCoroutines[uuid].onError
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ Python {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelCoro(uuid) {
|
function cancelCoro(uuid) {
|
||||||
|
delete Globals.pendingCoroutines[uuid]
|
||||||
call("BRIDGE.cancel_coro", [uuid])
|
call("BRIDGE.cancel_coro", [uuid])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user