Keep disconnect and quit responsive

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-10-10 20:34:30 +08:00
parent 6c5cd5c22f
commit dfcdfd254f
4 changed files with 558 additions and 11 deletions
+122 -5
View File
@@ -96,6 +96,7 @@ class ConnectionController(QtCore.QObject):
progressFinished = QtCore.Signal(bool)
notificationRequested = QtCore.Signal(str)
errorOccurred = QtCore.Signal(object)
shutdownFinished = QtCore.Signal(bool)
def __init__(self, parent=None, *, coreManager=None, updatesManager=None):
"""Initialize reusable runtime services and a disconnected state."""
@@ -114,6 +115,12 @@ class ConnectionController(QtCore.QObject):
self._startAdmissionPending = False
self._shuttingDown = False
self._runtimeCleanupFailed = False
self._stopOperation = None
self._disconnectionGeneration = 0
self._disconnectionNotification = ''
self._reconnectAfterStop = False
self._shutdownConnectPreference = None
self._shutdownComplete = False
self._actionTimer = QtCore.QTimer(self)
self._actionTimer.timeout.connect(self._callActionFromQueue)
@@ -175,7 +182,11 @@ class ConnectionController(QtCore.QObject):
self.stateChanged.emit(state)
if generation != self._connectionGeneration or self._state is not state:
if (
not isValid(self)
or generation != self._connectionGeneration
or self._state is not state
):
return
if self.interactionEnabled != interactionWasEnabled:
@@ -213,7 +224,11 @@ class ConnectionController(QtCore.QObject):
self._pendingHttpProxy = ''
self.progressFinished.emit(True)
if not isValid(self):
return
self._setActiveProfile(None)
if not isValid(self):
return
AppSettings.turnOFF('Connect')
@@ -571,9 +586,16 @@ class ConnectionController(QtCore.QObject):
def startDisconnection(self, notification: str = '') -> bool:
"""Stop the active runtime and optionally request a notification."""
if self.isDisconnecting() or (
if self.isDisconnecting():
self._reconnectAfterStop = False
return False
if (
self.state is ConnectionState.Disconnected
and not self._startAdmissionPending
and not (
self._shuttingDown
and callable(getattr(self._coreManager, 'stopAsync', None))
)
):
return False
@@ -588,6 +610,8 @@ class ConnectionController(QtCore.QObject):
self._startOperation = None
self._setState(ConnectionState.Disconnecting)
if not isValid(self) or generation != self._connectionGeneration:
return True
self._actionTimer.stop()
if operation is not None and isValid(operation):
@@ -609,6 +633,33 @@ class ConnectionController(QtCore.QObject):
logger.error(f'failed to turn off system proxy: {ex}')
self._runtimeCleanupFailed = False
self._disconnectionGeneration = generation
self._disconnectionNotification = notification
asyncStop = getattr(self._coreManager, 'stopAsync', None)
if callable(asyncStop):
try:
self._stopOperation = stopOperation = asyncStop()
connectWeakly(
stopOperation.finished,
self,
'_connectionStopFinished',
sender=stopOperation,
)
connectWeakly(
stopOperation.destroyed,
self,
'_connectionStopDestroyed',
sender=stopOperation,
)
stopOperation.start()
return True
except Exception as ex:
# Any non-exit exceptions
self._runtimeCleanupFailed = True
logger.error(f'failed to stop connection runtime: {ex}')
self._finishDisconnection(generation, notification)
return True
try:
self._coreManager.stopAll()
@@ -621,10 +672,36 @@ class ConnectionController(QtCore.QObject):
logger.error(f'failed to stop connection runtime: {ex}')
self._finishDisconnection(generation, notification)
return True
def _connectionStopFinished(self, operation, error):
if operation is not self._stopOperation:
return
self._stopOperation = None
if error:
self._runtimeCleanupFailed = True
logger.error(f'failed to stop connection runtime: {error}')
self._finishDisconnection(
self._disconnectionGeneration, self._disconnectionNotification
)
def _connectionStopDestroyed(self, *_args):
if self._stopOperation is not None and not isValid(self._stopOperation):
self._stopOperation = None
self._runtimeCleanupFailed = True
self._finishDisconnection(
self._disconnectionGeneration, self._disconnectionNotification
)
def _finishDisconnection(self, generation, notification):
reconnect, self._reconnectAfterStop = self._reconnectAfterStop, False
self._emitRuntimesChanged()
if not isValid(self):
return
self._reset()
if generation != self._connectionGeneration:
if not isValid(self) or generation != self._connectionGeneration:
return True
while not self._actionQueue.empty():
@@ -636,9 +713,29 @@ class ConnectionController(QtCore.QObject):
pass
Mixins.ConnectionAware.callDisconnectedCallback()
if not isValid(self) or generation != self._connectionGeneration:
return
if notification:
self.notificationRequested.emit(notification)
if not isValid(self):
return
if self._shuttingDown:
if self._shutdownConnectPreference:
AppSettings.turnON_('Connect')
success = not self._runtimeCleanupFailed
self._shutdownComplete = success
if not success:
self._shuttingDown = False
self._shutdownConnectPreference = None
self.shutdownFinished.emit(success)
elif (
reconnect
and not self._runtimeCleanupFailed
and generation == self._connectionGeneration
):
self.startConnection()
return True
@@ -648,7 +745,8 @@ class ConnectionController(QtCore.QObject):
return False
if self.isConnected() or self.isConnecting():
self.startDisconnection(notification)
self._reconnectAfterStop = True
return self.startDisconnection(notification)
return self.startConnection()
@@ -661,7 +759,13 @@ class ConnectionController(QtCore.QObject):
def shutdown(self):
"""Stop runtime resources without changing the next-start preference."""
if self._shutdownComplete:
self.shutdownFinished.emit(True)
return True
reconnectOnStartup = AppSettings.isStateON_('Connect')
if self._shutdownConnectPreference is None:
self._shutdownConnectPreference = reconnectOnStartup
reconnectOnStartup = self._shutdownConnectPreference
self._shuttingDown = True
@@ -669,16 +773,29 @@ class ConnectionController(QtCore.QObject):
if (
self.state is not ConnectionState.Disconnected
or self._startAdmissionPending
or callable(getattr(self._coreManager, 'stopAsync', None))
):
self.startDisconnection()
if self.isDisconnecting() or not self._shuttingDown:
return False
if not self._runtimeCleanupFailed:
return
return True
# Disconnect keeps the UI usable on failure. Final shutdown must
# surface retained resources before its owner is destroyed.
self._coreManager.stopAll()
self._runtimeCleanupFailed = False
self._shutdownComplete = True
self.shutdownFinished.emit(True)
return True
except Exception:
# Any non-exit exceptions
self._shuttingDown = False
self._shutdownConnectPreference = None
self.shutdownFinished.emit(False)
raise
finally:
if reconnectOnStartup:
AppSettings.turnON_('Connect')