π HandleReconnect (GoMT4)¶
Goal: robust reconnects for unary and streaming RPCs using the helpers already present in this repo.
Real code refs:
- Backoff & helpers:
examples/mt4/MT4Account.go(backoffDelay,waitWithCtx,maxRetries, etc.)- Unary pattern:
examples/mt4/MT4Account.go(retry oncodes.Unavailable)- Streams:
OnSymbolTick,OnOpenedOrdersProfitwrappers
β 1) Principles¶
- Retry only transient transport errors:
codes.Unavailable,io.EOF. - Respect context (timeouts/cancel) to avoid leaks.
- Use exponential backoff + jitter (central constants in
MT4Account.go).
πΉ 2) Unary RPC with built-in retry (pattern)¶
Most account methods already follow this template: try β on Unavailable wait backoffDelay(attempt) β retry.
func (a *MT4Account) callWithRetry(ctx context.Context, fn func(context.Context) error) error {
var last error
for attempt := 0; attempt < maxRetries; attempt++ {
if err := fn(ctx); err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.Unavailable {
// transient transport β backoff, then retry
if err := waitWithCtx(ctx, backoffDelay(attempt)); err != nil { return err }
last = err
continue
}
return err // non-transient β bubble up
}
return nil // success
}
return fmt.Errorf("max retries reached: %w", last)
}
Usage (example: health-check AccountSummary):
hctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
err := a.callWithRetry(hctx, func(c context.Context) error {
_, err := a.AccountSummary(c)
return err
})
if err != nil { return err }
The same pattern is used internally by methods like
OrderSend,Quote, etc., in your account layer.
πΈ 3) Streaming reconnect loop (structure)¶
Your stream helpers (OnSymbolTick, OnOpenedOrdersProfit) already encapsulate the reconnect loop. The core logic looks like this:
func (a *MT4Account) runStreamWithReconnect(ctx context.Context, start func(context.Context) (recv func() (*pb.Tick, error), close func() error, err error),
) (<-chan *pb.Tick, <-chan error) {
dataCh := make(chan *pb.Tick, 1024)
errCh := make(chan error, 1)
go func() {
defer close(dataCh)
defer close(errCh)
for attempt := 0; attempt < maxRetries; attempt++ {
// (re)open stream
recv, closeFn, err := start(ctx)
if err != nil {
// cannot open β transient?
if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {
if err := waitWithCtx(ctx, backoffDelay(attempt)); err != nil { errCh <- err; return }
continue
}
errCh <- err; return
}
// receive loop
for {
msg, err := recv()
if err == nil {
select {
case dataCh <- msg:
case <-ctx.Done(): _ = closeFn(); return
}
continue
}
// stream error β decide if reconnect
if err == io.EOF {
// server closed β reconnect with backoff
} else if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {
// transient transport β reconnect
} else {
// permanent
_ = closeFn(); errCh <- err; return
}
_ = closeFn()
if err := waitWithCtx(ctx, backoffDelay(attempt)); err != nil { errCh <- err; return }
break // out to reopen
}
}
errCh <- fmt.Errorf("max stream retries reached")
}()
return dataCh, errCh
}
In your repo, this logic is packaged in concrete helpers:
OnSymbolTick(ctx, symbols),OnOpenedOrdersProfit(ctx, bufSize).
βΆοΈ 4) Consumer pattern (donβt block!)¶
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Example: quotes stream
dataCh, errCh := account.OnSymbolTick(ctx, []string{"EURUSD","GBPUSD"})
for {
select {
case <-ctx.Done():
return // graceful stop
case err := <-errCh:
if err != nil { log.Printf("stream stopped: %v", err); return }
case t := <-dataCh:
// offload heavy work
processAsync(t)
}
}
- Heavy work β send to the worker via a buffered channel.
- Don't forget
ctx.Done()for a clean finish.
π§ 5) Tuning backoff (central knobs)¶
Constants found in examples/mt4/MT4Account.go:
const (
backoffBase = 300 * time.Millisecond
backoffMax = 5 * time.Second
jitterRange = 200 * time.Millisecond
maxRetries = 10
)
- Home WiβFi / unstable β try
backoffMax=8β10s,jitterRange=300β400ms. - VPS / LAN β
backoffBase=150ms,backoffMax=3β5s.
β οΈ Pitfalls¶
- Retrain business errors β not allowed. We only use transport options (
Unavailable,EOF). - Forgot to cancel the context β goroutin leaks. Always `defer cancel()'.
- Blocking dataCh β the stream will stop. Either a buffer or a fast reception.
- Endless retreats β limit `MaxRetries', log the final error.
π See also¶
Reliability (en)β timeouts, reconnects & backoff summary.StreamQuotes.md,StreamOpenedOrderProfits.mdβ ready-made wrappers.UnaryRetries.mdβ point examples for individual methods.