diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index ca568384f6..c470a71db6 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -5,8 +5,8 @@ This module acts as a central place for AMP-servers and -clients to get commands """ -from functools import wraps import time +from functools import wraps from twisted.protocols import amp from collections import defaultdict, namedtuple from io import BytesIO @@ -358,6 +358,48 @@ class AMPMultiConnectionProtocol(amp.AMP): deferred.addCallback(self._safeEmit) deferred.addErrback(self.unhandledError) + def stringReceived(self, string): + """ + Overrides the base stringReceived of twisted in order to handle + the strange error reported in https://github.com/evennia/evennia/issues/2053, + which can lead to the amp connection locking up. + + Args: + string (str): the data coming in. + + Notes: + + To test, add the following code to the beginning of + `evennia.server.amp_client.AMPServerClientProtocol.data_to_portal`, then + run multiple commands until the error trigger: + :: + + import random + from twisted.protocols.amp import AmpBox + always_fail = False + if always_fail or random.random() < 0.05: + breaker = AmpBox() + breaker['_answer'.encode()]='13541'.encode() + self.transport.write(breaker.serialize()) + + """ + try: + pto = "proto_" + self.state + statehandler = getattr(self, pto) + except AttributeError: + log.msg("callback", self.state, "not found") + else: + try: + # make sure to catch a KeyError cleanly here + self.state = statehandler(string) + if self.state == "done": + self.transport.loseConnection() + except KeyError as err: + _get_logger().log_err( + f"AMP error (KeyError: {err}). Discarded data (see " + "https://github.com/evennia/evennia/issues/2053)" + ) + def dataReceived(self, data): """ Handle non-AMP messages, such as HTTP communication.