Files
nofx/trader/syncloop/syncloop.go
tinkle-community 953240565f fix(trader): stop order-sync goroutine leak and rate-limit hammering
Every StartOrderSync spawned a ticker goroutine that ran forever — it
survived trader stop AND deletion, so each quick-created trader left a
permanent 30s Hyperliquid poll behind. Stacked leaks turned into an
~8s effective hammer that tripped Hyperliquid's 429 rate limit, which
then broke the symbol board, trader creation, and order sync itself.

- new trader/syncloop package: shared stoppable sync loop with
  exponential failure backoff (30s base, 5min cap)
- all 9 exchanges' StartOrderSync now take the trader's stop channel
  and stop when the trader stops (close broadcast from AutoTrader.Stop)
- provider/hyperliquid: GetPerpDexCoins now serves a 5min TTL cache and
  falls back to the stale board when the upstream returns 429, so the
  symbol panel keeps working through rate limiting
2026-06-11 21:45:31 +08:00

49 lines
1.4 KiB
Go

// Package syncloop runs background exchange order-sync loops with a shared
// lifecycle: loops stop when the owning trader stops, and consecutive
// failures back off exponentially so a rate-limiting exchange (HTTP 429)
// is not hammered at full frequency.
package syncloop
import (
"time"
"nofx/logger"
)
// maxBackoff caps the failure backoff so a recovered exchange is picked up
// within a few minutes at worst.
const maxBackoff = 5 * time.Minute
// Run starts a background loop calling syncFn at the given interval until
// stop is closed. After each consecutive failure the wait doubles (capped at
// maxBackoff); the first success resets it to the base interval.
func Run(stop <-chan struct{}, interval time.Duration, name string, syncFn func() error) {
if interval <= 0 {
interval = 30 * time.Second
}
go func() {
wait := interval
timer := time.NewTimer(wait)
defer timer.Stop()
for {
select {
case <-stop:
logger.Infof("⏹ %s order sync stopped", name)
return
case <-timer.C:
if err := syncFn(); err != nil {
wait *= 2
if wait > maxBackoff {
wait = maxBackoff
}
logger.Infof("⚠️ %s order sync failed: %v (backing off, next attempt in %v)", name, err, wait)
} else {
wait = interval
}
timer.Reset(wait)
}
}
}()
logger.Infof("🔄 %s order sync started (interval: %v)", name, interval)
}