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["SELFHOST"] = True # application process is in charge of running the websites
|
||||||
app.config["GENERATORS"] = 8 # maximum concurrent world gens
|
app.config["GENERATORS"] = 8 # maximum concurrent world gens
|
||||||
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
|
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["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["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
|
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),
|
"players": get_players(room.seed),
|
||||||
"last_port": room.last_port,
|
"last_port": room.last_port,
|
||||||
"last_activity": room.last_activity,
|
"last_activity": room.last_activity,
|
||||||
"timeout": room.timeout,
|
|
||||||
"downloads": downloads,
|
"downloads": downloads,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ def get_rooms():
|
|||||||
"creation_time": room.creation_time,
|
"creation_time": room.creation_time,
|
||||||
"last_activity": room.last_activity,
|
"last_activity": room.last_activity,
|
||||||
"last_port": room.last_port,
|
"last_port": room.last_port,
|
||||||
"timeout": room.timeout,
|
|
||||||
"tracker": to_url(room.tracker),
|
"tracker": to_url(room.tracker),
|
||||||
})
|
})
|
||||||
return jsonify(response)
|
return jsonify(response)
|
||||||
|
|||||||
@@ -124,16 +124,14 @@ def autohost(config: dict):
|
|||||||
hoster = MultiworldInstance(config, x)
|
hoster = MultiworldInstance(config, x)
|
||||||
hosters.append(hoster)
|
hosters.append(hoster)
|
||||||
hoster.start()
|
hoster.start()
|
||||||
|
activity_timedelta = timedelta(seconds=config["ROOM_IDLE_TIMEOUT"] + 5)
|
||||||
while not stop_event.wait(0.1):
|
while not stop_event.wait(0.1):
|
||||||
with db_session:
|
with db_session:
|
||||||
rooms = select(
|
rooms = select(
|
||||||
room for room in Room if
|
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:
|
for room in rooms:
|
||||||
# we have to filter twice, as the per-room timeout can't currently be PonyORM transpiled.
|
hosters[room.id.int % len(hosters)].start_room(room.id)
|
||||||
if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout + 5):
|
|
||||||
hosters[room.id.int % len(hosters)].start_room(room.id)
|
|
||||||
|
|
||||||
except AlreadyRunningException:
|
except AlreadyRunningException:
|
||||||
logging.info("Autohost reports as already running, not starting another.")
|
logging.info("Autohost reports as already running, not starting another.")
|
||||||
@@ -187,6 +185,7 @@ class MultiworldInstance():
|
|||||||
self.cert = config["SELFLAUNCHCERT"]
|
self.cert = config["SELFLAUNCHCERT"]
|
||||||
self.key = config["SELFLAUNCHKEY"]
|
self.key = config["SELFLAUNCHKEY"]
|
||||||
self.host = config["HOST_ADDRESS"]
|
self.host = config["HOST_ADDRESS"]
|
||||||
|
self.room_idle_timer = config["ROOM_IDLE_TIMEOUT"]
|
||||||
self.rooms_to_start = multiprocessing.Queue()
|
self.rooms_to_start = multiprocessing.Queue()
|
||||||
self.rooms_shutting_down = multiprocessing.Queue()
|
self.rooms_shutting_down = multiprocessing.Queue()
|
||||||
self.name = f"MultiHoster{id}"
|
self.name = f"MultiHoster{id}"
|
||||||
@@ -198,7 +197,7 @@ class MultiworldInstance():
|
|||||||
process = multiprocessing.Process(group=None, target=run_server_process,
|
process = multiprocessing.Process(group=None, target=run_server_process,
|
||||||
args=(self.name, self.ponyconfig, get_static_server_data(),
|
args=(self.name, self.ponyconfig, get_static_server_data(),
|
||||||
self.cert, self.key, self.host,
|
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)
|
name=self.name)
|
||||||
process.start()
|
process.start()
|
||||||
self.process = process
|
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,
|
def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
|
||||||
cert_file: typing.Optional[str], cert_key_file: typing.Optional[str],
|
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
|
from setproctitle import setproctitle
|
||||||
|
|
||||||
setproctitle(name)
|
setproctitle(name)
|
||||||
@@ -316,7 +317,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
|
|||||||
else:
|
else:
|
||||||
ctx.logger.exception("Could not determine port. Likely hosting failure.")
|
ctx.logger.exception("Could not determine port. Likely hosting failure.")
|
||||||
with db_session:
|
with db_session:
|
||||||
ctx.auto_shutdown = Room.get(id=room_id).timeout
|
ctx.auto_shutdown = room_idle_timeout
|
||||||
if ctx.saving:
|
if ctx.saving:
|
||||||
setattr(asyncio.current_task(), "save", lambda: ctx._save(True))
|
setattr(asyncio.current_task(), "save", lambda: ctx._save(True))
|
||||||
assert ctx.shutdown_task is None
|
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
|
# ensure the Room does not spin up again on its own, minute of safety buffer
|
||||||
room = Room.get(id=room_id)
|
room = Room.get(id=room_id)
|
||||||
room.last_activity = datetime.datetime.utcnow() - \
|
room.last_activity = datetime.datetime.utcnow() - \
|
||||||
datetime.timedelta(minutes=1, seconds=room.timeout)
|
datetime.timedelta(minutes=1, seconds=room_idle_timeout)
|
||||||
del room
|
del room
|
||||||
logging.info(f"Shutting down room {room_id} on {name}.")
|
logging.info(f"Shutting down room {room_id} on {name}.")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ def host_room(room: UUID):
|
|||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
# indicate that the page should reload to get the assigned port
|
# 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))
|
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):
|
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
|
# 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)
|
seed = Required('Seed', index=True)
|
||||||
multisave = Optional(buffer, lazy=True)
|
multisave = Optional(buffer, lazy=True)
|
||||||
show_spoiler = Required(int, default=0) # 0 -> never, 1 -> after completion, -> 2 always
|
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)
|
tracker = Optional(UUID, index=True)
|
||||||
# Port special value -1 means the server errored out. Another attempt can be made with a page refresh
|
# 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)
|
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.
|
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
|
||||||
<br />
|
<br />
|
||||||
{% endif %}
|
{% 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,
|
Should you wish to continue later,
|
||||||
anyone can simply refresh this page and the server will resume.<br>
|
anyone can simply refresh this page and the server will resume.<br>
|
||||||
{% if room.last_port == -1 %}
|
{% if room.last_port == -1 %}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ __all__ = [
|
|||||||
"create_room",
|
"create_room",
|
||||||
"start_room",
|
"start_room",
|
||||||
"stop_room",
|
"stop_room",
|
||||||
"set_room_timeout",
|
|
||||||
"get_multidata_for_room",
|
"get_multidata_for_room",
|
||||||
"set_multidata_for_room",
|
"set_multidata_for_room",
|
||||||
"stop_autogen",
|
"stop_autogen",
|
||||||
@@ -139,7 +138,6 @@ def stop_room(app_client: "FlaskClient",
|
|||||||
from pony.orm import db_session
|
from pony.orm import db_session
|
||||||
|
|
||||||
from WebHostLib.models import Command, Room
|
from WebHostLib.models import Command, Room
|
||||||
from WebHostLib import app
|
|
||||||
|
|
||||||
poll_interval = 2
|
poll_interval = 2
|
||||||
|
|
||||||
@@ -152,14 +150,12 @@ def stop_room(app_client: "FlaskClient",
|
|||||||
with db_session:
|
with db_session:
|
||||||
room: Room = Room.get(id=room_uuid)
|
room: Room = Room.get(id=room_uuid)
|
||||||
if simulate_idle:
|
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:
|
else:
|
||||||
new_last_activity = datetime.utcnow() - timedelta(days=3)
|
new_last_activity = datetime.utcnow() - timedelta(days=3)
|
||||||
room.last_activity = new_last_activity
|
room.last_activity = new_last_activity
|
||||||
address = f"localhost:{room.last_port}" if room.last_port > 0 else None
|
address = f"localhost:{room.last_port}" if room.last_port > 0 else None
|
||||||
if address:
|
if address:
|
||||||
original_timeout = room.timeout
|
|
||||||
room.timeout = 1 # avoid spinning it up again
|
|
||||||
Command(room=room, commandtext="/exit")
|
Command(room=room, commandtext="/exit")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -186,28 +182,13 @@ def stop_room(app_client: "FlaskClient",
|
|||||||
room = Room.get(id=room_uuid)
|
room = Room.get(id=room_uuid)
|
||||||
room.last_port = 0 # easier to detect when the host is up this way
|
room.last_port = 0 # easier to detect when the host is up this way
|
||||||
if address:
|
if address:
|
||||||
room.timeout = original_timeout
|
|
||||||
room.last_activity = new_last_activity
|
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:
|
def get_multidata_for_room(webhost_client: "FlaskClient", room_id: str) -> bytes:
|
||||||
from pony.orm import db_session
|
from pony.orm import db_session
|
||||||
|
|
||||||
from WebHostLib.models import Room
|
from WebHostLib.models import Room
|
||||||
from WebHostLib import app
|
|
||||||
|
|
||||||
room_uuid = to_python(room_id)
|
room_uuid = to_python(room_id)
|
||||||
with db_session:
|
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 pony.orm import db_session
|
||||||
|
|
||||||
from WebHostLib.models import Room
|
from WebHostLib.models import Room
|
||||||
from WebHostLib import app
|
|
||||||
|
|
||||||
room_uuid = to_python(room_id)
|
room_uuid = to_python(room_id)
|
||||||
with db_session:
|
with db_session:
|
||||||
@@ -258,9 +238,11 @@ def _stop_webhost_mp(name_filter: str, graceful: bool = True) -> None:
|
|||||||
proc.kill()
|
proc.kill()
|
||||||
proc.join()
|
proc.join()
|
||||||
|
|
||||||
|
|
||||||
def stop_autogen(graceful: bool = True) -> None:
|
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
|
# 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)
|
_stop_webhost_mp("SpawnPoolWorker-", graceful)
|
||||||
|
|
||||||
|
|
||||||
def stop_autohost(graceful: bool = True) -> None:
|
def stop_autohost(graceful: bool = True) -> None:
|
||||||
_stop_webhost_mp("MultiHoster", graceful)
|
_stop_webhost_mp("MultiHoster", graceful)
|
||||||
|
|||||||
Reference in New Issue
Block a user