veteroner commited on
Commit
0a910ec
·
verified ·
1 Parent(s): 918f6f8

fix: WAL-inclusive backup + reachable scorecard GO + complete run log

Browse files
Files changed (1) hide show
  1. trading/trading_state_sync.py +49 -2
trading/trading_state_sync.py CHANGED
@@ -102,6 +102,45 @@ def _download_bytes(remote: str) -> bytes | None:
102
  return None
103
 
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  def upload_trading_state(market_id: str) -> bool:
106
  """Push state.json + trading.db to Supabase. Returns True if anything uploaded."""
107
  if not _enabled():
@@ -120,8 +159,9 @@ def upload_trading_state(market_id: str) -> bool:
120
  db_path = get_trading_db_path(market_id)
121
  if Path(db_path).exists():
122
  try:
123
- if _upload_bytes(_db_remote(market_id), Path(db_path).read_bytes(),
124
- "application/octet-stream"):
 
125
  ok = True
126
  except Exception as e:
127
  logger.warning("trading_state: db read/upload error (%s): %s", market_id, e)
@@ -157,6 +197,13 @@ def download_trading_state(market_id: str, *, overwrite: bool = False) -> bool:
157
  if body:
158
  db_path.parent.mkdir(parents=True, exist_ok=True)
159
  db_path.write_bytes(body)
 
 
 
 
 
 
 
160
  restored = True
161
 
162
  if restored:
 
102
  return None
103
 
104
 
105
+ def _consistent_db_snapshot(db_path: Path) -> bytes | None:
106
+ """Produce a consistent full snapshot of a SQLite DB, INCLUDING un-checkpointed
107
+ WAL contents.
108
+
109
+ TradingStore runs in WAL mode: recent transactions live in `trading.db-wal`
110
+ until a checkpoint, so a raw file copy of the main .db misses them — in
111
+ production this froze the remote backup days behind the live data. The
112
+ sqlite3 online-backup API reads *through* the WAL, giving a complete,
113
+ transactionally-consistent image even while the worker holds open
114
+ connections.
115
+ """
116
+ import sqlite3
117
+ import tempfile
118
+
119
+ tmp = tempfile.NamedTemporaryFile(prefix="dbsnap_", suffix=".db", delete=False)
120
+ tmp_path = Path(tmp.name)
121
+ tmp.close()
122
+ try:
123
+ src = sqlite3.connect(str(db_path))
124
+ try:
125
+ dst = sqlite3.connect(str(tmp_path))
126
+ try:
127
+ src.backup(dst)
128
+ dst.commit()
129
+ finally:
130
+ dst.close()
131
+ finally:
132
+ src.close()
133
+ return tmp_path.read_bytes()
134
+ except Exception as e:
135
+ logger.warning("trading_state: db snapshot failed for %s: %s", db_path, e)
136
+ return None
137
+ finally:
138
+ try:
139
+ tmp_path.unlink(missing_ok=True)
140
+ except Exception:
141
+ pass
142
+
143
+
144
  def upload_trading_state(market_id: str) -> bool:
145
  """Push state.json + trading.db to Supabase. Returns True if anything uploaded."""
146
  if not _enabled():
 
159
  db_path = get_trading_db_path(market_id)
160
  if Path(db_path).exists():
161
  try:
162
+ payload = _consistent_db_snapshot(Path(db_path))
163
+ if payload and _upload_bytes(_db_remote(market_id), payload,
164
+ "application/octet-stream"):
165
  ok = True
166
  except Exception as e:
167
  logger.warning("trading_state: db read/upload error (%s): %s", market_id, e)
 
197
  if body:
198
  db_path.parent.mkdir(parents=True, exist_ok=True)
199
  db_path.write_bytes(body)
200
+ # Drop any leftover WAL/SHM siblings: mixing a freshly-restored main
201
+ # file with a stale WAL from a previous incarnation corrupts state.
202
+ for suffix in ("-wal", "-shm"):
203
+ try:
204
+ Path(str(db_path) + suffix).unlink(missing_ok=True)
205
+ except Exception:
206
+ pass
207
  restored = True
208
 
209
  if restored: