From facecdf4875e868bcda574de2b7f66039f439e58 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 16 Jun 2020 11:26:54 +0200 Subject: [PATCH] implement --auto_shutdown , shutting down a multiserver after that many minutes of inactivity and set WebHost to a default of 6 hours --- MultiServer.py | 48 +++++++++++++++++++++++++++++++++++------ WebHost/__init__.py | 44 +++++-------------------------------- WebHost/customserver.py | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 46 deletions(-) create mode 100644 WebHost/customserver.py diff --git a/MultiServer.py b/MultiServer.py index 1724c5ee..3b118da1 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -57,8 +57,10 @@ class Client(Endpoint): class Context(Node): def __init__(self, host: str, port: int, password: str, location_check_points: int, hint_cost: int, - item_cheat: bool, forfeit_mode: str = "disabled", remaining_mode: str = "disabled", auto_shutdown=0): + item_cheat: bool, forfeit_mode: str = "disabled", remaining_mode: str = "disabled", + auto_shutdown: typing.SupportsFloat = 0): super(Context, self).__init__() + self.shutdown_task = None self.data_filename = None self.save_filename = None self.saving = False @@ -88,7 +90,7 @@ class Context(Node): typing.Tuple[int, int], datetime.datetime] = {} # datetime of last connection self.client_game_state: typing.Dict[typing.Tuple[int, int], int] = collections.defaultdict(int) self.er_hint_data: typing.Dict[int, typing.Dict[int, str]] = {} - self.auto_shutdown = 0 + self.auto_shutdown = auto_shutdown self.commandprocessor = ServerCommandProcessor(self) self.embedded_blacklist = {"host", "port"} self.client_ids: typing.Dict[typing.Tuple[int, int], datetime.datetime] = {} @@ -948,6 +950,8 @@ class ServerCommandProcessor(CommandProcessor): def _cmd_exit(self) -> bool: """Shutdown the server""" asyncio.create_task(self.ctx.server.ws_server._close()) + if self.ctx.shutdown_task: + self.ctx.shutdown_task.cancel() self.ctx.running = False return True @@ -1095,9 +1099,30 @@ def parse_args() -> argparse.Namespace: return args -async def auto_shutdown(ctx): - # to be implemented soon - pass +async def auto_shutdown(ctx, to_cancel=None): + await asyncio.sleep(ctx.auto_shutdown * 60) + while ctx.running: + if not ctx.client_activity_timers.values(): + asyncio.create_task(ctx.server.ws_server._close()) + ctx.running = False + if to_cancel: + for task in to_cancel: + task.cancel() + logging.info("Shutting down due to inactivity.") + else: + newest_activity = max(ctx.client_activity_timers.values()) + delta = datetime.datetime.now(datetime.timezone.utc) - newest_activity + seconds = ctx.auto_shutdown * 60 - delta.total_seconds() + if seconds < 0: + asyncio.create_task(ctx.server.ws_server._close()) + ctx.running = False + if to_cancel: + for task in to_cancel: + task.cancel() + logging.info("Shutting down due to inactivity.") + else: + await asyncio.sleep(seconds) + async def main(args: argparse.Namespace): logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO)) @@ -1128,9 +1153,18 @@ async def main(args: argparse.Namespace): ip = args.host if args.host else Utils.get_public_ipv4() logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password)) + await ctx.server - await console(ctx) + console_task = asyncio.create_task(console(ctx)) + if ctx.auto_shutdown: + ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [console_task])) + await console_task + if ctx.shutdown_task: + await ctx.shutdown_task if __name__ == '__main__': loop = asyncio.get_event_loop() - loop.run_until_complete(main(parse_args())) + try: + loop.run_until_complete(main(parse_args())) + except asyncio.exceptions.CancelledError: + pass diff --git a/WebHost/__init__.py b/WebHost/__init__.py index c4b940ac..2ee236ab 100644 --- a/WebHost/__init__.py +++ b/WebHost/__init__.py @@ -2,18 +2,16 @@ import os import logging -import sys import threading import typing import multiprocessing -import functools -from pony.flask import Pony -from pony.orm import Database, Required, Optional, commit, select, db_session +from pony.orm import Database, db_session -import websockets -from flask import Flask, flash, request, redirect, url_for, render_template, Response, g +from flask import Flask, flash, request, redirect, url_for, render_template, Response from werkzeug.utils import secure_filename + + UPLOAD_FOLDER = os.path.relpath('uploads') LOGS_FOLDER = os.path.relpath('logs') multidata_folder = os.path.join(UPLOAD_FOLDER, "multidata") @@ -116,39 +114,7 @@ def host_multidata(filename: str): return render_template("host_multidata.html", filename=filename) -def run_server_process(multidata: str): - async def main(): - logging.basicConfig(format='[%(asctime)s] %(message)s', - level=logging.INFO, - filename=os.path.join(LOGS_FOLDER, multidata + ".txt")) - ctx = Context("", 0, "", 1, 1000, - True, "enabled", "goal") - ctx.load(os.path.join(multidata_folder, multidata), True) - ctx.auto_shutdown = 24 * 60 * 60 # 24 hours - ctx.init_save() - - ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ping_timeout=None, - ping_interval=None) - - await ctx.server - for wssocket in ctx.server.ws_server.sockets: - socketname = wssocket.getsockname() - if wssocket.family == socket.AF_INET6: - logging.info(f'Hosting game at [{get_public_ipv6()}]:{socketname[1]}') - elif wssocket.family == socket.AF_INET: - logging.info(f'Hosting game at {get_public_ipv4()}:{socketname[1]}') - while ctx.running: - await asyncio.sleep(1) - logging.info("Shutting down") - - import asyncio - if ".." not in sys.path: - sys.path.append("..") - from MultiServer import Context, server - from Utils import get_public_ipv4, get_public_ipv6 - import socket - asyncio.run(main()) - +from WebHost.customserver import run_server_process if __name__ == "__main__": multiprocessing.freeze_support() diff --git a/WebHost/customserver.py b/WebHost/customserver.py new file mode 100644 index 00000000..e52bca0e --- /dev/null +++ b/WebHost/customserver.py @@ -0,0 +1,45 @@ +import functools +import logging +import os +import sys + +import websockets + +from WebHost import LOGS_FOLDER, multidata_folder + + +def run_server_process(multidata: str): + async def main(): + logging.basicConfig(format='[%(asctime)s] %(message)s', + level=logging.INFO, + filename=os.path.join(LOGS_FOLDER, multidata + ".txt")) + ctx = Context("", 0, "", 1, 1000, + True, "enabled", "goal", 0) + ctx.load(os.path.join(multidata_folder, multidata), True) + ctx.auto_shutdown = 24 * 60 * 60 # 24 hours + ctx.init_save() + + ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ping_timeout=None, + ping_interval=None) + + await ctx.server + for wssocket in ctx.server.ws_server.sockets: + socketname = wssocket.getsockname() + if wssocket.family == socket.AF_INET6: + logging.info(f'Hosting game at [{get_public_ipv6()}]:{socketname[1]}') + elif wssocket.family == socket.AF_INET: + logging.info(f'Hosting game at {get_public_ipv4()}:{socketname[1]}') + ctx.auto_shutdown = 6 * 60 + ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, [])) + while ctx.running: + await asyncio.sleep(1) + await ctx.shutdown_task + logging.info("Shutting down") + + import asyncio + if ".." not in sys.path: + sys.path.append("..") + from MultiServer import Context, server, auto_shutdown + from Utils import get_public_ipv4, get_public_ipv6 + import socket + asyncio.run(main())