Skip to content

Commit d888a81

Browse files
greynewellclaude
andauthored
Fix deadlock when UDP port is already in use (#58)
* Fix deadlock when UDP port 7734 is already in use When listenUDP failed silently and FSWatch was disabled, the Run() select loop had no way to receive events — causing a deadlock panic on kill or hanging forever. Now listenUDP signals startup success/failure via a buffered channel; if the port is already bound (watch already running), we return a clear error immediately instead of hanging. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Distinguish EADDRINUSE from other UDP bind errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1ca605e commit d888a81

1 file changed

Lines changed: 16 additions & 3 deletions

File tree

internal/files/daemon.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"context"
55
"crypto/rand"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"net"
910
"os"
1011
"path/filepath"
1112
"sort"
1213
"strings"
1314
"sync"
15+
"syscall"
1416
"time"
1517

1618
"github.com/supermodeltools/cli/internal/api"
@@ -84,7 +86,17 @@ func (d *Daemon) Run(ctx context.Context) error {
8486

8587
d.logf("[step:2] Starting listeners")
8688
if d.cfg.NotifyPort > 0 {
87-
go d.listenUDP(ctx)
89+
udpReady := make(chan error, 1)
90+
go d.listenUDP(ctx, udpReady)
91+
if err := <-udpReady; err != nil {
92+
if !d.cfg.FSWatch {
93+
if errors.Is(err, syscall.EADDRINUSE) {
94+
return fmt.Errorf("UDP port %d already in use — is `supermodel watch` already running?", d.cfg.NotifyPort)
95+
}
96+
return fmt.Errorf("failed to start UDP listener on port %d: %w", d.cfg.NotifyPort, err)
97+
}
98+
d.logf("Warning: UDP listener failed (FSWatch active, continuing): %v", err)
99+
}
88100
}
89101

90102
if d.cfg.FSWatch {
@@ -525,14 +537,15 @@ func (d *Daemon) computeAffectedFiles(changedFiles []string) []string {
525537
return daemonSortedKeys(affected)
526538
}
527539

528-
func (d *Daemon) listenUDP(ctx context.Context) {
540+
func (d *Daemon) listenUDP(ctx context.Context, ready chan<- error) {
529541
addr := fmt.Sprintf("127.0.0.1:%d", d.cfg.NotifyPort)
530542
conn, err := net.ListenPacket("udp", addr)
531543
if err != nil {
532-
d.logf("UDP listener failed: %v", err)
544+
ready <- err
533545
return
534546
}
535547
defer conn.Close()
548+
ready <- nil
536549
d.logf("UDP listener on %s", addr)
537550

538551
go func() {

0 commit comments

Comments
 (0)