diff options
author | Calum Lind <calumlind+deluge@gmail.com> | 2023-12-03 20:47:11 +0100 |
---|---|---|
committer | Calum Lind <calumlind+deluge@gmail.com> | 2024-01-21 15:16:22 +0100 |
commit | 70468241151ff3559a3468db685e9b04d7c6d291 (patch) | |
tree | fb9687e06c5f306bca009f229483fe43b31bad1b | |
parent | [GTK3] Fix changing torrent ownership (diff) | |
download | deluge-70468241151ff3559a3468db685e9b04d7c6d291.tar.xz deluge-70468241151ff3559a3468db685e9b04d7c6d291.zip |
[Alerts] Fix alert handler segfault on lt.pop_alerts
We cannot handle an alert after calling lt.pop_alerts for a subsequent
time since the alert objects are invalidated and with cause a segfault.
To resolve this issue add a timeout to the handler calls and wait in the
alert thread for either the handlers to be called or eventually be
cancelled before getting more alerts.
This is still not an ideal solution and might leave to backlog of alerts
but this is better than crashing the application. Perhaps the timeout
could be tweaked to be shorter for certain alert types such as stats.
Related: https://github.com/arvidn/libtorrent/issues/6437
-rw-r--r-- | deluge/core/alertmanager.py | 59 |
1 files changed, 29 insertions, 30 deletions
diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index 71045b0a3..cf541f015 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -17,10 +17,12 @@ This should typically only be used by the Core. Plugins should utilize the import contextlib import logging import threading +import time from collections import defaultdict +from functools import partial from typing import Any, Callable -from twisted.internet import reactor, threads +from twisted.internet import reactor, task, threads import deluge.component as component from deluge._libtorrent import lt @@ -56,8 +58,7 @@ class AlertManager(component.Component): # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} self.handlers = defaultdict(list) - self.handlers_retry_timeout = 0.3 - self.handlers_retry_count = 6 + self.handlers_timeout_secs = 2 self.delayed_calls = [] self._event = threading.Event() @@ -82,45 +83,33 @@ class AlertManager(component.Component): def wait_for_alert_in_thread(self): while self._component_state not in ('Stopping', 'Stopped'): + if self.check_delayed_calls(): + time.sleep(0.05) + continue + if self.session.wait_for_alert(1000) is None: continue if self._event.wait(): threads.blockingCallFromThread(reactor, self.maybe_handle_alerts) + def on_delayed_call_timeout(self, result, timeout, **kwargs): + log.warning('Alert handler was timed-out before being called %s', kwargs) + def cancel_delayed_calls(self): """Cancel all delayed handlers.""" for delayed_call in self.delayed_calls: - if delayed_call.active(): - delayed_call.cancel() + delayed_call.cancel() self.delayed_calls = [] - def check_delayed_calls(self, retries: int = 0) -> bool: - """Returns True if any handler calls are delayed (upto retry limit).""" - self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()] - if not self.delayed_calls: - return False - - if retries > self.handlers_retry_count: - log.warning( - 'Alert handlers timeout reached, cancelling: %s', self.delayed_calls - ) - self.cancel_delayed_calls() - return False + def check_delayed_calls(self) -> bool: + """Returns True if any handler calls are delayed.""" + self.delayed_calls = [dc for dc in self.delayed_calls if not dc.called] + return len(self.delayed_calls) > 0 - return True - - def maybe_handle_alerts(self, retries: int = 0) -> None: + def maybe_handle_alerts(self) -> None: if self._component_state != 'Started': return - if self.check_delayed_calls(retries): - log.debug('Waiting for delayed alerts: %s', self.delayed_calls) - retries += 1 - reactor.callLater( - self.handlers_retry_timeout, self.maybe_handle_alerts, retries - ) - return - self.handle_alerts() def register_handler(self, alert_type: str, handler: Callable[[Any], None]) -> None: @@ -182,8 +171,18 @@ class AlertManager(component.Component): for handler in self.handlers[alert_type]: if log.isEnabledFor(logging.DEBUG): log.debug('Handling alert: %s', alert_type) - - self.delayed_calls.append(reactor.callLater(0, handler, alert)) + d = task.deferLater(reactor, 0, handler, alert) + on_handler_timeout = partial( + self.on_delayed_call_timeout, + handler=handler.__qualname__, + alert_type=alert_type, + ) + d.addTimeout( + self.handlers_timeout_secs, + reactor, + onTimeoutCancel=on_handler_timeout, + ) + self.delayed_calls.append(d) def set_alert_queue_size(self, queue_size): """Sets the maximum size of the libtorrent alert queue""" |