diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index e4c2ab83c7..bf08b777ad 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -26,6 +26,7 @@ app.jinja_env.filters['get_file_safe_name'] = get_file_safe_name app.config["SELFHOST"] = True # application process is in charge of running the websites app.config["GENERATORS"] = 8 # maximum concurrent world gens app.config["HOSTERS"] = 8 # maximum concurrent room hosters +app.config["ROOM_IDLE_TIMEOUT"] = 2 * 60 * 60 # seconds of idle before a Room spins down app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms. app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections diff --git a/WebHostLib/api/room.py b/WebHostLib/api/room.py index 78623bbe3e..458bb0f469 100644 --- a/WebHostLib/api/room.py +++ b/WebHostLib/api/room.py @@ -38,6 +38,5 @@ def room_info(room_id: UUID) -> Dict[str, Any]: "players": get_players(room.seed), "last_port": room.last_port, "last_activity": room.last_activity, - "timeout": room.timeout, "downloads": downloads, } diff --git a/WebHostLib/api/user.py b/WebHostLib/api/user.py index 59c8e57283..c6875caf7d 100644 --- a/WebHostLib/api/user.py +++ b/WebHostLib/api/user.py @@ -16,7 +16,6 @@ def get_rooms(): "creation_time": room.creation_time, "last_activity": room.last_activity, "last_port": room.last_port, - "timeout": room.timeout, "tracker": to_url(room.tracker), }) return jsonify(response) diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index 96ffbe9e95..67806d1ff8 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -124,16 +124,14 @@ def autohost(config: dict): hoster = MultiworldInstance(config, x) hosters.append(hoster) hoster.start() - + activity_timedelta = timedelta(seconds=config["ROOM_IDLE_TIMEOUT"] + 5) while not stop_event.wait(0.1): with db_session: rooms = select( room for room in Room if - room.last_activity >= datetime.utcnow() - timedelta(days=3)) + room.last_activity >= datetime.utcnow() - activity_timedelta) for room in rooms: - # we have to filter twice, as the per-room timeout can't currently be PonyORM transpiled. - if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout + 5): - hosters[room.id.int % len(hosters)].start_room(room.id) + hosters[room.id.int % len(hosters)].start_room(room.id) except AlreadyRunningException: logging.info("Autohost reports as already running, not starting another.") @@ -187,6 +185,7 @@ class MultiworldInstance(): self.cert = config["SELFLAUNCHCERT"] self.key = config["SELFLAUNCHKEY"] self.host = config["HOST_ADDRESS"] + self.room_idle_timer = config["ROOM_IDLE_TIMEOUT"] self.rooms_to_start = multiprocessing.Queue() self.rooms_shutting_down = multiprocessing.Queue() self.name = f"MultiHoster{id}" @@ -198,7 +197,7 @@ class MultiworldInstance(): process = multiprocessing.Process(group=None, target=run_server_process, args=(self.name, self.ponyconfig, get_static_server_data(), self.cert, self.key, self.host, - self.rooms_to_start, self.rooms_shutting_down), + self.rooms_to_start, self.rooms_shutting_down, self.room_idle_timer), name=self.name) process.start() self.process = process diff --git a/WebHostLib/customserver.py b/WebHostLib/customserver.py index 14ae291982..fef26a3444 100644 --- a/WebHostLib/customserver.py +++ b/WebHostLib/customserver.py @@ -231,7 +231,8 @@ def set_up_logging(room_id) -> logging.Logger: def run_server_process(name: str, ponyconfig: dict, static_server_data: dict, cert_file: typing.Optional[str], cert_key_file: typing.Optional[str], - host: str, rooms_to_run: multiprocessing.Queue, rooms_shutting_down: multiprocessing.Queue): + host: str, rooms_to_run: multiprocessing.Queue, rooms_shutting_down: multiprocessing.Queue, + room_idle_timeout: int): from setproctitle import setproctitle setproctitle(name) @@ -316,7 +317,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict, else: ctx.logger.exception("Could not determine port. Likely hosting failure.") with db_session: - ctx.auto_shutdown = Room.get(id=room_id).timeout + ctx.auto_shutdown = room_idle_timeout if ctx.saving: setattr(asyncio.current_task(), "save", lambda: ctx._save(True)) assert ctx.shutdown_task is None @@ -347,7 +348,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict, # ensure the Room does not spin up again on its own, minute of safety buffer room = Room.get(id=room_id) room.last_activity = datetime.datetime.utcnow() - \ - datetime.timedelta(minutes=1, seconds=room.timeout) + datetime.timedelta(minutes=1, seconds=room_idle_timeout) del room logging.info(f"Shutting down room {room_id} on {name}.") finally: diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index 82faaf2b16..0eb67f7241 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -231,7 +231,7 @@ def host_room(room: UUID): now = datetime.datetime.utcnow() # indicate that the page should reload to get the assigned port should_refresh = ((not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3)) - or room.last_activity < now - datetime.timedelta(seconds=room.timeout)) + or room.last_activity < now - datetime.timedelta(seconds=app.config["ROOM_IDLE_TIMEOUT"])) if now - room.last_activity > datetime.timedelta(minutes=1): # we only set last_activity if needed, otherwise parallel access on /room will cause an internal server error diff --git a/WebHostLib/models.py b/WebHostLib/models.py index 7fa54f26a0..18320bfd59 100644 --- a/WebHostLib/models.py +++ b/WebHostLib/models.py @@ -27,7 +27,6 @@ class Room(db.Entity): seed = Required('Seed', index=True) multisave = Optional(buffer, lazy=True) show_spoiler = Required(int, default=0) # 0 -> never, 1 -> after completion, -> 2 always - timeout = Required(int, default=lambda: 2 * 60 * 60) # seconds since last activity to shutdown tracker = Optional(UUID, index=True) # Port special value -1 means the server errored out. Another attempt can be made with a page refresh last_port = Optional(int, default=lambda: 0) diff --git a/WebHostLib/templates/hostRoom.html b/WebHostLib/templates/hostRoom.html index 10ff5e8447..11f07bdb21 100644 --- a/WebHostLib/templates/hostRoom.html +++ b/WebHostLib/templates/hostRoom.html @@ -29,7 +29,7 @@ and a Sphere Tracker enabled.
{% endif %} - The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity. + The server for this room will be paused after {{ config["ROOM_IDLE_TIMEOUT"]//60//60 }} hours of inactivity. Should you wish to continue later, anyone can simply refresh this page and the server will resume.
{% if room.last_port == -1 %} diff --git a/test/hosting/webhost.py b/test/hosting/webhost.py index a8e70a50c2..e0c0862aa1 100644 --- a/test/hosting/webhost.py +++ b/test/hosting/webhost.py @@ -19,7 +19,6 @@ __all__ = [ "create_room", "start_room", "stop_room", - "set_room_timeout", "get_multidata_for_room", "set_multidata_for_room", "stop_autogen", @@ -139,7 +138,6 @@ def stop_room(app_client: "FlaskClient", from pony.orm import db_session from WebHostLib.models import Command, Room - from WebHostLib import app poll_interval = 2 @@ -152,14 +150,12 @@ def stop_room(app_client: "FlaskClient", with db_session: room: Room = Room.get(id=room_uuid) if simulate_idle: - new_last_activity = datetime.utcnow() - timedelta(seconds=room.timeout + 5) + new_last_activity = datetime.utcnow() - timedelta(seconds=2 * 60 * 60 + 5) else: new_last_activity = datetime.utcnow() - timedelta(days=3) room.last_activity = new_last_activity address = f"localhost:{room.last_port}" if room.last_port > 0 else None if address: - original_timeout = room.timeout - room.timeout = 1 # avoid spinning it up again Command(room=room, commandtext="/exit") try: @@ -186,28 +182,13 @@ def stop_room(app_client: "FlaskClient", room = Room.get(id=room_uuid) room.last_port = 0 # easier to detect when the host is up this way if address: - room.timeout = original_timeout room.last_activity = new_last_activity - print("timeout restored") - - -def set_room_timeout(room_id: str, timeout: float) -> None: - from pony.orm import db_session - - from WebHostLib.models import Room - from WebHostLib import app - - room_uuid = to_python(room_id) - with db_session: - room: Room = Room.get(id=room_uuid) - room.timeout = timeout def get_multidata_for_room(webhost_client: "FlaskClient", room_id: str) -> bytes: from pony.orm import db_session from WebHostLib.models import Room - from WebHostLib import app room_uuid = to_python(room_id) with db_session: @@ -219,7 +200,6 @@ def set_multidata_for_room(webhost_client: "FlaskClient", room_id: str, data: by from pony.orm import db_session from WebHostLib.models import Room - from WebHostLib import app room_uuid = to_python(room_id) with db_session: @@ -258,9 +238,11 @@ def _stop_webhost_mp(name_filter: str, graceful: bool = True) -> None: proc.kill() proc.join() + def stop_autogen(graceful: bool = True) -> None: # FIXME: this name filter is jank, but there seems to be no way to add a custom prefix for a Pool _stop_webhost_mp("SpawnPoolWorker-", graceful) + def stop_autohost(graceful: bool = True) -> None: _stop_webhost_mp("MultiHoster", graceful)