Allow AMP to handle multiple connections gracefully (more stable).
This commit is contained in:
parent
ee6f3f8065
commit
9813a9d346
3 changed files with 53 additions and 13 deletions
|
|
@ -29,7 +29,7 @@ except ImportError:
|
||||||
import pickle
|
import pickle
|
||||||
from twisted.protocols import amp
|
from twisted.protocols import amp
|
||||||
from twisted.internet import protocol
|
from twisted.internet import protocol
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred, DeferredList
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.utils import to_str, variable_from_module
|
from evennia.utils.utils import to_str, variable_from_module
|
||||||
import zlib # Used in Compressed class
|
import zlib # Used in Compressed class
|
||||||
|
|
@ -45,11 +45,13 @@ PSYNC = chr(3) # portal session sync
|
||||||
SLOGIN = chr(4) # server session login
|
SLOGIN = chr(4) # server session login
|
||||||
SDISCONN = chr(5) # server session disconnect
|
SDISCONN = chr(5) # server session disconnect
|
||||||
SDISCONNALL = chr(6) # server session disconnect all
|
SDISCONNALL = chr(6) # server session disconnect all
|
||||||
SSHUTD = chr(7) # server shutdown
|
SSHUTD = chr(7) # server shutdown (shutdown portal too)
|
||||||
SSYNC = chr(8) # server session sync
|
SSYNC = chr(8) # server session sync
|
||||||
SCONN = chr(11) # server creating new connection (for irc bots and etc)
|
SCONN = chr(11) # server creating new connection (for irc bots and etc)
|
||||||
PCONNSYNC = chr(12) # portal post-syncing a session
|
PCONNSYNC = chr(12) # portal post-syncing a session
|
||||||
PDISCONNALL = chr(13) # portal session disconnect all
|
PDISCONNALL = chr(13) # portal session disconnect all
|
||||||
|
SRELOAD = chr(14) # server reloading (have portal start a new server)
|
||||||
|
|
||||||
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
|
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
|
||||||
|
|
||||||
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
|
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
|
||||||
|
|
@ -97,6 +99,7 @@ class AmpServerFactory(protocol.ServerFactory):
|
||||||
"""
|
"""
|
||||||
self.server = server
|
self.server = server
|
||||||
self.protocol = AMPProtocol
|
self.protocol = AMPProtocol
|
||||||
|
self.connections = []
|
||||||
|
|
||||||
def buildProtocol(self, addr):
|
def buildProtocol(self, addr):
|
||||||
"""
|
"""
|
||||||
|
|
@ -114,6 +117,9 @@ class AmpServerFactory(protocol.ServerFactory):
|
||||||
return self.server.amp_protocol
|
return self.server.amp_protocol
|
||||||
|
|
||||||
|
|
||||||
|
_AMP_TRANSPORTS = []
|
||||||
|
|
||||||
|
|
||||||
class AmpClientFactory(protocol.ReconnectingClientFactory):
|
class AmpClientFactory(protocol.ReconnectingClientFactory):
|
||||||
"""
|
"""
|
||||||
This factory creates an instance of the Portal, an AMPProtocol
|
This factory creates an instance of the Portal, an AMPProtocol
|
||||||
|
|
@ -327,6 +333,9 @@ def loads(data):
|
||||||
return pickle.loads(to_str(data))
|
return pickle.loads(to_str(data))
|
||||||
|
|
||||||
|
|
||||||
|
def cmdline_input(data):
|
||||||
|
print("cmdline_input received:\n %s" % data)
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
# Core AMP protocol for communication Server <-> Portal
|
# Core AMP protocol for communication Server <-> Portal
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
@ -356,6 +365,22 @@ class AMPProtocol(amp.AMP):
|
||||||
self.send_mode = True
|
self.send_mode = True
|
||||||
self.send_task = None
|
self.send_task = None
|
||||||
|
|
||||||
|
def dataReceived(self, data):
|
||||||
|
if data[0] != b'\0':
|
||||||
|
cmdline_input(data)
|
||||||
|
else:
|
||||||
|
super(AMPProtocol, self).dataReceived(data)
|
||||||
|
|
||||||
|
def makeConnection(self, transport):
|
||||||
|
"""
|
||||||
|
Copied from parent AMP protocol
|
||||||
|
"""
|
||||||
|
global _AMP_TRANSPORTS
|
||||||
|
# this makes for a factor x10 faster sends across the wire
|
||||||
|
transport.setTcpNoDelay(True)
|
||||||
|
super(AMPProtocol, self).makeConnection(transport)
|
||||||
|
_AMP_TRANSPORTS.append(transport)
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
This is called when an AMP connection is (re-)established
|
This is called when an AMP connection is (re-)established
|
||||||
|
|
@ -363,9 +388,6 @@ class AMPProtocol(amp.AMP):
|
||||||
need to make sure to only trigger resync from the portal side.
|
need to make sure to only trigger resync from the portal side.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# this makes for a factor x10 faster sends across the wire
|
|
||||||
self.transport.setTcpNoDelay(True)
|
|
||||||
|
|
||||||
if hasattr(self.factory, "portal"):
|
if hasattr(self.factory, "portal"):
|
||||||
# only the portal has the 'portal' property, so we know we are
|
# only the portal has the 'portal' property, so we know we are
|
||||||
# on the portal side and can initialize the connection.
|
# on the portal side and can initialize the connection.
|
||||||
|
|
@ -387,7 +409,8 @@ class AMPProtocol(amp.AMP):
|
||||||
portal will continuously try to reconnect, showing the problem
|
portal will continuously try to reconnect, showing the problem
|
||||||
that way.
|
that way.
|
||||||
"""
|
"""
|
||||||
pass
|
global _AMP_TRANSPORTS
|
||||||
|
_AMP_TRANSPORTS = [transport for transport in _AMP_TRANSPORTS if transport.connected == 1]
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
|
|
||||||
|
|
@ -421,9 +444,18 @@ class AMPProtocol(amp.AMP):
|
||||||
(sessid, kwargs).
|
(sessid, kwargs).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.callRemote(command,
|
if hasattr(self.factory, "portal") or len(_AMP_TRANSPORTS) == 1:
|
||||||
packed_data=dumps((sessid, kwargs))
|
return self.callRemote(command,
|
||||||
).addErrback(self.errback, command.key)
|
packed_data=dumps((sessid, kwargs))
|
||||||
|
).addErrback(self.errback, command.key)
|
||||||
|
|
||||||
|
else:
|
||||||
|
deferreds = []
|
||||||
|
for transport in _AMP_TRANSPORTS:
|
||||||
|
self.transport = transport
|
||||||
|
deferreds.append(self.callRemote(command,
|
||||||
|
packed_data=dumps((sessid, kwargs))))
|
||||||
|
return DeferredList(deferreds, fireOnOneErrback=1).addErrback(self.errback, command.key)
|
||||||
|
|
||||||
# Message definition + helper methods to call/create each message type
|
# Message definition + helper methods to call/create each message type
|
||||||
|
|
||||||
|
|
@ -584,6 +616,9 @@ class AMPProtocol(amp.AMP):
|
||||||
# the server orders the portal to shut down
|
# the server orders the portal to shut down
|
||||||
self.factory.portal.shutdown(restart=False)
|
self.factory.portal.shutdown(restart=False)
|
||||||
|
|
||||||
|
elif operation == SRELOAD: # server reload
|
||||||
|
self.factory.portal.server_reload(**kwargs)
|
||||||
|
|
||||||
elif operation == SSYNC: # server_session_sync
|
elif operation == SSYNC: # server_session_sync
|
||||||
# server wants to save session data to the portal,
|
# server wants to save session data to the portal,
|
||||||
# maybe because it's about to shut down.
|
# maybe because it's about to shut down.
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,11 @@ by game/evennia.py).
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from builtins import object
|
from builtins import object
|
||||||
|
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from twisted.application import internet, service
|
from twisted.application import internet, service
|
||||||
from twisted.internet import protocol, reactor
|
from twisted.internet import protocol, reactor
|
||||||
from twisted.internet.task import LoopingCall
|
|
||||||
from twisted.web import server
|
|
||||||
import django
|
import django
|
||||||
django.setup()
|
django.setup()
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ SSYNC = chr(8) # server session sync
|
||||||
SCONN = chr(11) # server portal connection (for bots)
|
SCONN = chr(11) # server portal connection (for bots)
|
||||||
PCONNSYNC = chr(12) # portal post-syncing session
|
PCONNSYNC = chr(12) # portal post-syncing session
|
||||||
PDISCONNALL = chr(13) # portal session discnnect all
|
PDISCONNALL = chr(13) # portal session discnnect all
|
||||||
|
SRELOAD = chr(14) # server reloading (have portal start a new server)
|
||||||
|
|
||||||
|
|
||||||
# i18n
|
# i18n
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
@ -432,9 +434,15 @@ class ServerSessionHandler(SessionHandler):
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SCONN,
|
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SCONN,
|
||||||
protocol_path=protocol_path, config=configdict)
|
protocol_path=protocol_path, config=configdict)
|
||||||
|
|
||||||
|
def portal_restart_server(self):
|
||||||
|
"""
|
||||||
|
Called by server when reloading. We tell the portal to start a new server instance.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def portal_shutdown(self):
|
def portal_shutdown(self):
|
||||||
"""
|
"""
|
||||||
Called by server when shutting down the portal.
|
Called by server when shutting down the portal (usually because server is going down too).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue