func RunUDP(
	every time.Duration,
	h Hearbeater,
	lastUDPHeartbeatAck *atomic.Int64,
	lastUDPHeartbeatSent *atomic.Int64,
	stop chan struct{},
	errReporter func(err error),
) {
	ticker := time.NewTicker(every)
	defer ticker.Stop()
	first := true
	for {
		lastAck := time.Unix(0, lastUDPHeartbeatAck.Load())
		lastSent := time.Unix(0, lastUDPHeartbeatSent.Load())
		if !first && lastSent.After(lastAck) {
			errReporter(fmt.Errorf("no UDP heartbeat received since %v (%v ago)", lastAck, time.Since(lastAck)))
			return
		}
		if err := h(); err != nil {
			if isConnectionClosed(err) {
				return
			}
			errReporter(err)
			return
		}
		lastUDPHeartbeatSent.Store(time.Now().UnixNano())
		if first {
			first = false
		}
		select {
		case <-ticker.C:
		case <-stop:
			return
		}
	}
}
RunUDP periodically calls the given heartbeater to send a heartbeat packet.
It can be stopped by closing the stop channel and will report any error that
occurs using the given errReporter.
package heartbeat
import (
	"fmt"
	"net"
	"time"
	"go.uber.org/atomic"
)
type Hearbeater func() error
func Run(
	every time.Duration,
	h Hearbeater,
	lastHeartbeatAck *atomic.Int64,
	lastHeartbeatSent *atomic.Int64,
	stop chan struct{},
	errReporter func(err error),
) {
	ticker := time.NewTicker(every)
	defer ticker.Stop()
	first := true
	for {
		lastAck := time.Unix(0, lastHeartbeatAck.Load())
		lastSent := time.Unix(0, lastHeartbeatSent.Load())
		if !first && lastSent.After(lastAck) {
			errReporter(fmt.Errorf("no heartbeat received since %v (%v ago)", lastAck, time.Since(lastAck)))
			return
		}
		if err := h(); err != nil {
			errReporter(err)
			return
		}
		lastHeartbeatSent.Store(time.Now().UnixNano())
		if first {
			first = false
		}
		select {
		case <-ticker.C:
		case <-stop:
			return
		}
	}
}