fix: RDP slot occupancy and cleanup_loop always running

- admin_page: slot shown as occupied based on ACTIVE status only (no time cutoff)
- go_service: busy slots checked by ACTIVE status (no cutoff) — cleanup_loop handles expiry
- startup_event: cleanup_loop starts regardless of ENABLE_STARTUP_MAINTENANCE flag;
  pool/container init guarded by the flag separately
- cleanup_loop: RDPSLOT sessions expire correctly and trigger container restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 06:48:00 +00:00
parent 552898e3e9
commit a4a96c45b0
+31 -31
View File
@@ -1742,37 +1742,41 @@ def startup_event():
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
ensure_icons_dir() ensure_icons_dir()
bootstrap_admin() bootstrap_admin()
if not ENABLE_STARTUP_MAINTENANCE:
logger.info("startup_maintenance_disabled")
return
if not try_acquire_maintenance_leader(): if not try_acquire_maintenance_leader():
logger.info("maintenance_leader_skipped") logger.info("maintenance_leader_skipped")
return return
db = SessionLocal() if ENABLE_STARTUP_MAINTENANCE:
try: db = SessionLocal()
ensure_universal_pool() try:
ensure_web_pool() ensure_universal_pool()
for svc in db.scalars( ensure_web_pool()
select(Service).where( for svc in db.scalars(
Service.active == True, select(Service).where(
Service.type.in_([ServiceType.WEB, ServiceType.RDP]), Service.active == True,
) Service.type.in_([ServiceType.WEB, ServiceType.RDP]),
).all(): )
if svc.type == ServiceType.WEB and WEB_POOL_SIZE <= 0: ).all():
ensure_warm_pool(svc) if svc.type == ServiceType.WEB and WEB_POOL_SIZE <= 0:
elif svc.type == ServiceType.RDP: ensure_warm_pool(svc)
slots = db.scalars(select(RdpSlot).where(RdpSlot.service_id == svc.id)).all() elif svc.type == ServiceType.RDP:
for slot in slots: slots = db.scalars(select(RdpSlot).where(RdpSlot.service_id == svc.id)).all()
try: for slot in slots:
start_rdp_slot_container(slot, svc) try:
slot.container_name = _rdp_slot_container_name(svc.slug, slot.id) cname = _rdp_slot_container_name(svc.slug, slot.id)
except Exception: try:
logger.exception("startup_rdp_slot_start_failed slot_id=%s", slot.id) c = docker_client().containers.get(cname)
if slots: if c.status != "running":
db.commit() c.start()
finally: except docker.errors.NotFound:
db.close() start_rdp_slot_container(slot, svc)
slot.container_name = cname
except Exception:
logger.exception("startup_rdp_slot_start_failed slot_id=%s", slot.id)
if slots:
db.commit()
finally:
db.close()
thread = threading.Thread(target=cleanup_loop, daemon=True) thread = threading.Thread(target=cleanup_loop, daemon=True)
thread.start() thread.start()
@@ -1953,7 +1957,6 @@ def admin_page(request: Request, admin: User = Depends(require_admin), db: Sessi
{"cutoff": cutoff}, {"cutoff": cutoff},
).mappings().all() ).mappings().all()
rdp_slots: dict[int, list] = {} rdp_slots: dict[int, list] = {}
cutoff_slot = now_utc() - dt.timedelta(seconds=SESSION_IDLE_SECONDS)
for svc in rdp_services: for svc in rdp_services:
slots = db.scalars(select(RdpSlot).where(RdpSlot.service_id == svc.id).order_by(RdpSlot.id)).all() slots = db.scalars(select(RdpSlot).where(RdpSlot.service_id == svc.id).order_by(RdpSlot.id)).all()
slot_list = [] slot_list = []
@@ -1962,7 +1965,6 @@ def admin_page(request: Request, admin: User = Depends(require_admin), db: Sessi
select(SessionModel).where( select(SessionModel).where(
SessionModel.container_id == f"RDPSLOT:{slot.id}", SessionModel.container_id == f"RDPSLOT:{slot.id}",
SessionModel.status == SessionStatus.ACTIVE, SessionModel.status == SessionStatus.ACTIVE,
SessionModel.last_access_at >= cutoff_slot,
) )
) )
running = False running = False
@@ -2162,12 +2164,10 @@ def go_service(
session_id = str(uuid.uuid4()) session_id = str(uuid.uuid4())
try: try:
with allocator_lock(db, 91003, timeout_seconds=GO_POOL_LOCK_TIMEOUT_SECONDS): with allocator_lock(db, 91003, timeout_seconds=GO_POOL_LOCK_TIMEOUT_SECONDS):
cutoff = now_utc() - dt.timedelta(seconds=SESSION_IDLE_SECONDS)
busy_slot_ids: set[int] = set() busy_slot_ids: set[int] = set()
for row in db.scalars( for row in db.scalars(
select(SessionModel).where( select(SessionModel).where(
SessionModel.status == SessionStatus.ACTIVE, SessionModel.status == SessionStatus.ACTIVE,
SessionModel.last_access_at >= cutoff,
SessionModel.service_id == service.id, SessionModel.service_id == service.id,
SessionModel.container_id.like("RDPSLOT:%"), SessionModel.container_id.like("RDPSLOT:%"),
) )