修复复盘列表与统计并行同步撞锁导致模拟盘加载失败。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -929,7 +929,10 @@
|
||||
var kpi = $("or-kpi");
|
||||
var groups = $("or-stats-groups");
|
||||
if (!kpi || !groups) return;
|
||||
fetch("/api/options/review/stats?" + baseQs().toString(), { credentials: "same-origin" })
|
||||
// 与 trades?sync=1 并行时不再二次同步,避免 SQLite database is locked
|
||||
var qs = baseQs();
|
||||
qs.set("sync", "0");
|
||||
fetch("/api/options/review/stats?" + qs.toString(), { credentials: "same-origin" })
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
|
||||
@@ -66,13 +66,17 @@ def _safe_float(v: Any) -> Optional[float]:
|
||||
|
||||
|
||||
def get_sync_state(conn: sqlite3.Connection, key: str) -> Optional[str]:
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT value FROM options_review_sync_state WHERE key=?", (key,)
|
||||
).fetchone()
|
||||
except sqlite3.OperationalError:
|
||||
return None
|
||||
return str(row["value"]) if row and row["value"] is not None else None
|
||||
|
||||
|
||||
def set_sync_state(conn: sqlite3.Connection, key: str, value: str) -> None:
|
||||
try:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO options_review_sync_state(key, value, updated_at)
|
||||
@@ -81,6 +85,9 @@ def set_sync_state(conn: sqlite3.Connection, key: str, value: str) -> None:
|
||||
""",
|
||||
(key, value, _now_str()),
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
# 列表/统计并发刷新时可能撞锁;标记失败不阻断读列表
|
||||
pass
|
||||
|
||||
|
||||
def _purge_review_trade_by_key(conn: sqlite3.Connection, history_key: str) -> bool:
|
||||
@@ -627,8 +634,16 @@ def ensure_local_review_synced(
|
||||
repair_swapped_closed_at_signal_note,
|
||||
)
|
||||
|
||||
ts_migrate: dict[str, Any] = {}
|
||||
note_swap: dict[str, Any] = {}
|
||||
try:
|
||||
ts_migrate = migrate_options_utc_wall_clock_to_app(conn)
|
||||
except Exception as e:
|
||||
ts_migrate = {"ok": False, "msg": str(e)}
|
||||
try:
|
||||
note_swap = repair_swapped_closed_at_signal_note(conn)
|
||||
except Exception as e:
|
||||
note_swap = {"ok": False, "msg": str(e)}
|
||||
if backfill_exchange_pnl and ex is not None:
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import fetch_all_option_positions_history
|
||||
@@ -640,7 +655,10 @@ def ensure_local_review_synced(
|
||||
backfill_closed_options_realized_pnl_from_history(conn, hist)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
out = sync_all_review_sources(conn, ex=ex, from_exchange=False)
|
||||
except sqlite3.OperationalError as e:
|
||||
out = {"ok": False, "msg": f"sync locked: {e}"}
|
||||
if isinstance(out, dict):
|
||||
out["ts_migrate"] = ts_migrate
|
||||
out["note_swap"] = note_swap
|
||||
|
||||
@@ -291,9 +291,18 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
|
||||
"no",
|
||||
)
|
||||
if do_sync:
|
||||
try:
|
||||
ex, _err = _require_ex(cfg)
|
||||
ensure_local_review_synced(conn, ex=ex if ex is not None else None)
|
||||
conn.commit()
|
||||
except Exception as sync_err:
|
||||
# 同步撞锁时仍返回已有列表,避免整页 500
|
||||
print(f"[options_review_trades] sync skipped: {sync_err}", flush=True)
|
||||
try:
|
||||
conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
init_options_review_tables(conn)
|
||||
filt = dict(
|
||||
source_type=_review_source_for_mode(request.args.get("source_type")),
|
||||
underlying=(request.args.get("underlying") or "").strip() or None,
|
||||
@@ -433,10 +442,27 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
|
||||
@lr
|
||||
def api_options_review_stats():
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
# 默认不同步:与 trades?sync=1 并行时避免双写撞锁;需要时再 sync=1
|
||||
do_sync = (request.args.get("sync") or "0").strip().lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
)
|
||||
if do_sync:
|
||||
try:
|
||||
ex, _err = _require_ex(cfg)
|
||||
ensure_local_review_synced(conn, ex=ex if ex is not None else None)
|
||||
conn.commit()
|
||||
except Exception as sync_err:
|
||||
print(f"[options_review_stats] sync skipped: {sync_err}", flush=True)
|
||||
try:
|
||||
conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
init_options_review_tables(conn)
|
||||
else:
|
||||
init_options_review_tables(conn)
|
||||
stats = compute_review_stats(
|
||||
conn,
|
||||
source_type=_review_source_for_mode(request.args.get("source_type")),
|
||||
|
||||
@@ -112,11 +112,16 @@ def migrate_options_utc_wall_clock_to_app(conn: sqlite3.Connection) -> dict[str,
|
||||
|
||||
init_options_tables(conn)
|
||||
trades = _shift_table("options_trades", ("created_at", "closed_at"))
|
||||
except sqlite3.OperationalError:
|
||||
return {"skipped": 0, "trades": 0, "review": 0, "hidden": 0, "locked": 1}
|
||||
except Exception:
|
||||
trades = 0
|
||||
|
||||
try:
|
||||
review = _shift_table("options_review_trades", ("opened_at", "closed_at"))
|
||||
hidden = _shift_table("options_review_hidden", ("closed_at",))
|
||||
except sqlite3.OperationalError:
|
||||
return {"skipped": 0, "trades": trades, "review": 0, "hidden": 0, "locked": 1}
|
||||
|
||||
set_sync_state(conn, _MIGRATE_FLAG, "1")
|
||||
return {"skipped": 0, "trades": trades, "review": review, "hidden": hidden}
|
||||
@@ -129,7 +134,7 @@ def repair_swapped_closed_at_signal_note(conn: sqlite3.Connection) -> dict[str,
|
||||
幂等:同步状态 options_closed_at_note_swap_done=1 后仍扫描可修行(新坏行可再修).
|
||||
"""
|
||||
from lib.options.options_review_db import init_options_review_tables
|
||||
from lib.options.options_review_lib import set_sync_state
|
||||
from lib.options.options_review_lib import get_sync_state, set_sync_state
|
||||
|
||||
init_options_review_tables(conn)
|
||||
trades_fixed = 0
|
||||
@@ -174,9 +179,13 @@ def repair_swapped_closed_at_signal_note(conn: sqlite3.Connection) -> dict[str,
|
||||
(note, hk, closed),
|
||||
)
|
||||
review_fixed += int(cur.rowcount or 0)
|
||||
except sqlite3.OperationalError:
|
||||
# 并发刷新撞锁时跳过本轮修复,不影响列表读取
|
||||
return {"trades": 0, "review": 0, "locked": 1}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 复盘表里仍残留非时间戳 closed_at 的,若能从 local_opt 源修好则已处理;否则仅标记完成
|
||||
# 已标记过则不再写同步状态,减少并发写冲突
|
||||
if get_sync_state(conn, _SWAP_REPAIR_FLAG) != "1":
|
||||
set_sync_state(conn, _SWAP_REPAIR_FLAG, "1")
|
||||
return {"trades": trades_fixed, "review": review_fixed}
|
||||
|
||||
@@ -456,4 +456,4 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="/static/options_review.js?v=24"></script>
|
||||
<script src="/static/options_review.js?v=25"></script>
|
||||
|
||||
Reference in New Issue
Block a user