package gateway import ( "context" "math/rand" "time" "github.com/rs/zerolog" ) type Heartbeat interface { Start(ctx context.Context, interval time.Duration) Stop() ForceHeartbeat() Ack() } var _ Heartbeat = &heartbeatImpl{} type heartbeatImpl struct { logger *zerolog.Logger gateway Gateway cancelRoutine context.CancelFunc } func NewHeartbeat(logger *zerolog.Logger, gateway Gateway) *heartbeatImpl { return &heartbeatImpl{ logger: logger, gateway: gateway, cancelRoutine: nil, } } func (h *heartbeatImpl) Start(ctx context.Context, interval time.Duration) { routineCtx, cancel := context.WithCancel(ctx) h.cancelRoutine = cancel go h.heartbeatRoutine(routineCtx, interval) } func (h *heartbeatImpl) Stop() { h.cancelRoutine() h.cancelRoutine = nil } func (h *heartbeatImpl) ForceHeartbeat() { h.gateway.Heartbeat() } func (h *heartbeatImpl) Ack() { // What do we do here? h.logger.Debug().Msg("received heartbeat ack") } func (h *heartbeatImpl) heartbeatRoutine(ctx context.Context, interval time.Duration) { h.logger.Debug().Msgf("beating heart every %dms", interval.Milliseconds()) // REF: heartbeat_interval * jitter jitter := rand.Intn(int(interval)) select { case <-time.After(time.Duration(jitter)): case <-ctx.Done(): h.logger.Debug().Msg("heartbeat routine stopped before jitter heartbeat") return } ticker := time.NewTicker(interval) defer ticker.Stop() for { h.logger.Debug().Msg("sending heartbeat") if err := h.gateway.Heartbeat(); err != nil { h.logger.Error().Err(err).Msg("failed to send heartbeat") } select { case <-ctx.Done(): return case <-ticker.C: continue } } }