fix(api): replace threading.Lock with asyncio.Lock for hydration guard

await inside a threading.Lock yields to the event loop while the OS
thread still holds the lock — potential deadlock under FastAPI thread
pool dispatch. asyncio.Lock is the correct primitive for async
critical sections. Also fixed stale diurnal.py docstring that had the
delegation direction backwards.
This commit is contained in:
2026-04-30 21:24:11 -04:00
parent 3fce597a70
commit 257857338c
2 changed files with 4 additions and 4 deletions

View File

@@ -38,7 +38,7 @@ def _parse_window(window: str) -> tuple[int, int, int, int] | None:
Returns ``None`` for malformed input — callers treat that as
"always-on" so a single config typo never silences the whole fleet
(mirrors :func:`decnet.realism.personas.in_active_hours` semantics).
(:func:`decnet.realism.personas.in_active_hours` delegates here).
"""
try:
start_s, end_s = window.split("-")

View File

@@ -16,8 +16,8 @@ waiting for the orchestrator's next refresh tick.
"""
from __future__ import annotations
import asyncio
import json
import threading
from typing import Any
from fastapi import APIRouter, Depends, HTTPException
@@ -32,7 +32,7 @@ log = get_logger("api.realism.config")
_CONFIG_KEY = "weights"
_hydrated = False
_hydrate_lock = threading.Lock()
_hydrate_lock = asyncio.Lock()
@router.get(
@@ -56,7 +56,7 @@ async def get_config(
"""
global _hydrated
if not _hydrated:
with _hydrate_lock:
async with _hydrate_lock:
if not _hydrated:
row = await repo.get_realism_config(_CONFIG_KEY)
if row is not None: