v1.2.20: remaining Codex fixes — _op_results lock, advisory lock for scenarios, _ensure_schema logging, stale async generation token, validate-cfs explicit JSONDecodeError
This commit is contained in:
+31
-24
@@ -24,6 +24,7 @@
|
||||
"""
|
||||
|
||||
from flask import Blueprint, current_app, jsonify, request
|
||||
import threading
|
||||
import uuid
|
||||
import os
|
||||
import json
|
||||
@@ -261,23 +262,27 @@ def api_test():
|
||||
|
||||
# Результаты фоновых операций: opUid → {status, error, stages, duration, _ts}
|
||||
# _ts — timestamp добавления, для TTL-очистки (макс. 500 записей или старше 1 часа)
|
||||
# ЗАЩИЩЕНО _op_results_lock — несколько потоков _finish_op + main thread api_test_status
|
||||
_op_results = {}
|
||||
_op_results_lock = threading.Lock()
|
||||
_MAX_OP_RESULTS = 500
|
||||
|
||||
|
||||
def _cleanup_op_results():
|
||||
"""Удалить старые записи: старше 1 часа или сверх лимита."""
|
||||
"""Удалить старые записи: старше 1 часа или сверх лимита.
|
||||
Потокобезопасно — под _op_results_lock."""
|
||||
import time
|
||||
now = time.time()
|
||||
# Удалить старше 1 часа
|
||||
stale = [k for k, v in _op_results.items() if now - v.get("_ts", 0) > 3600]
|
||||
for k in stale:
|
||||
del _op_results[k]
|
||||
# Если всё ещё много — удалить самые старые
|
||||
if len(_op_results) > _MAX_OP_RESULTS:
|
||||
sorted_keys = sorted(_op_results.keys(), key=lambda k: _op_results[k].get("_ts", 0))
|
||||
for k in sorted_keys[:len(_op_results) - _MAX_OP_RESULTS]:
|
||||
del _op_results[k]
|
||||
with _op_results_lock:
|
||||
# Удалить старше 1 часа (pop с default — безопасно при конкурентном доступе)
|
||||
stale = [k for k, v in _op_results.items() if now - v.get("_ts", 0) > 3600]
|
||||
for k in stale:
|
||||
_op_results.pop(k, None)
|
||||
# Если всё ещё много — удалить самые старые
|
||||
if len(_op_results) > _MAX_OP_RESULTS:
|
||||
sorted_keys = sorted(_op_results.keys(), key=lambda k: _op_results[k].get("_ts", 0))
|
||||
for k in sorted_keys[:len(_op_results) - _MAX_OP_RESULTS]:
|
||||
_op_results.pop(k, None)
|
||||
|
||||
|
||||
def _get_instance_display_name(client, instance_uid):
|
||||
@@ -291,7 +296,7 @@ def _get_instance_display_name(client, instance_uid):
|
||||
|
||||
|
||||
def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, client_id, stand, is_delete=False, params=None, user_email="", app_version=""):
|
||||
"""Фоном ждать dtFinish и сохранить результат."""
|
||||
"""Фоном ждать dtFinish и сохранить результат (потокобезопасно для _op_results)."""
|
||||
import time
|
||||
_cleanup_op_results()
|
||||
t0 = time.time()
|
||||
@@ -299,15 +304,16 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
|
||||
# Поллинг через общий модуль
|
||||
poll_result = poll_until_done(client, op_uid)
|
||||
|
||||
# Обновить _op_results для UI
|
||||
_op_results[op_uid] = {
|
||||
"status": poll_result["status"],
|
||||
"displayName": display_name,
|
||||
"error": poll_result["error_log"],
|
||||
"stages": poll_result["stages"],
|
||||
"duration": poll_result["duration"],
|
||||
"_ts": time.time(),
|
||||
}
|
||||
# Обновить _op_results для UI — под локом
|
||||
with _op_results_lock:
|
||||
_op_results[op_uid] = {
|
||||
"status": poll_result["status"],
|
||||
"displayName": display_name,
|
||||
"error": poll_result["error_log"],
|
||||
"stages": poll_result["stages"],
|
||||
"duration": poll_result["duration"],
|
||||
"_ts": time.time(),
|
||||
}
|
||||
|
||||
# tracker_remove при успешном delete
|
||||
if poll_result["status"] == "OK" and is_delete:
|
||||
@@ -346,10 +352,11 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
|
||||
|
||||
@bp.route("/api/test/status/<op_uid>")
|
||||
def api_test_status(op_uid):
|
||||
"""Получить текущий статус операции (поллинг с UI)."""
|
||||
# сначала проверяем фоновый трекер
|
||||
if op_uid in _op_results:
|
||||
return jsonify(_op_results[op_uid])
|
||||
"""Получить текущий статус операции (поллинг с UI) — потокобезопасно."""
|
||||
# сначала проверяем фоновый трекер — под локом
|
||||
with _op_results_lock:
|
||||
if op_uid in _op_results:
|
||||
return jsonify(_op_results[op_uid])
|
||||
# иначе спрашиваем API напрямую
|
||||
try:
|
||||
data = get_client().get(f"/instanceOperations/{op_uid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages")
|
||||
|
||||
Reference in New Issue
Block a user