diff --git a/evennia/scripts/scripts.py b/evennia/scripts/scripts.py index 50633fca05..ca83c63275 100644 --- a/evennia/scripts/scripts.py +++ b/evennia/scripts/scripts.py @@ -540,71 +540,3 @@ class Store(DefaultScript): "Setup the script" self.key = "sys_storage" self.desc = _("This is a generic storage container.") - - -class CheckSessions(DefaultScript): - "Check sessions regularly." - def at_script_creation(self): - "Setup the script" - self.key = "sys_session_check" - self.desc = _("Checks sessions so they are live.") - self.interval = 60 # repeat every 60 seconds - self.persistent = True - - def at_repeat(self): - "called every 60 seconds" - global _SESSIONS - if not _SESSIONS: - from evennia.server.sessionhandler import SESSIONS as _SESSIONS - #print "session check!" - #print "ValidateSessions run" - _SESSIONS.validate_sessions() - -_FLUSH_CACHE = None -_IDMAPPER_CACHE_MAX_MEMORY = settings.IDMAPPER_CACHE_MAXSIZE -class ValidateIdmapperCache(DefaultScript): - """ - Check memory use of idmapper cache - """ - def at_script_creation(self): - self.key = "sys_cache_validate" - self.desc = _("Restrains size of idmapper cache.") - self.interval = 61 * 5 # staggered compared to session check - self.persistent = True - - def at_repeat(self): - "Called every ~5 mins" - global _FLUSH_CACHE - if not _FLUSH_CACHE: - from evennia.utils.idmapper.models import conditional_flush as _FLUSH_CACHE - _FLUSH_CACHE(_IDMAPPER_CACHE_MAX_MEMORY) - -class ValidateScripts(DefaultScript): - "Check script validation regularly" - def at_script_creation(self): - "Setup the script" - self.key = "sys_scripts_validate" - self.desc = _("Validates all scripts regularly.") - self.interval = 3600 # validate every hour. - self.persistent = True - - def at_repeat(self): - "called every hour" - #print "ValidateScripts run." - ScriptDB.objects.validate() - - -class ValidateChannelHandler(DefaultScript): - "Update the channelhandler to make sure it's in sync." - def at_script_creation(self): - "Setup the script" - self.key = "sys_channels_validate" - self.desc = _("Updates the channel handler") - self.interval = 3700 # validate a little later than ValidateScripts - self.persistent = True - - def at_repeat(self): - "called every hour+" - #print "ValidateChannelHandler run." - channelhandler.CHANNELHANDLER.update() - diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index fd5480db74..42b975a3c6 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -130,39 +130,6 @@ def create_channels(): channel = create.create_channel(**channeldict) channel.connect(goduser) -def create_system_scripts(): - """ - Setup the system repeat scripts. They are automatically started - by the create_script function. - """ - from evennia.scripts import scripts - - print " Creating and starting global scripts ..." - - # check so that all sessions are alive. - script1 = create.create_script(scripts.CheckSessions) - # validate all scripts in script table. - script2 = create.create_script(scripts.ValidateScripts) - # update the channel handler to make sure it's in sync - script3 = create.create_script(scripts.ValidateChannelHandler) - # flush the idmapper cache - script4 = create.create_script(scripts.ValidateIdmapperCache) - - if not script1 or not script2 or not script3 or not script4: - print " Error creating system scripts." - - -def start_game_time(): - """ - This starts a persistent script that keeps track of the - in-game time (in whatever accelerated reference frame), but also - the total run time of the server as well as its current uptime - (the uptime can also be found directly from the server though). - """ - print " Starting in-game time ..." - from evennia.utils import gametime - gametime.init_gametime() - def at_initial_setup(): """ @@ -206,20 +173,15 @@ def handle_setup(last_step): # this means we don't need to handle setup since # it already ran sucessfully once. return - elif last_step is None: - # config doesn't exist yet. First start of server - last_step = 0 + # if None, set it to 0 + last_step = last_step or 0 # setting up the list of functions to run - setup_queue = [ - create_config_values, - create_objects, - create_channels, - create_system_scripts, - start_game_time, - at_initial_setup, - reset_server - ] + setup_queue = [create_config_values, + create_objects, + create_channels, + at_initial_setup, + reset_server] #print " Initial setup: %s steps." % (len(setup_queue)) @@ -241,6 +203,7 @@ def handle_setup(last_step): from evennia.comms.models import ChannelDB ChannelDB.objects.all().delete() raise + # save this step ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1) # We got through the entire list. Set last_step to -1 so we don't # have to run this again. diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 1c1c522de2..c9b9b088ac 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -8,11 +8,13 @@ by game/evennia.py). """ +import time import sys import os from twisted.application import internet, service from twisted.internet import protocol, reactor +from twisted.internet.task import LoopingCall from twisted.web import server import django django.setup() @@ -67,6 +69,28 @@ AMP_INTERFACE = settings.AMP_INTERFACE AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE +# Maintenance function - this is called repeatedly by the portal. + +_IDLE_TIMEOUT = settings.IDLE_TIMEOUT +def _portal_maintenance(): + """ + The maintenance function handles repeated checks and updates + that the server needs to do. It is called every minute. + """ + # check for idle sessions + now = time.time() + + reason = "Idle timeout exceeded, disconnecting." + for session in [sess for sess in PORTAL_SESSIONS.sessions.values() + if (now - sess.cmd_last) > _IDLE_TIMEOUT]: + session.data_out(reason) + PORTAL_SESSIONS.disconnect(session) +if _IDLE_TIMEOUT > 0: + # only start the maintenance task if we care about idling. + _maintenance_task = LoopingCall(_portal_maintenance) + _maintenance_task.start(60) # called every minute + + #------------------------------------------------------------ # Portal Service object #------------------------------------------------------------ diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 28bcc7b46f..66bbbddb4f 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -295,6 +295,7 @@ class PortalSessionHandler(SessionHandler): serialized before passed on. """ + self.cmd_last = time() self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid, msg=text, data=kwargs) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 2ad8acfb6c..5701be9290 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -8,6 +8,7 @@ sessions etc. """ import re +from twisted.internet.task import LoopingCall from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, GA, WILL, WONT, ECHO from evennia.server.session import Session from evennia.server.portal import ttype, mssp, telnet_oob, naws @@ -15,6 +16,8 @@ from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP from evennia.server.portal.mxp import Mxp, mxp_parse from evennia.utils import utils, ansi, logger +NOP = chr(241) + _RE_N = re.compile(r"\{n$") _RE_LEND = re.compile(r"\n$|\r$", re.MULTILINE) @@ -60,6 +63,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): from evennia.utils.utils import delay delay(2, callback=self.handshake_done, retval=True) + # set up a keep-alive + self.keep_alive = LoopingCall(self._write, NOP) + self.keep_alive.start(30) + + def handshake_done(self, force=False): """ This is called by all telnet extensions once they are finished. diff --git a/evennia/server/server.py b/evennia/server/server.py index 1a0ec9c658..d10aa84bcb 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -14,6 +14,8 @@ import os from twisted.web import server, static from twisted.application import internet, service from twisted.internet import reactor, defer +from twisted.internet.task import LoopingCall + import django django.setup() @@ -72,6 +74,42 @@ RSS_ENABLED = settings.RSS_ENABLED WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED +# Maintenance function - this is called repeatedly by the server + +_MAINTENANCE_COUNT = 0 +_FLUSH_CACHE = None +_IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE +def _server_maintenance(): + """ + This maintenance function handles repeated checks and updates that + the server needs to do. It is called every 5 minutes. + """ + global EVENNIA, _MAINTENANCE_COUNT + global _FLUSH_CACHE + if not _FLUSH_CACHE: + from evennia.utils.idmapper.models import conditional_flush as _FLUSH_CACHE + + _MAINTENANCE_COUNT += 1 + + # update game time + EVENNIA.runtime += 60.0 + ServerConfig.objects.conf("runtime", EVENNIA.runtime) + EVENNIA.runtime_last_saved = time.time() + + if _MAINTENANCE_COUNT % 300 == 0: + # check cache size every 5 minutes + print "maintenance: check flush cache..." + _FLUSH_CACHE(_IDMAPPER_CACHE_MAXSIZE) + if _MAINTENANCE_COUNT % 3600 == 0: + # validate scripts every hour + print "maintenance: validate scripts..." + evennia.ScriptDB.objects.validate() + if _MAINTENANCE_COUNT % 3700 == 0: + # validate channels off-sync with scripts + print "maintenance: validate channels..." + evennia.CHANNEL_HANDLER.update() + + #------------------------------------------------------------ # Evennia Main Server object #------------------------------------------------------------ @@ -104,8 +142,6 @@ class Evennia(object): # Run the initial setup if needed self.run_initial_setup() - self.start_time = time.time() - # initialize channelhandler channelhandler.CHANNELHANDLER.update() @@ -113,9 +149,13 @@ class Evennia(object): # by Ctrl-C, reboot etc. reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _reactor_stopping=True) - self.game_running = True + # track the server time + self.start_time = time.time() + self.runtime = ServerConfig.objects.conf("runtime", default=0.0) + self.runtime_last_saved = self.start_time + self.run_init_hooks() # Server startup methods @@ -323,10 +363,6 @@ class Evennia(object): self.at_server_cold_stop() - # stopping time - from evennia.utils import gametime - gametime.save() - self.at_server_stop() # if _reactor_stopping is true, reactor does not need to # be stopped again. @@ -486,6 +522,9 @@ ServerConfig.objects.conf("server_starting_mode", delete=True) if os.name == 'nt': # Windows only: Set PID file manually - f = open(os.path.join(settings.GAME_DIR, 'server.pid'), 'w') - f.write(str(os.getpid())) - f.close() + with open(os.path.join(settings.GAME_DIR, 'server.pid'), 'w') as f: + f.write(str(os.getpid())) + +# start the maintenance task +maintenance_task = LoopingCall(_server_maintenance) +maintenance_task.start(60) # call every minute diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index 0c96a31ce6..b4d51c2210 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -8,10 +8,6 @@ total runtime of the server and the current uptime. from time import time from django.conf import settings -from evennia.scripts.scripts import DefaultScript -from evennia.utils.create import create_script - -GAMETIME_SCRIPT_NAME = "sys_game_time" # Speed-up factor of the in-game time compared # to real time. @@ -35,67 +31,25 @@ WEEK = DAY * settings.TIME_DAY_PER_WEEK MONTH = WEEK * settings.TIME_WEEK_PER_MONTH YEAR = MONTH * settings.TIME_MONTH_PER_YEAR -# Cached time stamps -SERVER_STARTTIME = time() -SERVER_RUNTIME = 0.0 +# link to the main Server +_EVENNIA = None -class GameTime(DefaultScript): - """ - This script repeatedly saves server times so - it can be retrieved after server downtime. - """ - def at_script_creation(self): - """ - Setup the script - """ - self.key = GAMETIME_SCRIPT_NAME - self.desc = "Saves uptime/runtime" - self.interval = 60 - self.persistent = True - self.start_delay = True - self.attributes.add("run_time", 0.0) # OOC time - self.attributes.add("up_time", 0.0) # OOC time - - def at_repeat(self): - """ - Called every minute to update the timers. - """ - self.attributes.add("run_time", runtime()) - self.attributes.add("up_time", uptime()) - - def at_start(self): - """ - This is called once every server restart. - We reset the up time and load the relevant - times. - """ - global SERVER_RUNTIME - SERVER_RUNTIME = self.attributes.get("run_time") - -def save(): - "Force save of time. This is called by server when shutting down/reloading." - from evennia.scripts.models import ScriptDB - try: - script = ScriptDB.objects.get(db_key=GAMETIME_SCRIPT_NAME) - script.at_repeat() - except Exception: - from evennia.utils import logger - logger.log_trace() - def _format(seconds, *divisors) : """ - Helper function. Creates a tuple of even dividends given - a range of divisors. + Helper function. Creates a tuple of even dividends given a range + of divisors. - Inputs - seconds - number of seconds to format - *divisors - a number of integer dividends. The number of seconds will be - integer-divided by the first number in this sequence, the remainder - will be divided with the second and so on. - Output: - A tuple of length len(*args)+1, with the last element being the last remaining - seconds not evenly divided by the supplied dividends. + Args: + seconds (int): Number of seconds to format + *divisors (int): a sequence of numbers of integer dividends. The + number of seconds will be integer-divided by the first number in + this sequence, the remainder will be divided with the second and + so on. + Returns: + time (tuple): This tuple has length len(*args)+1, with the + last element being the last remaining seconds not evenly + divided by the supplied dividends. """ results = [] @@ -111,14 +65,20 @@ def _format(seconds, *divisors) : def runtime(format=False): "Get the total runtime of the server since first start (minus downtimes)" - runtime = SERVER_RUNTIME + (time() - SERVER_STARTTIME) + global _EVENNIA + if not _EVENNIA: + from evennia.server.server import EVENNIA as _EVENNIA + runtime = _EVENNIA.runtime + (time() - _EVENNIA.runtime_last_saved) if format: return _format(runtime, 31536000, 2628000, 604800, 86400, 3600, 60) return runtime def uptime(format=False): "Get the current uptime of the server since last reload" - uptime = time() - SERVER_STARTTIME + global _EVENNIA + if not _EVENNIA: + from evennia.server.server import EVENNIA as _EVENNIA + uptime = time() - _EVENNIA.start_time if format: return _format(uptime, 31536000, 2628000, 604800, 86400, 3600, 60) return uptime @@ -167,13 +127,3 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, return _format(gametime, YEAR, MONTH, WEEK, DAY, HOUR, MIN) return gametime - -# Time administration routines - -def init_gametime(): - """ - This is called once, when the server starts for the very first time. - """ - # create the GameTime script and start it - game_time = create_script(GameTime) - game_time.start()