WebHost: Update UTC datetime usage (timezone-naive) (#4906)

This commit is contained in:
josephwhite
2026-03-10 13:57:48 -04:00
committed by GitHub
parent c3659fb3ef
commit 4b37283d22
8 changed files with 42 additions and 24 deletions

View File

@@ -4,14 +4,14 @@ import json
import logging
import multiprocessing
import typing
from datetime import timedelta, datetime
from datetime import timedelta
from threading import Event, Thread
from typing import Any
from uuid import UUID
from pony.orm import db_session, select, commit, PrimaryKey
from Utils import restricted_loads
from Utils import restricted_loads, utcnow
from .locker import Locker, AlreadyRunningException
_stop_event = Event()
@@ -129,10 +129,10 @@ def autohost(config: dict):
with db_session:
rooms = select(
room for room in Room if
room.last_activity >= datetime.utcnow() - timedelta(days=3))
room.last_activity >= utcnow() - timedelta(days=3))
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):
if room.last_activity >= utcnow() - timedelta(seconds=room.timeout + 5):
hosters[room.id.int % len(hosters)].start_room(room.id)
except AlreadyRunningException:

View File

@@ -172,7 +172,7 @@ class WebHostContext(Context):
room.multisave = pickle.dumps(self.get_save())
# saving only occurs on activity, so we can "abuse" this information to mark this as last_activity
if not exit_save: # we don't want to count a shutdown as activity, which would restart the server again
room.last_activity = datetime.datetime.utcnow()
room.last_activity = Utils.utcnow()
return True
def get_save(self) -> dict:
@@ -367,8 +367,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
with db_session:
# 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)
room.last_activity = Utils.utcnow() - datetime.timedelta(minutes=1, seconds=room.timeout)
del room
tear_down_logging(room_id)
logging.info(f"Shutting down room {room_id} on {name}.")

View File

@@ -1,8 +1,9 @@
from datetime import timedelta, datetime
from datetime import timedelta
from flask import render_template
from pony.orm import count
from Utils import utcnow
from WebHostLib import app, cache
from .models import Room, Seed
@@ -10,6 +11,6 @@ from .models import Room, Seed
@app.route('/', methods=['GET', 'POST'])
@cache.cached(timeout=300) # cache has to appear under app route for caching to work
def landing():
rooms = count(room for room in Room if room.creation_time >= datetime.utcnow() - timedelta(days=7))
seeds = count(seed for seed in Seed if seed.creation_time >= datetime.utcnow() - timedelta(days=7))
rooms = count(room for room in Room if room.creation_time >= utcnow() - timedelta(days=7))
seeds = count(seed for seed in Seed if seed.creation_time >= utcnow() - timedelta(days=7))
return render_template("landing.html", rooms=rooms, seeds=seeds)

View File

@@ -9,11 +9,12 @@ from flask import request, redirect, url_for, render_template, Response, session
from pony.orm import count, commit, db_session
from werkzeug.utils import secure_filename
from worlds.AutoWorld import AutoWorldRegister, World
from . import app, cache
from .markdown import render_markdown
from .models import Seed, Room, Command, UUID, uuid4
from Utils import title_sorted
from Utils import title_sorted, utcnow
class WebWorldTheme(StrEnum):
DIRT = "dirt"
@@ -233,11 +234,12 @@ def host_room(room: UUID):
if room is None:
return abort(404)
now = datetime.datetime.utcnow()
now = 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))
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)
)
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
# due to "pony.orm.core.OptimisticCheckError: Object Room was updated outside of current transaction"

View File

@@ -2,6 +2,8 @@ from datetime import datetime
from uuid import UUID, uuid4
from pony.orm import Database, PrimaryKey, Required, Set, Optional, buffer, LongStr
from Utils import utcnow
db = Database()
STATE_QUEUED = 0
@@ -20,8 +22,8 @@ class Slot(db.Entity):
class Room(db.Entity):
id = PrimaryKey(UUID, default=uuid4)
last_activity = Required(datetime, default=lambda: datetime.utcnow(), index=True)
creation_time = Required(datetime, default=lambda: datetime.utcnow(), index=True) # index used by landing page
last_activity: datetime = Required(datetime, default=lambda: utcnow(), index=True)
creation_time: datetime = Required(datetime, default=lambda: utcnow(), index=True) # index used by landing page
owner = Required(UUID, index=True)
commands = Set('Command')
seed = Required('Seed', index=True)
@@ -38,7 +40,7 @@ class Seed(db.Entity):
rooms = Set(Room)
multidata = Required(bytes, lazy=True)
owner = Required(UUID, index=True)
creation_time = Required(datetime, default=lambda: datetime.utcnow(), index=True) # index used by landing page
creation_time: datetime = Required(datetime, default=lambda: utcnow(), index=True) # index used by landing page
slots = Set(Slot)
spoiler = Optional(LongStr, lazy=True)
meta = Required(LongStr, default=lambda: "{\"race\": false}") # additional meta information/tags

View File

@@ -10,7 +10,7 @@ from werkzeug.exceptions import abort
from MultiServer import Context, get_saving_second
from NetUtils import ClientStatus, Hint, NetworkItem, NetworkSlot, SlotType
from Utils import restricted_loads, KeyedDefaultDict
from Utils import restricted_loads, KeyedDefaultDict, utcnow
from . import app, cache
from .models import GameDataPackage, Room
@@ -273,9 +273,10 @@ class TrackerData:
Does not include players who have no activity recorded.
"""
last_activity: Dict[TeamPlayer, datetime.timedelta] = {}
now = datetime.datetime.utcnow()
now = utcnow()
for (team, player), timestamp in self._multisave.get("client_activity_timers", []):
last_activity[team, player] = now - datetime.datetime.utcfromtimestamp(timestamp)
from_timestamp = datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc).replace(tzinfo=None)
last_activity[team, player] = now - from_timestamp
return last_activity