diff --git a/cmd/clawgo/main.go b/cmd/clawgo/main.go index cd521e3..5d3a4ca 100644 --- a/cmd/clawgo/main.go +++ b/cmd/clawgo/main.go @@ -350,7 +350,12 @@ func runNode(cfg NodeConfig) error { client, err := connectBridge(cfg.BridgeAddr) if err != nil { logf("bridge connect failed: %v", err) - time.Sleep(backoff) + if err := sleepContext(ctx, backoff); err != nil { + if mdnsCleanup != nil { + mdnsCleanup() + } + return nil + } if backoff < 15*time.Second { backoff *= 2 if backoff > 15*time.Second { @@ -482,7 +487,12 @@ func runNode(cfg NodeConfig) error { } return nil } - time.Sleep(backoff) + if err := sleepContext(ctx, backoff); err != nil { + if mdnsCleanup != nil { + mdnsCleanup() + } + return nil + } if backoff < 15*time.Second { backoff *= 2 if backoff > 15*time.Second { diff --git a/cmd/clawgo/sleep.go b/cmd/clawgo/sleep.go new file mode 100644 index 0000000..b3f1645 --- /dev/null +++ b/cmd/clawgo/sleep.go @@ -0,0 +1,17 @@ +package main + +import ( + "context" + "time" +) + +func sleepContext(ctx context.Context, d time.Duration) error { + t := time.NewTimer(d) + defer t.Stop() + select { + case <-ctx.Done(): + return ctx.Err() + case <-t.C: + return nil + } +} diff --git a/cmd/clawgo/sleep_test.go b/cmd/clawgo/sleep_test.go new file mode 100644 index 0000000..4f4cc6d --- /dev/null +++ b/cmd/clawgo/sleep_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "errors" + "testing" + "time" +) + +func TestSleepContextCanceledReturnsCanceled(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + err := sleepContext(ctx, 30*time.Second) + if !errors.Is(err, context.Canceled) { + t.Fatalf("sleepContext(canceled, 30s) = %v, want context.Canceled", err) + } +} + +func TestSleepContextCancelDuringWait(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan error, 1) + go func() { + done <- sleepContext(ctx, 30*time.Second) + }() + cancel() + + var err error + select { + case err = <-done: + case <-time.After(2 * time.Second): + t.Fatal("sleepContext did not return after cancel") + } + if !errors.Is(err, context.Canceled) { + t.Fatalf("sleepContext canceled mid-wait = %v, want context.Canceled", err) + } +} + +func TestSleepContextCompletesWhenContextStaysOpen(t *testing.T) { + err := sleepContext(context.Background(), time.Millisecond) + if err != nil { + t.Fatalf("sleepContext(background, 1ms) = %v, want nil", err) + } +}