discovery: prevent double-close panic; fix IPv6 URLs & expiration guard

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9f7b1a34065de77f4a8982b0f5feebc86a6a6964
This commit is contained in:
raf 2026-03-27 21:43:37 +03:00
commit 0f62641d23
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"net"
"sync" "sync"
"time" "time"
@ -20,6 +21,7 @@ type Discovery struct {
discovered map[string]*discoveredPeer discovered map[string]*discoveredPeer
mu sync.RWMutex mu sync.RWMutex
stopCh chan struct{} stopCh chan struct{}
stopOnce sync.Once
waitGroup sync.WaitGroup waitGroup sync.WaitGroup
onAddUpstream func(url string, priority int) onAddUpstream func(url string, priority int)
onRemoveUpstream func(url string) onRemoveUpstream func(url string)
@ -71,6 +73,7 @@ func (d *Discovery) Start(ctx context.Context) error {
if err := d.resolver.Browse(ctx, d.cfg.ServiceName, d.cfg.Domain, entries); err != nil { if err := d.resolver.Browse(ctx, d.cfg.ServiceName, d.cfg.Domain, entries); err != nil {
close(entries) close(entries)
d.stopOnce.Do(func() { close(d.stopCh) })
d.waitGroup.Wait() d.waitGroup.Wait()
return fmt.Errorf("browse services: %w", err) return fmt.Errorf("browse services: %w", err)
} }
@ -85,7 +88,7 @@ func (d *Discovery) Start(ctx context.Context) error {
// Stops the discovery process. // Stops the discovery process.
func (d *Discovery) Stop() { func (d *Discovery) Stop() {
close(d.stopCh) d.stopOnce.Do(func() { close(d.stopCh) })
d.waitGroup.Wait() d.waitGroup.Wait()
} }
@ -122,8 +125,7 @@ func (d *Discovery) handleEntry(_ context.Context, entry *zeroconf.ServiceEntry)
addr = entry.AddrIPv6[0].String() addr = entry.AddrIPv6[0].String()
} }
port := entry.Port url := "http://" + net.JoinHostPort(addr, fmt.Sprintf("%d", entry.Port))
url := fmt.Sprintf("http://%s:%d", addr, port)
key := fmt.Sprintf("%s@%s", entry.Instance, entry.HostName) key := fmt.Sprintf("%s@%s", entry.Instance, entry.HostName)
d.mu.Lock() d.mu.Lock()
@ -184,6 +186,9 @@ func (d *Discovery) cleanupPeers() {
// TTL is the discovery response time; peers should re-announce periodically. // TTL is the discovery response time; peers should re-announce periodically.
// Use 3x TTL as the expiration window. // Use 3x TTL as the expiration window.
expiration := d.cfg.DiscoveryTime.Duration * 3 expiration := d.cfg.DiscoveryTime.Duration * 3
if expiration == 0 {
expiration = time.Second
}
for key, peer := range d.discovered { for key, peer := range d.discovered {
if now.Sub(peer.lastSeen) > expiration { if now.Sub(peer.lastSeen) > expiration {