fix: pool backup failover on reconnect, perfect dropper scripts, bcrypt password hashing

This commit is contained in:
drjones
2026-05-30 12:49:40 -07:00
parent 9eaf1e82ac
commit 0e19eb9eb9
7 changed files with 276 additions and 110 deletions

View File

@@ -84,6 +84,14 @@ type Proxy struct {
reconnecting bool
reconnectDelay time.Duration
verboseTraffic bool
// Pool failover: if the active config fails on reconnect, cycle through
// these alternatives before giving up. The primary Config is always index -1
// (the original); backups are tried in order. primaryConfig preserves the
// original so we can reset after a full round fails.
primaryConfig Config
backupConfigs []Config
backupIdx int // next backup to try
}
type PendingShare struct {
@@ -116,6 +124,7 @@ type Config struct {
func NewProxy(cfg *Config) *Proxy {
return &Proxy{
config: cfg,
primaryConfig: *cfg,
stopCh: make(chan struct{}),
shareQueue: make(chan *PendingShare, 100),
pendingResults: make(map[int]*pendingShareResult),
@@ -123,6 +132,14 @@ func NewProxy(cfg *Config) *Proxy {
}
}
// SetBackupConfigs registers fallback pool configs tried in order when the
// active config fails on reconnect. Call before Start().
func (p *Proxy) SetBackupConfigs(backups []Config) {
p.mu.Lock()
p.backupConfigs = backups
p.mu.Unlock()
}
func (p *Proxy) SetReconnectDelay(d time.Duration) {
if d > 0 {
p.mu.Lock()
@@ -702,18 +719,64 @@ func (p *Proxy) reconnect() {
default:
}
if err := p.connect(); err != nil {
log.Printf("[Pool] Reconnect failed: %v", err)
if p.onError != nil {
p.onError(fmt.Errorf("pool reconnect failed: %w", err))
// Build the full candidate list: [current primary] + backup configs.
// If the primary fails we rotate through backups so the fleet keeps hashing
// even when the configured pool is down.
p.mu.RLock()
primary := p.primaryConfig
backups := append([]Config(nil), p.backupConfigs...) // copy
startIdx := p.backupIdx
p.mu.RUnlock()
candidates := make([]Config, 0, 1+len(backups))
candidates = append(candidates, primary)
candidates = append(candidates, backups...)
total := len(candidates)
for i := 0; i < total; i++ {
// Rotate: try startIdx (last successful backup) before falling back to primary
idx := (startIdx + i) % total
cfg := candidates[idx]
p.mu.Lock()
*p.config = cfg
p.mu.Unlock()
if err := p.connect(); err != nil {
label := "primary"
if idx > 0 {
label = fmt.Sprintf("backup#%d (%s:%d)", idx, cfg.Host, cfg.Port)
}
log.Printf("[Pool] Reconnect to %s failed: %v", label, err)
continue
}
time.Sleep(30 * time.Second)
select {
case <-p.stopCh:
return
default:
p.scheduleReconnect()
// Succeeded — remember where we are so next reconnect starts here
p.mu.Lock()
p.backupIdx = idx
p.mu.Unlock()
if idx > 0 {
log.Printf("[Pool] Now connected to backup#%d (%s:%d)", idx, cfg.Host, cfg.Port)
}
return
}
// All candidates failed
if p.onError != nil {
p.onError(fmt.Errorf("all pool endpoints unreachable (%d tried)", total))
}
// Restore primary so next cycle starts fresh
p.mu.Lock()
*p.config = primary
p.backupIdx = 0
p.mu.Unlock()
time.Sleep(30 * time.Second)
select {
case <-p.stopCh:
return
default:
p.scheduleReconnect()
}
}