fix(chat): resolve Auto-route failover event-loop freeze (#812)
Root cause: ensure_structured_column() executed a synchronous blocking engine.connect().execute(SELECT information_schema.columns ...) inside the async request path (_build_chat_history / _maybe_compress_history), freezing the uvicorn event loop and hanging all endpoints (~200s, recovered only by docker restart). Fix: - chat_context.py: remove per-request ensure_structured_column() calls; run once idempotently at app startup (main.py lifespan). Use CURRENT_DATABASE() for PostgreSQL compatibility. - main.py: add startup-only ensure_structured_column() hook (try/except). - llm_client.py: trust_env=False + bounded httpx.Timeout/wait_for so broken endpoints fail fast instead of hanging the event loop. - chat_service.py: run media generation in a background daemon thread with its own event loop (_launch_media_task_bg) so SSE end does not cancel image/video tasks (#811); remove TEMP faulthandler diagnostic. Safe-align: persist accumulated live container changes (admin/*, knowledge, market, models, config, observability, mcp_client, platform_knowledge, im_bot_platform, chat_media, capability_registry, requirements) and add workflow_templates. Verified: live failover test (model-006 -> conn-refused) returns in ~1.9s with concurrent login 0.83s/200; event-loop responsiveness restored (sub-second).
This commit is contained in:
1 parent
394a7786e0
commit
a2504bce34
27 files changed
+253
-59
No files matched your search
@@ -284,7 +284,7 @@ async def claim_achievement_reward(
|
||||
|
||||
# Mark reward as claimed
|
||||
_execute_raw(db,
|
||||
"UPDATE user_achievements SET reward_claimed = 1, claimed_at = NOW() WHERE user_id = :uid AND achievement_id = :aid",
|
||||
"UPDATE user_achievements SET reward_claimed = true, claimed_at = NOW() WHERE user_id = :uid AND achievement_id = :aid",
|
||||
{"uid": user_id, "aid": ach_id})
|
||||
|
||||
return {"code": 0, "message": "奖励领取成功"}
|
||||
|
||||
@@ -316,7 +316,7 @@ async def create_dashboard(
|
||||
config_json = json.loads('[{"type":"stat","title":"暂无数据","metric":"none","chart_type":"number"}]')
|
||||
|
||||
if is_default:
|
||||
db.execute(text("UPDATE analytics_dashboards SET is_default=0"))
|
||||
db.execute(text("UPDATE analytics_dashboards SET is_default=false"))
|
||||
|
||||
db.execute(text("""
|
||||
INSERT INTO analytics_dashboards (id, name, description, config, is_default, created_by)
|
||||
@@ -332,7 +332,7 @@ async def create_dashboard(
|
||||
@router.delete("/dashboards/{dashboard_id}")
|
||||
async def delete_dashboard(dashboard_id: str, db: Session = Depends(get_db), user=Depends(get_current_user)):
|
||||
"""删除仪表盘"""
|
||||
db.execute(text("DELETE FROM analytics_dashboards WHERE id=:id AND is_default=0"), {"id": dashboard_id})
|
||||
db.execute(text("DELETE FROM analytics_dashboards WHERE id=:id AND is_default=false"), {"id": dashboard_id})
|
||||
db.commit()
|
||||
return {"code": 200, "data": {"message": "仪表盘已删除"}}
|
||||
|
||||
|
||||
@@ -445,7 +445,7 @@ async def billing_stats(db: Session = Depends(get_db), user=Depends(get_current_
|
||||
SELECT DATE(created_at) as d, COALESCE(SUM(amount), 0) as rev
|
||||
FROM billing_transactions
|
||||
WHERE transaction_type='recharge' AND status='completed'
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
AND created_at >= NOW() - INTERVAL '7 DAY'
|
||||
GROUP BY DATE(created_at) ORDER BY d
|
||||
""")).fetchall()
|
||||
daily_revenue = [{"date": str(r[0]), "revenue": float(r[1])} for r in revenue_7d]
|
||||
|
||||
@@ -103,7 +103,7 @@ async def get_execution_stats(db: Session = Depends(get_db), _=Depends(get_curre
|
||||
try:
|
||||
total = _execute_raw(db, "SELECT COUNT(*) as cnt FROM code_exec_tasks")[0]["cnt"]
|
||||
today = _execute_raw(db,
|
||||
"SELECT COUNT(*) as cnt FROM code_exec_tasks WHERE DATE(created_at) = CURDATE()")[0]["cnt"]
|
||||
"SELECT COUNT(*) as cnt FROM code_exec_tasks WHERE DATE(created_at) = CURRENT_DATE")[0]["cnt"]
|
||||
success_count = _execute_raw(db,
|
||||
"SELECT COUNT(*) as cnt FROM code_exec_tasks WHERE exit_code = 0 AND error IS NULL")[0]["cnt"]
|
||||
failed_count = _execute_raw(db,
|
||||
|
||||
@@ -703,7 +703,7 @@ echo "Installation complete!"
|
||||
{d.get('version', '1.0.0')}
|
||||
|
||||
## Installation
|
||||
Run `bash install.sh` to install.
|
||||
Run \"bash install.sh\" to install.
|
||||
|
||||
## License
|
||||
A valid license key is required. Contact your administrator for details.
|
||||
|
||||
@@ -169,7 +169,7 @@ async def get_im_bot_stats(user=Depends(get_current_user)):
|
||||
today_stats = _row_to_dict(rows[0]) if rows else {"msg_in": 0, "msg_out": 0, "users": 0, "errors": 0}
|
||||
|
||||
# Total routing rules
|
||||
routes_count = _execute_raw(db, text("SELECT COUNT(*) as cnt FROM im_bot_routes WHERE is_active=1"))[0]._mapping['cnt']
|
||||
routes_count = _execute_raw(db, text("SELECT COUNT(*) as cnt FROM im_bot_routes WHERE is_active=true"))[0]._mapping['cnt']
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
@@ -202,7 +202,7 @@ async def get_dashboard(user=Depends(get_current_user)):
|
||||
SUM(messages_out) as msg_out,
|
||||
SUM(unique_users) as users
|
||||
FROM im_bot_daily_stats
|
||||
WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||
WHERE stat_date >= CURRENT_DATE - INTERVAL '7 DAY'
|
||||
GROUP BY stat_date
|
||||
ORDER BY stat_date
|
||||
"""))
|
||||
@@ -217,7 +217,7 @@ async def get_dashboard(user=Depends(get_current_user)):
|
||||
LEFT JOIN (
|
||||
SELECT channel_id, SUM(messages_in + messages_out) as msg_count
|
||||
FROM im_bot_daily_stats
|
||||
WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
WHERE stat_date >= CURRENT_DATE - INTERVAL '30 DAY'
|
||||
GROUP BY channel_id
|
||||
) s ON c.id = s.channel_id
|
||||
ORDER BY c.created_at DESC
|
||||
@@ -357,7 +357,7 @@ async def get_channel_detail(channel_id: str, user=Depends(get_current_user)):
|
||||
SELECT stat_date, messages_in, messages_out, unique_users, error_count
|
||||
FROM im_bot_daily_stats
|
||||
WHERE channel_id = :cid
|
||||
AND stat_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||
AND stat_date >= CURRENT_DATE - INTERVAL '7 DAY'
|
||||
ORDER BY stat_date
|
||||
"""), {"cid": channel_id})
|
||||
channel['stats_7d'] = _rows_to_dict(stats)
|
||||
@@ -428,7 +428,7 @@ async def activate_channel(channel_id: str, user=Depends(get_current_user)):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
_execute_raw(db, text(
|
||||
"UPDATE channels SET is_active=1, status='connected' WHERE id=:cid"
|
||||
"UPDATE channels SET is_active=true, status='connected' WHERE id=:cid"
|
||||
), {"cid": channel_id})
|
||||
return {"success": True, "message": "Channel activated"}
|
||||
except Exception as e:
|
||||
@@ -443,7 +443,7 @@ async def deactivate_channel(channel_id: str, user=Depends(get_current_user)):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
_execute_raw(db, text(
|
||||
"UPDATE channels SET is_active=0, status='disconnected' WHERE id=:cid"
|
||||
"UPDATE channels SET is_active=false, status='disconnected' WHERE id=:cid"
|
||||
), {"cid": channel_id})
|
||||
return {"success": True, "message": "Channel deactivated"}
|
||||
except Exception as e:
|
||||
|
||||
@@ -442,7 +442,7 @@ def revoke_license(
|
||||
raise HTTPException(404, "License not found")
|
||||
|
||||
_execute_raw(db, text(
|
||||
"UPDATE offline_licenses SET status = 'revoked', revoked_reason = :reason, is_active = 0 WHERE id = :lid"
|
||||
"UPDATE offline_licenses SET status = 'revoked', revoked_reason = :reason, is_active = false WHERE id = :lid"
|
||||
), {"reason": reason, "lid": license_id})
|
||||
|
||||
return {"code": 0, "message": "License revoked"}
|
||||
@@ -623,7 +623,7 @@ def get_usage_stats(db: Session = Depends(get_db)):
|
||||
"SELECT COUNT(DISTINCT user_hash) FROM usage_reports"
|
||||
))
|
||||
today_reports = _execute_raw(db, text(
|
||||
"SELECT COUNT(*) FROM usage_reports WHERE DATE(created_at) = CURDATE()"
|
||||
"SELECT COUNT(*) FROM usage_reports WHERE DATE(created_at) = CURRENT_DATE"
|
||||
))
|
||||
by_type = _execute_raw(db, text(
|
||||
"SELECT report_type, COUNT(*) as cnt FROM usage_reports GROUP BY report_type"
|
||||
|
||||
Reference in new issue
Block a user