Applied fixes (this commit):
get_connection() now a contextmanager that closes on exit (storage.py)services_orders.py)bitstamp_private_ws.py)Problem: private_ws_main retries with a fixed 5-second delay after any exception. If Bitstamp drops the connection repeatedly (rate limit, network blip), this creates a tight reconnect loop. Each reconnect calls list_accounts (opens a DB connection) and _get_token (HTTP call to Bitstamp), amplifying load.
Fix: Replace the fixed WS_RECONNECT_SECONDS sleep with exponential backoff:
Location: private_ws_main and _run_once in bitstamp_private_ws.py.
_CACHE_LOCKS unbounded growth (services_bitstamp.py)Problem: _CACHE_LOCKS dict grows every time a new cache_key is used (per account, per market symbol, etc.). Locks are never removed, even when the account is deleted or the cache entry expires.
Fix options (pick one):
weakref.WeakValueDictionary — locks get GC'd when no caller holds a reference. Simplest, but the with _cache_lock(cache_key): pattern keeps a strong reference during use, so the lock survives the call and is collected after._remove_cache_lock(cache_key) call in invalidate_account_cache and account deletion paths. More explicit but requires wiring up cleanup at every deletion site.Location: _CACHE_LOCKS, _cache_lock in services_bitstamp.py:24-46.
api_cache table eviction (repo.py / server.py)Problem: cache_put does an upsert, so existing keys don't duplicate. But expired rows are never deleted — cache_get skips them, but they stay in the SQLite file. Over weeks, the table grows and the file bloats. Same applies to order_records and account_balance_snapshots (no retention policy).
Fix: Add a _evict_expired_cache() function in repo.py:
def evict_expired_cache() -> int:
now = utc_now_iso()
with get_connection() as conn:
count = conn.execute("DELETE FROM api_cache WHERE expires_at <= ?", (now,)).rowcount
conn.commit()
return count
Call it from the existing metadata refresh loop (_metadata_refresh_loop in server.py) every cycle — it already runs every 24 hours, which is sufficient for cache eviction.
For order_records and account_balance_snapshots, consider a configurable retention (e.g., keep last 90 days, or last 10,000 rows per account). This is a bigger change and should be a separate discussion.
Location: repo.py, _metadata_refresh_loop in server.py.
Price lookups via crypto_client.py must always return fresh data. Caching is handled upstream. Do not add a local TTL cache here.
data/exec_mcp.sqlite3) — if growing, Fix 5 is needed.docker stats for the container after 24-48 hours of running to confirm memory is flat.