From 257857338c19e7a636dd8fc1ff41bd42beef9d1d Mon Sep 17 00:00:00 2001 From: anti Date: Thu, 30 Apr 2026 21:24:11 -0400 Subject: [PATCH] fix(api): replace threading.Lock with asyncio.Lock for hydration guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- decnet/realism/diurnal.py | 2 +- decnet/web/router/realism/api_config.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/decnet/realism/diurnal.py b/decnet/realism/diurnal.py index dc1a2080..f555234e 100644 --- a/decnet/realism/diurnal.py +++ b/decnet/realism/diurnal.py @@ -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("-") diff --git a/decnet/web/router/realism/api_config.py b/decnet/web/router/realism/api_config.py index 55b9064d..214f4726 100644 --- a/decnet/web/router/realism/api_config.py +++ b/decnet/web/router/realism/api_config.py @@ -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: