mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 15:13:52 -08:00
WebHost: turn Room.timeout from a database column into a flask app config field
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
|
||||
<br />
|
||||
{% 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.<br>
|
||||
{% if room.last_port == -1 %}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user