# HG changeset patch # User Goffi # Date 1554569480 -7200 # Node ID e9016bfd8cb2767bdc92edabfbac73c72355c3b9 # Parent 368a60f05d0e914703e5a7f1ab3148aa4d49cce2 core (xmpp): advanced handling of connection termination factory's clientConnectionFailed and clientConnectionLost methods are monkey patched to allow client to tune termination: - a warning when connection is lost in an unclean way - connector is saved to allow to disable automatic reconnection and retry manually later - new triggers connection_failed and connection_lost allow plugins to tune connection termination workflow diff -r 368a60f05d0e -r e9016bfd8cb2 sat/core/xmpp.py --- a/sat/core/xmpp.py Fri Apr 05 21:22:05 2019 +0200 +++ b/sat/core/xmpp.py Sat Apr 06 18:51:20 2019 +0200 @@ -17,10 +17,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from functools import partial from sat.core.i18n import _ from sat.core.constants import Const as C from sat.memory import cache -from twisted.internet import defer +from twisted.internet import defer, error as internet_error from twisted.words.protocols.jabber.xmlstream import XMPPHandler from twisted.words.protocols.jabber import xmlstream from twisted.words.protocols.jabber import error @@ -53,6 +54,16 @@ def __init__(self, host_app, profile, max_retries): factory = self.factory + + # we monkey patch clientConnectionLost to handle networkEnabled/networkDisabled + # and to allow plugins to tune reconnection mechanism + clientConnectionFailed_ori = factory.clientConnectionFailed + clientConnectionLost_ori = factory.clientConnectionLost + factory.clientConnectionFailed = partial( + self.connectionTerminated, term_type=u"failed", cb=clientConnectionFailed_ori) + factory.clientConnectionLost = partial( + self.connectionTerminated, term_type=u"lost", cb=clientConnectionLost_ori) + factory.maxRetries = max_retries factory.maxDelay = 30 # when self._connected_d is None, we are not connected @@ -275,6 +286,40 @@ ## connection ## + def connectionTerminated(self, connector, reason, term_type, cb): + """Display disconnection reason, and call factory method + + This method is monkey patched to factory, allowing plugins to handle finely + reconnection with the triggers. + @param connector(twisted.internet.base.BaseConnector): current connector + @param reason(failure.Failure): why connection has been terminated + @param term_type(unicode): on of 'failed' or 'lost' + @param cb(callable): original factory method + + @trigger connection_failed(connector, reason): connection can't be established + @trigger connection_lost(connector, reason): connection was available but it not + anymore + """ + # we save connector because it may be deleted when connection will be dropped + # if reconnection is disabled + self._saved_connector = connector + if reason is not None and not isinstance(reason.value, + internet_error.ConnectionDone): + try: + reason_str = unicode(reason.value) + except Exception: + # FIXME: workaround for Android were p4a strips docstrings + # while Twisted use docstring in __str__ + # TODO: create a ticket upstream, Twisted should work when optimization + # is used + reason_str = unicode(reason.value.__class__) + log.warning(u"Connection {term_type}: {reason}".format( + term_type = term_type, + reason=reason_str)) + if not self.host_app.trigger.point(u"connection_" + term_type, connector, reason): + return + return cb(connector, reason) + def _connected(self, xs): send_hooks = [] receive_hooks = []