fix(admin): починить панель отладки — она отдавала 500
Моя же правка про зависшие проверки уронила /admin/debug: колонки tasks в PostgreSQL — timestamptz, а модель объявляет их без зоны, и asyncpg отверг переданное из Python время («can't subtract offset-naive and offset-aware»). Возраст задачи теперь считает сама база (now() - coalesce(updated_at, created_at)), так что расхождение объявления и реального типа колонки роли не играет. Проверено на живых данных: находит все 4 задачи, висящие с 30 мая. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import io
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -929,20 +929,24 @@ async def debug_snapshot(db: AsyncSession = Depends(get_db)) -> dict:
|
|||||||
# Пользователь видит вечное «обрабатывается» — молча и без следов в логах,
|
# Пользователь видит вечное «обрабатывается» — молча и без следов в логах,
|
||||||
# поэтому такие задачи должны быть видны в отладке (нашлись экземпляры,
|
# поэтому такие задачи должны быть видны в отладке (нашлись экземпляры,
|
||||||
# висевшие с мая).
|
# висевшие с мая).
|
||||||
# tasks.created_at/updated_at — timestamptz, поэтому сравниваем с
|
#
|
||||||
# осведомлённым о зоне временем (naive utcnow() дал бы TypeError)
|
# Возраст считает сама база: колонки в PostgreSQL — timestamptz, а модель
|
||||||
task_stale_cutoff = datetime.now(UTC) - timedelta(hours=2)
|
# объявляет их без зоны, и передача сюда Python-времени ломается на
|
||||||
|
# несовпадении (asyncpg отвергает aware-параметр, naive даёт неверный сдвиг).
|
||||||
stale_tasks = (
|
stale_tasks = (
|
||||||
await db.execute(
|
await db.execute(
|
||||||
select(Task)
|
text("""
|
||||||
.where(
|
SELECT public_id, type, created_at,
|
||||||
Task.status == "processing",
|
round(extract(epoch FROM now() - coalesce(updated_at, created_at))
|
||||||
func.coalesce(Task.updated_at, Task.created_at) < task_stale_cutoff,
|
/ 3600.0, 1) AS stuck_hours
|
||||||
)
|
FROM tasks
|
||||||
.order_by(Task.created_at.desc())
|
WHERE status = 'processing'
|
||||||
.limit(20)
|
AND coalesce(updated_at, created_at) < now() - interval '2 hours'
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 20
|
||||||
|
""")
|
||||||
)
|
)
|
||||||
).scalars().all()
|
).all()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"generated_at": datetime.utcnow().isoformat(timespec="seconds"),
|
"generated_at": datetime.utcnow().isoformat(timespec="seconds"),
|
||||||
@@ -980,9 +984,7 @@ async def debug_snapshot(db: AsyncSession = Depends(get_db)) -> dict:
|
|||||||
"public_id": t.public_id,
|
"public_id": t.public_id,
|
||||||
"type": t.type,
|
"type": t.type,
|
||||||
"created_at": t.created_at,
|
"created_at": t.created_at,
|
||||||
"stuck_hours": round(
|
"stuck_hours": float(t.stuck_hours),
|
||||||
(datetime.now(UTC) - (t.updated_at or t.created_at)).total_seconds() / 3600, 1
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
for t in stale_tasks
|
for t in stale_tasks
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user