fix: pass scalars to _check_single_node to prevent DetachedInstanceError
- Refactor _check_single_node to accept plain scalar args (node_id, check_method, check_target, ip) instead of a detached ORM Node object - Extract scalars inside the session in _run_status_checks before it closes - Capture datetime.now() once per check to keep DB and WebSocket timestamps consistent
This commit is contained in:
@@ -16,30 +16,40 @@ logger = logging.getLogger(__name__)
|
|||||||
scheduler: AsyncIOScheduler = AsyncIOScheduler()
|
scheduler: AsyncIOScheduler = AsyncIOScheduler()
|
||||||
|
|
||||||
|
|
||||||
async def _check_single_node(node: Node) -> tuple[str, dict[str, object] | None]:
|
async def _check_single_node(
|
||||||
"""Run a single node check; returns (node_id, result_or_None)."""
|
node_id: str,
|
||||||
|
check_method: str,
|
||||||
|
check_target: str | None,
|
||||||
|
ip: str | None,
|
||||||
|
) -> tuple[str, dict[str, object] | None]:
|
||||||
|
"""Run a single node check; returns (node_id, result_or_None).
|
||||||
|
|
||||||
|
Accepts plain scalars — not an ORM object — so there is no risk of
|
||||||
|
DetachedInstanceError when the originating session has already closed.
|
||||||
|
"""
|
||||||
from app.api.routes.status import broadcast_status # avoid circular import
|
from app.api.routes.status import broadcast_status # avoid circular import
|
||||||
|
|
||||||
try:
|
try:
|
||||||
check_result = await check_node(node.check_method or "", node.check_target, node.ip)
|
check_result = await check_node(check_method, check_target, ip)
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
async with AsyncSessionLocal() as db:
|
async with AsyncSessionLocal() as db:
|
||||||
n = await db.get(Node, node.id)
|
n = await db.get(Node, node_id)
|
||||||
if n:
|
if n:
|
||||||
n.status = check_result["status"]
|
n.status = check_result["status"]
|
||||||
n.response_time_ms = check_result["response_time_ms"]
|
n.response_time_ms = check_result["response_time_ms"]
|
||||||
if check_result["status"] == "online":
|
if check_result["status"] == "online":
|
||||||
n.last_seen = datetime.now(timezone.utc)
|
n.last_seen = now
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await broadcast_status(
|
await broadcast_status(
|
||||||
node_id=node.id,
|
node_id=node_id,
|
||||||
status=check_result["status"],
|
status=check_result["status"],
|
||||||
checked_at=datetime.now(timezone.utc).isoformat(),
|
checked_at=now.isoformat(),
|
||||||
response_time_ms=check_result["response_time_ms"],
|
response_time_ms=check_result["response_time_ms"],
|
||||||
)
|
)
|
||||||
return node.id, check_result
|
return node_id, check_result
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Status check failed for node %s: %s", node.id, exc)
|
logger.error("Status check failed for node %s: %s", node_id, exc)
|
||||||
return node.id, None
|
return node_id, None
|
||||||
|
|
||||||
|
|
||||||
async def _run_status_checks() -> None:
|
async def _run_status_checks() -> None:
|
||||||
@@ -47,12 +57,20 @@ async def _run_status_checks() -> None:
|
|||||||
async with AsyncSessionLocal() as db:
|
async with AsyncSessionLocal() as db:
|
||||||
result = await db.execute(select(Node))
|
result = await db.execute(select(Node))
|
||||||
nodes = result.scalars().all()
|
nodes = result.scalars().all()
|
||||||
|
# Extract scalars while the session is open to avoid DetachedInstanceError
|
||||||
|
checkable = [
|
||||||
|
(n.id, n.check_method, n.check_target, n.ip)
|
||||||
|
for n in nodes
|
||||||
|
if n.check_method
|
||||||
|
]
|
||||||
|
|
||||||
checkable = [n for n in nodes if n.check_method]
|
|
||||||
if not checkable:
|
if not checkable:
|
||||||
return
|
return
|
||||||
|
|
||||||
await asyncio.gather(*[_check_single_node(n) for n in checkable])
|
await asyncio.gather(*[
|
||||||
|
_check_single_node(node_id, method, target, ip)
|
||||||
|
for node_id, method, target, ip in checkable
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
def start_scheduler() -> None:
|
def start_scheduler() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user