Skip to content

CORE-446: fix LoadMarkets marketsLoaded invariant#4

Merged
ntemireyeva merged 661 commits into
masterfrom
task/CORE-446-fix-load-markets-invariant
May 11, 2026
Merged

CORE-446: fix LoadMarkets marketsLoaded invariant#4
ntemireyeva merged 661 commits into
masterfrom
task/CORE-446-fix-load-markets-invariant

Conversation

@ntemireyeva

@ntemireyeva ntemireyeva commented May 10, 2026

Copy link
Copy Markdown

Summary

Exchange.LoadMarkets() in go/v4/exchange.go unconditionally sets this.marketsLoaded = true after LoadMarketsHelper returns. When the helper's deferred recover catches an internal panic (e.g., FetchMarkets returns a 502 from the exchange), SetMarkets never runs and this.Markets stays nil — but the flag is flipped anyway. Every subsequent LoadMarkets() call then takes the fast path, returns a typed-nil *sync.Map that does not satisfy IsError(), and the next Market(symbol) panics with the misleading "markets not loaded" message. The exchange instance is permanently broken until destroyed.

This is observed weekly in production during Hyperliquid's scheduled maintenance windows: 502 responses from /info poison freshly-spawned CCXT instances, the supervised actor's circuit breaker trips, and pods stay silent until manual restart.

Fix

Only set marketsLoaded = true when this.Markets != nil (i.e., SetMarkets actually ran inside the helper). When the helper panicked, marketsLoaded stays false so subsequent calls retry through the slow path. The panic-string still flows to subscribers via the existing broadcast loop, so error observability is unchanged.

9 lines of code (one if guard + comment).

What this PR contains

  • Merge upstream ccxt/ccxt master (v4.5.47 → v4.5.52) — no conflicts.
  • The marketsLoaded invariant fix.

Test plan

  • go build ./... from go/v4/ passes
  • Verified against repro tests in the downstream consumer (platform-umbrella) — bug-repro path, real-HTTP-500 path, healthy path, and pre-seed comparison all behave as expected post-fix.
  • Reviewer to confirm no regressions in fork's own test suite (if any)

Out of scope

  • Tagging a new fork version (separate step after merge)
  • Bumping the platform-umbrella go.mod pin (separate PR there)
  • Submitting equivalent fix to upstream ccxt/ccxt (separate PR upstream)

References

  • Incident analysis: 3commas-io/platform-umbrella CORE-446

🤖 Generated with Claude Code

github-actions Bot and others added 30 commits April 20, 2026 13:13
    This commit was created by changelog-from-release in 'On release published (pushback changelog)' CI workflow
* fix(blofin): createOrder TPSL

* static tests

* minor

* sl tp

* upd

* lint
* fix(binance): parse spot order type

* binance: update test
* fix(aster): multi watchOrderBook sub

* fix market detection

Co-authored-by: Copilot <copilot@github.com>

* other fixes

Co-authored-by: Copilot <copilot@github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
github-actions Bot and others added 24 commits May 8, 2026 07:36
* fix(bitmart): ws subscriptions fix

* fix hash

* symbol unif

* fix multi sub

* base add subExistForRawHash

* sub rnm

* sub transpilable

* mov

* move to base

* exchange-wide

* upd
* fix(krakenfutures): watch unique subscriptions

* len
* refactor: safe dict first value

* comm

* refix

* go bool
Exchange.LoadMarkets() unconditionally set this.marketsLoaded = true
after LoadMarketsHelper returned, even when the helper panicked
internally and this.Markets was never populated. The next caller would
hit the fast path, receive a typed-nil *sync.Map that does not satisfy
IsError(), and the next this.Market(symbol) call would panic with
"markets not loaded" — masking the real upstream HTTP error and
permanently corrupting the exchange instance until destroyed.

Fix: only set marketsLoaded = true when this.Markets is non-nil
after the helper returns. When the helper panicked, leave
marketsLoaded = false so subsequent LoadMarkets() calls retry through
the slow path. The panic-string still flows to subscribers via the
existing broadcast loop, so error observability is unchanged.

Refs: 3commas-io/platform-umbrella CORE-446

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ntemireyeva
ntemireyeva merged commit 7870604 into master May 11, 2026
6 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants