fix(ipc): make daemon dialing cancellable
- commit
- e48677f6b878e2171d07494e9c7b3a94b51d3db5
- author
- brice · · 2026-07-13 03:42 UTC
- parent
- 0a1ab39
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +143 -31
M internal/adapters/ipc/dial.go +6 -4
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package ipc | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "context" | |
| 4 | 5 | "fmt" | |
| 5 | 6 | "net" | |
| 6 | 7 | "path/filepath" | |
| @@ -8,13 +9,14 @@ import ( | |||
| 8 | 9 | "github.com/bnema/vev/internal/ports" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | - | // Dial connects to the daemon's socket in dir (dir/daemon.sock) and returns | |
| 12 | - | // a Transport speaking vev's framed protocol. A dial failure (no daemon | |
| 12 | + | // DialContext connects to the daemon's socket in dir (dir/daemon.sock) and | |
| 13 | + | // returns a Transport speaking vev's framed protocol. A dial failure (no daemon | |
| 13 | 14 | // listening, or a stale/absent socket) is reported as a wrapped error so | |
| 14 | 15 | // callers can decide whether to spawn a daemon and retry. | |
| 15 | - | func Dial(dir string, opts ...Option) (ports.Transport, error) { | |
| 16 | + | func DialContext(ctx context.Context, dir string, opts ...Option) (ports.Transport, error) { | |
| 16 | 17 | sockPath := filepath.Join(dir, socketFileName) | |
| 17 | - | conn, err := net.Dial("unix", sockPath) | |
| 18 | + | var dialer net.Dialer | |
| 19 | + | conn, err := dialer.DialContext(ctx, "unix", sockPath) | |
| 18 | 20 | if err != nil { | |
| 19 | 21 | return nil, fmt.Errorf("ipc: dial %s: %w", sockPath, err) | |
| 20 | 22 | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package ipc | 1 | package ipc | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "context" | |||
| 4 | "fmt" | 5 | "fmt" | ||
| 5 | "net" | 6 | "net" | ||
| 6 | "path/filepath" | 7 | "path/filepath" | ||
| @@ -8,13 +9,14 @@ import ( | |||||
| 8 | "github.com/bnema/vev/internal/ports" | 9 | "github.com/bnema/vev/internal/ports" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | - | // Dial connects to the daemon's socket in dir (dir/daemon.sock) and returns | 12 | + | // DialContext connects to the daemon's socket in dir (dir/daemon.sock) and |
| 12 | - | // a Transport speaking vev's framed protocol. A dial failure (no daemon | 13 | + | // returns a Transport speaking vev's framed protocol. A dial failure (no daemon |
| 13 | // listening, or a stale/absent socket) is reported as a wrapped error so | 14 | // listening, or a stale/absent socket) is reported as a wrapped error so | ||
| 14 | // callers can decide whether to spawn a daemon and retry. | 15 | // callers can decide whether to spawn a daemon and retry. | ||
| 15 | - | func Dial(dir string, opts ...Option) (ports.Transport, error) { | 16 | + | func DialContext(ctx context.Context, dir string, opts ...Option) (ports.Transport, error) { |
| 16 | sockPath := filepath.Join(dir, socketFileName) | 17 | sockPath := filepath.Join(dir, socketFileName) | ||
| 17 | - | conn, err := net.Dial("unix", sockPath) | 18 | + | var dialer net.Dialer |
| 19 | + | conn, err := dialer.DialContext(ctx, "unix", sockPath) | |||
| 18 | if err != nil { | 20 | if err != nil { | ||
| 19 | return nil, fmt.Errorf("ipc: dial %s: %w", sockPath, err) | 21 | return nil, fmt.Errorf("ipc: dial %s: %w", sockPath, err) | ||
| 20 | } | 22 | } | ||
A internal/adapters/ipc/dial_test.go +79 -0
| @@ -0,0 +1,79 @@ | |||
| 1 | + | package ipc | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "errors" | |
| 6 | + | "path/filepath" | |
| 7 | + | "testing" | |
| 8 | + | "time" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/vev/internal/ports" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | type dialTestObserver struct{} | |
| 14 | + | ||
| 15 | + | func (dialTestObserver) ObserveRuntime(ports.RuntimeMark) {} | |
| 16 | + | ||
| 17 | + | func TestDialContextCanceledOrExpired(t *testing.T) { | |
| 18 | + | tests := []struct { | |
| 19 | + | name string | |
| 20 | + | ctx func() (context.Context, context.CancelFunc) | |
| 21 | + | want error | |
| 22 | + | }{ | |
| 23 | + | { | |
| 24 | + | name: "canceled", | |
| 25 | + | ctx: func() (context.Context, context.CancelFunc) { | |
| 26 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 27 | + | cancel() | |
| 28 | + | return ctx, func() {} | |
| 29 | + | }, | |
| 30 | + | want: context.Canceled, | |
| 31 | + | }, | |
| 32 | + | { | |
| 33 | + | name: "deadline exceeded", | |
| 34 | + | ctx: func() (context.Context, context.CancelFunc) { | |
| 35 | + | return context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) | |
| 36 | + | }, | |
| 37 | + | want: context.DeadlineExceeded, | |
| 38 | + | }, | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | for _, tt := range tests { | |
| 42 | + | t.Run(tt.name, func(t *testing.T) { | |
| 43 | + | ctx, cancel := tt.ctx() | |
| 44 | + | defer cancel() | |
| 45 | + | ||
| 46 | + | transport, err := DialContext(ctx, t.TempDir()) | |
| 47 | + | if transport != nil { | |
| 48 | + | t.Fatal("DialContext returned a transport for a completed context") | |
| 49 | + | } | |
| 50 | + | if !errors.Is(err, tt.want) { | |
| 51 | + | t.Fatalf("DialContext error = %v, want wrapping %v", err, tt.want) | |
| 52 | + | } | |
| 53 | + | }) | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | func TestDialContextPreservesOptions(t *testing.T) { | |
| 58 | + | dir := filepath.Join(t.TempDir(), "vev") | |
| 59 | + | listener, err := Listen(dir) | |
| 60 | + | if err != nil { | |
| 61 | + | t.Fatalf("Listen: %v", err) | |
| 62 | + | } | |
| 63 | + | defer func() { _ = listener.Close() }() | |
| 64 | + | ||
| 65 | + | observer := dialTestObserver{} | |
| 66 | + | transport, err := DialContext(context.Background(), dir, WithRuntimeObserver(observer)) | |
| 67 | + | if err != nil { | |
| 68 | + | t.Fatalf("DialContext: %v", err) | |
| 69 | + | } | |
| 70 | + | defer func() { _ = transport.Close() }() | |
| 71 | + | ||
| 72 | + | got, ok := transport.(*unixTransport) | |
| 73 | + | if !ok { | |
| 74 | + | t.Fatalf("DialContext transport = %T, want *unixTransport", transport) | |
| 75 | + | } | |
| 76 | + | if got.observer != observer { | |
| 77 | + | t.Fatal("DialContext did not apply runtime observer option") | |
| 78 | + | } | |
| 79 | + | } | |
| @@ -0,0 +1,79 @@ | |||||
| 1 | + | package ipc | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "errors" | |||
| 6 | + | "path/filepath" | |||
| 7 | + | "testing" | |||
| 8 | + | "time" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/vev/internal/ports" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | type dialTestObserver struct{} | |||
| 14 | + | ||||
| 15 | + | func (dialTestObserver) ObserveRuntime(ports.RuntimeMark) {} | |||
| 16 | + | ||||
| 17 | + | func TestDialContextCanceledOrExpired(t *testing.T) { | |||
| 18 | + | tests := []struct { | |||
| 19 | + | name string | |||
| 20 | + | ctx func() (context.Context, context.CancelFunc) | |||
| 21 | + | want error | |||
| 22 | + | }{ | |||
| 23 | + | { | |||
| 24 | + | name: "canceled", | |||
| 25 | + | ctx: func() (context.Context, context.CancelFunc) { | |||
| 26 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 27 | + | cancel() | |||
| 28 | + | return ctx, func() {} | |||
| 29 | + | }, | |||
| 30 | + | want: context.Canceled, | |||
| 31 | + | }, | |||
| 32 | + | { | |||
| 33 | + | name: "deadline exceeded", | |||
| 34 | + | ctx: func() (context.Context, context.CancelFunc) { | |||
| 35 | + | return context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) | |||
| 36 | + | }, | |||
| 37 | + | want: context.DeadlineExceeded, | |||
| 38 | + | }, | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | for _, tt := range tests { | |||
| 42 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 43 | + | ctx, cancel := tt.ctx() | |||
| 44 | + | defer cancel() | |||
| 45 | + | ||||
| 46 | + | transport, err := DialContext(ctx, t.TempDir()) | |||
| 47 | + | if transport != nil { | |||
| 48 | + | t.Fatal("DialContext returned a transport for a completed context") | |||
| 49 | + | } | |||
| 50 | + | if !errors.Is(err, tt.want) { | |||
| 51 | + | t.Fatalf("DialContext error = %v, want wrapping %v", err, tt.want) | |||
| 52 | + | } | |||
| 53 | + | }) | |||
| 54 | + | } | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | func TestDialContextPreservesOptions(t *testing.T) { | |||
| 58 | + | dir := filepath.Join(t.TempDir(), "vev") | |||
| 59 | + | listener, err := Listen(dir) | |||
| 60 | + | if err != nil { | |||
| 61 | + | t.Fatalf("Listen: %v", err) | |||
| 62 | + | } | |||
| 63 | + | defer func() { _ = listener.Close() }() | |||
| 64 | + | ||||
| 65 | + | observer := dialTestObserver{} | |||
| 66 | + | transport, err := DialContext(context.Background(), dir, WithRuntimeObserver(observer)) | |||
| 67 | + | if err != nil { | |||
| 68 | + | t.Fatalf("DialContext: %v", err) | |||
| 69 | + | } | |||
| 70 | + | defer func() { _ = transport.Close() }() | |||
| 71 | + | ||||
| 72 | + | got, ok := transport.(*unixTransport) | |||
| 73 | + | if !ok { | |||
| 74 | + | t.Fatalf("DialContext transport = %T, want *unixTransport", transport) | |||
| 75 | + | } | |||
| 76 | + | if got.observer != observer { | |||
| 77 | + | t.Fatal("DialContext did not apply runtime observer option") | |||
| 78 | + | } | |||
| 79 | + | } | |||
M internal/app/daemon_integration_test.go +5 -5
| @@ -74,7 +74,7 @@ func recvPump(tr ports.Transport) *pump { | |||
| 74 | 74 | // attach dials, handshakes, and returns the transport plus its frame pump. | |
| 75 | 75 | func attach(t *testing.T, dir string, intent uint8, name string, sz domain.Size) (ports.Transport, *pump) { | |
| 76 | 76 | t.Helper() | |
| 77 | - | tr, err := ipc.Dial(dir) | |
| 77 | + | tr, err := ipc.DialContext(context.Background(), dir) | |
| 78 | 78 | require.NoError(t, err) | |
| 79 | 79 | hello := ports.Hello{Version: ports.ProtocolVersion, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color"} | |
| 80 | 80 | require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)})) | |
| @@ -91,7 +91,7 @@ func attach(t *testing.T, dir string, intent uint8, name string, sz domain.Size) | |||
| 91 | 91 | ||
| 92 | 92 | func listRemoteSessions(t *testing.T, dir string) ports.Sessions { | |
| 93 | 93 | t.Helper() | |
| 94 | - | tr, err := ipc.Dial(dir) | |
| 94 | + | tr, err := ipc.DialContext(context.Background(), dir) | |
| 95 | 95 | require.NoError(t, err) | |
| 96 | 96 | defer func() { _ = tr.Close() }() | |
| 97 | 97 | require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})})) | |
| @@ -104,7 +104,7 @@ func listRemoteSessions(t *testing.T, dir string) ports.Sessions { | |||
| 104 | 104 | } | |
| 105 | 105 | ||
| 106 | 106 | func killAll(dir string) error { | |
| 107 | - | tr, err := ipc.Dial(dir) | |
| 107 | + | tr, err := ipc.DialContext(context.Background(), dir) | |
| 108 | 108 | if err != nil { | |
| 109 | 109 | return err | |
| 110 | 110 | } | |
| @@ -248,7 +248,7 @@ func TestIntegration_CommandPaletteRenamesEphemeralSession(t *testing.T) { | |||
| 248 | 248 | text = awaitScreenText(t, p, sz, " work ") | |
| 249 | 249 | require.NotContains(t, text, "work*") | |
| 250 | 250 | ||
| 251 | - | listTr, err := ipc.Dial(dir) | |
| 251 | + | listTr, err := ipc.DialContext(context.Background(), dir) | |
| 252 | 252 | require.NoError(t, err) | |
| 253 | 253 | defer func() { _ = listTr.Close() }() | |
| 254 | 254 | require.NoError(t, listTr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})})) | |
| @@ -367,7 +367,7 @@ func TestIntegration_KillAllShutsDownDaemon(t *testing.T) { | |||
| 367 | 367 | tr2, _ := attach(t, dir, ports.IntentNew, "two", sz) | |
| 368 | 368 | require.NoError(t, tr2.Close()) | |
| 369 | 369 | ||
| 370 | - | killTr, err := ipc.Dial(dir) | |
| 370 | + | killTr, err := ipc.DialContext(context.Background(), dir) | |
| 371 | 371 | require.NoError(t, err) | |
| 372 | 372 | defer func() { _ = killTr.Close() }() | |
| 373 | 373 | require.NoError(t, killTr.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(ports.Kill{All: true})})) | |
| @@ -74,7 +74,7 @@ func recvPump(tr ports.Transport) *pump { | |||||
| 74 | // attach dials, handshakes, and returns the transport plus its frame pump. | 74 | // attach dials, handshakes, and returns the transport plus its frame pump. | ||
| 75 | func attach(t *testing.T, dir string, intent uint8, name string, sz domain.Size) (ports.Transport, *pump) { | 75 | func attach(t *testing.T, dir string, intent uint8, name string, sz domain.Size) (ports.Transport, *pump) { | ||
| 76 | t.Helper() | 76 | t.Helper() | ||
| 77 | - | tr, err := ipc.Dial(dir) | 77 | + | tr, err := ipc.DialContext(context.Background(), dir) |
| 78 | require.NoError(t, err) | 78 | require.NoError(t, err) | ||
| 79 | hello := ports.Hello{Version: ports.ProtocolVersion, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color"} | 79 | hello := ports.Hello{Version: ports.ProtocolVersion, Intent: intent, Name: name, Size: sz, TermEnv: "xterm-256color"} | ||
| 80 | require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)})) | 80 | require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)})) | ||
| @@ -91,7 +91,7 @@ func attach(t *testing.T, dir string, intent uint8, name string, sz domain.Size) | |||||
| 91 | 91 | ||||
| 92 | func listRemoteSessions(t *testing.T, dir string) ports.Sessions { | 92 | func listRemoteSessions(t *testing.T, dir string) ports.Sessions { | ||
| 93 | t.Helper() | 93 | t.Helper() | ||
| 94 | - | tr, err := ipc.Dial(dir) | 94 | + | tr, err := ipc.DialContext(context.Background(), dir) |
| 95 | require.NoError(t, err) | 95 | require.NoError(t, err) | ||
| 96 | defer func() { _ = tr.Close() }() | 96 | defer func() { _ = tr.Close() }() | ||
| 97 | require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})})) | 97 | require.NoError(t, tr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})})) | ||
| @@ -104,7 +104,7 @@ func listRemoteSessions(t *testing.T, dir string) ports.Sessions { | |||||
| 104 | } | 104 | } | ||
| 105 | 105 | ||||
| 106 | func killAll(dir string) error { | 106 | func killAll(dir string) error { | ||
| 107 | - | tr, err := ipc.Dial(dir) | 107 | + | tr, err := ipc.DialContext(context.Background(), dir) |
| 108 | if err != nil { | 108 | if err != nil { | ||
| 109 | return err | 109 | return err | ||
| 110 | } | 110 | } | ||
| @@ -248,7 +248,7 @@ func TestIntegration_CommandPaletteRenamesEphemeralSession(t *testing.T) { | |||||
| 248 | text = awaitScreenText(t, p, sz, " work ") | 248 | text = awaitScreenText(t, p, sz, " work ") | ||
| 249 | require.NotContains(t, text, "work*") | 249 | require.NotContains(t, text, "work*") | ||
| 250 | 250 | ||||
| 251 | - | listTr, err := ipc.Dial(dir) | 251 | + | listTr, err := ipc.DialContext(context.Background(), dir) |
| 252 | require.NoError(t, err) | 252 | require.NoError(t, err) | ||
| 253 | defer func() { _ = listTr.Close() }() | 253 | defer func() { _ = listTr.Close() }() | ||
| 254 | require.NoError(t, listTr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})})) | 254 | require.NoError(t, listTr.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})})) | ||
| @@ -367,7 +367,7 @@ func TestIntegration_KillAllShutsDownDaemon(t *testing.T) { | |||||
| 367 | tr2, _ := attach(t, dir, ports.IntentNew, "two", sz) | 367 | tr2, _ := attach(t, dir, ports.IntentNew, "two", sz) | ||
| 368 | require.NoError(t, tr2.Close()) | 368 | require.NoError(t, tr2.Close()) | ||
| 369 | 369 | ||||
| 370 | - | killTr, err := ipc.Dial(dir) | 370 | + | killTr, err := ipc.DialContext(context.Background(), dir) |
| 371 | require.NoError(t, err) | 371 | require.NoError(t, err) | ||
| 372 | defer func() { _ = killTr.Close() }() | 372 | defer func() { _ = killTr.Close() }() | ||
| 373 | require.NoError(t, killTr.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(ports.Kill{All: true})})) | 373 | require.NoError(t, killTr.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(ports.Kill{All: true})})) | ||
M internal/app/run.go +11 -11
| @@ -565,8 +565,8 @@ type localDaemonDialer struct { | |||
| 565 | 565 | func (d localDaemonDialer) Dial(ctx context.Context) (ports.Transport, error) { | |
| 566 | 566 | dial := realDial | |
| 567 | 567 | if d.observer != nil { | |
| 568 | - | dial = func(dir string) (ports.Transport, error) { | |
| 569 | - | return ipc.Dial(dir, ipc.WithRuntimeObserver(d.observer)) | |
| 568 | + | dial = func(ctx context.Context, dir string) (ports.Transport, error) { | |
| 569 | + | return ipc.DialContext(ctx, dir, ipc.WithRuntimeObserver(d.observer)) | |
| 570 | 570 | } | |
| 571 | 571 | } | |
| 572 | 572 | return ensureDaemon(ctx, d.dir, dial, realSpawn, defaultBackoff) | |
| @@ -651,7 +651,7 @@ func requestDaemonStop(ctx context.Context) error { | |||
| 651 | 651 | ||
| 652 | 652 | done := make(chan error, 1) | |
| 653 | 653 | go func() { | |
| 654 | - | transport, err := realDial(ipc.SocketDir()) | |
| 654 | + | transport, err := realDial(ctx, ipc.SocketDir()) | |
| 655 | 655 | if err != nil { | |
| 656 | 656 | done <- fmt.Errorf("vev: no daemon running") | |
| 657 | 657 | return | |
| @@ -704,8 +704,8 @@ func runStdio(ctx context.Context) error { | |||
| 704 | 704 | if observerCloser != nil { | |
| 705 | 705 | defer func() { _ = observerCloser.Close() }() | |
| 706 | 706 | } | |
| 707 | - | transport, err := ensureDaemon(ctx, ipc.SocketDir(), func(dir string) (ports.Transport, error) { | |
| 708 | - | return ipc.Dial(dir, ipc.WithRuntimeObserver(observer)) | |
| 707 | + | transport, err := ensureDaemon(ctx, ipc.SocketDir(), func(ctx context.Context, dir string) (ports.Transport, error) { | |
| 708 | + | return ipc.DialContext(ctx, dir, ipc.WithRuntimeObserver(observer)) | |
| 709 | 709 | }, realSpawn, defaultBackoff) | |
| 710 | 710 | if err != nil { | |
| 711 | 711 | return err | |
| @@ -823,8 +823,8 @@ func runUDPProxy(ctx context.Context, session string, ready io.Writer) error { | |||
| 823 | 823 | if _, err := rand.Read(key); err != nil { | |
| 824 | 824 | return err | |
| 825 | 825 | } | |
| 826 | - | daemonTr, err := ensureDaemon(ctx, ipc.SocketDir(), func(dir string) (ports.Transport, error) { | |
| 827 | - | return ipc.Dial(dir, ipc.WithRuntimeObserver(observer)) | |
| 826 | + | daemonTr, err := ensureDaemon(ctx, ipc.SocketDir(), func(ctx context.Context, dir string) (ports.Transport, error) { | |
| 827 | + | return ipc.DialContext(ctx, dir, ipc.WithRuntimeObserver(observer)) | |
| 828 | 828 | }, realSpawn, defaultBackoff) | |
| 829 | 829 | if err != nil { | |
| 830 | 830 | return err | |
| @@ -975,8 +975,8 @@ func proxyTransports(ctx context.Context, a, b ports.Transport, log *slog.Logger | |||
| 975 | 975 | ||
| 976 | 976 | // runList prints the daemon's session listing. With no daemon running, it | |
| 977 | 977 | // falls back to the persisted stopped-session records. | |
| 978 | - | func runList(_ context.Context) error { | |
| 979 | - | transport, err := realDial(ipc.SocketDir()) | |
| 978 | + | func runList(ctx context.Context) error { | |
| 979 | + | transport, err := realDial(ctx, ipc.SocketDir()) | |
| 980 | 980 | if err != nil { | |
| 981 | 981 | records, loadErr := persist.LoadReadOnly(platform.StateDir()) | |
| 982 | 982 | if loadErr != nil { | |
| @@ -1040,8 +1040,8 @@ func printSessions(w io.Writer, sessions []ports.SessionInfo) { | |||
| 1040 | 1040 | } | |
| 1041 | 1041 | ||
| 1042 | 1042 | // runKill asks the daemon to terminate a named session, every session, or the daemon. | |
| 1043 | - | func runKill(_ context.Context, name string, all, daemon bool) error { | |
| 1044 | - | transport, err := realDial(ipc.SocketDir()) | |
| 1043 | + | func runKill(ctx context.Context, name string, all, daemon bool) error { | |
| 1044 | + | transport, err := realDial(ctx, ipc.SocketDir()) | |
| 1045 | 1045 | if err != nil { | |
| 1046 | 1046 | if name != "" && !all && !daemon { | |
| 1047 | 1047 | storePath := persist.StorePath(platform.StateDir()) | |
| @@ -565,8 +565,8 @@ type localDaemonDialer struct { | |||||
| 565 | func (d localDaemonDialer) Dial(ctx context.Context) (ports.Transport, error) { | 565 | func (d localDaemonDialer) Dial(ctx context.Context) (ports.Transport, error) { | ||
| 566 | dial := realDial | 566 | dial := realDial | ||
| 567 | if d.observer != nil { | 567 | if d.observer != nil { | ||
| 568 | - | dial = func(dir string) (ports.Transport, error) { | 568 | + | dial = func(ctx context.Context, dir string) (ports.Transport, error) { |
| 569 | - | return ipc.Dial(dir, ipc.WithRuntimeObserver(d.observer)) | 569 | + | return ipc.DialContext(ctx, dir, ipc.WithRuntimeObserver(d.observer)) |
| 570 | } | 570 | } | ||
| 571 | } | 571 | } | ||
| 572 | return ensureDaemon(ctx, d.dir, dial, realSpawn, defaultBackoff) | 572 | return ensureDaemon(ctx, d.dir, dial, realSpawn, defaultBackoff) | ||
| @@ -651,7 +651,7 @@ func requestDaemonStop(ctx context.Context) error { | |||||
| 651 | 651 | ||||
| 652 | done := make(chan error, 1) | 652 | done := make(chan error, 1) | ||
| 653 | go func() { | 653 | go func() { | ||
| 654 | - | transport, err := realDial(ipc.SocketDir()) | 654 | + | transport, err := realDial(ctx, ipc.SocketDir()) |
| 655 | if err != nil { | 655 | if err != nil { | ||
| 656 | done <- fmt.Errorf("vev: no daemon running") | 656 | done <- fmt.Errorf("vev: no daemon running") | ||
| 657 | return | 657 | return | ||
| @@ -704,8 +704,8 @@ func runStdio(ctx context.Context) error { | |||||
| 704 | if observerCloser != nil { | 704 | if observerCloser != nil { | ||
| 705 | defer func() { _ = observerCloser.Close() }() | 705 | defer func() { _ = observerCloser.Close() }() | ||
| 706 | } | 706 | } | ||
| 707 | - | transport, err := ensureDaemon(ctx, ipc.SocketDir(), func(dir string) (ports.Transport, error) { | 707 | + | transport, err := ensureDaemon(ctx, ipc.SocketDir(), func(ctx context.Context, dir string) (ports.Transport, error) { |
| 708 | - | return ipc.Dial(dir, ipc.WithRuntimeObserver(observer)) | 708 | + | return ipc.DialContext(ctx, dir, ipc.WithRuntimeObserver(observer)) |
| 709 | }, realSpawn, defaultBackoff) | 709 | }, realSpawn, defaultBackoff) | ||
| 710 | if err != nil { | 710 | if err != nil { | ||
| 711 | return err | 711 | return err | ||
| @@ -823,8 +823,8 @@ func runUDPProxy(ctx context.Context, session string, ready io.Writer) error { | |||||
| 823 | if _, err := rand.Read(key); err != nil { | 823 | if _, err := rand.Read(key); err != nil { | ||
| 824 | return err | 824 | return err | ||
| 825 | } | 825 | } | ||
| 826 | - | daemonTr, err := ensureDaemon(ctx, ipc.SocketDir(), func(dir string) (ports.Transport, error) { | 826 | + | daemonTr, err := ensureDaemon(ctx, ipc.SocketDir(), func(ctx context.Context, dir string) (ports.Transport, error) { |
| 827 | - | return ipc.Dial(dir, ipc.WithRuntimeObserver(observer)) | 827 | + | return ipc.DialContext(ctx, dir, ipc.WithRuntimeObserver(observer)) |
| 828 | }, realSpawn, defaultBackoff) | 828 | }, realSpawn, defaultBackoff) | ||
| 829 | if err != nil { | 829 | if err != nil { | ||
| 830 | return err | 830 | return err | ||
| @@ -975,8 +975,8 @@ func proxyTransports(ctx context.Context, a, b ports.Transport, log *slog.Logger | |||||
| 975 | 975 | ||||
| 976 | // runList prints the daemon's session listing. With no daemon running, it | 976 | // runList prints the daemon's session listing. With no daemon running, it | ||
| 977 | // falls back to the persisted stopped-session records. | 977 | // falls back to the persisted stopped-session records. | ||
| 978 | - | func runList(_ context.Context) error { | 978 | + | func runList(ctx context.Context) error { |
| 979 | - | transport, err := realDial(ipc.SocketDir()) | 979 | + | transport, err := realDial(ctx, ipc.SocketDir()) |
| 980 | if err != nil { | 980 | if err != nil { | ||
| 981 | records, loadErr := persist.LoadReadOnly(platform.StateDir()) | 981 | records, loadErr := persist.LoadReadOnly(platform.StateDir()) | ||
| 982 | if loadErr != nil { | 982 | if loadErr != nil { | ||
| @@ -1040,8 +1040,8 @@ func printSessions(w io.Writer, sessions []ports.SessionInfo) { | |||||
| 1040 | } | 1040 | } | ||
| 1041 | 1041 | ||||
| 1042 | // runKill asks the daemon to terminate a named session, every session, or the daemon. | 1042 | // runKill asks the daemon to terminate a named session, every session, or the daemon. | ||
| 1043 | - | func runKill(_ context.Context, name string, all, daemon bool) error { | 1043 | + | func runKill(ctx context.Context, name string, all, daemon bool) error { |
| 1044 | - | transport, err := realDial(ipc.SocketDir()) | 1044 | + | transport, err := realDial(ctx, ipc.SocketDir()) |
| 1045 | if err != nil { | 1045 | if err != nil { | ||
| 1046 | if name != "" && !all && !daemon { | 1046 | if name != "" && !all && !daemon { | ||
| 1047 | storePath := persist.StorePath(platform.StateDir()) | 1047 | storePath := persist.StorePath(platform.StateDir()) | ||
M internal/app/spawn.go +11 -5
| @@ -32,7 +32,7 @@ const staleLockAge = 10 * time.Second | |||
| 32 | 32 | ||
| 33 | 33 | // dialFunc dials the daemon socket in dir. Injected so tests can drive the | |
| 34 | 34 | // spawn/backoff logic without a real socket. | |
| 35 | - | type dialFunc func(dir string) (ports.Transport, error) | |
| 35 | + | type dialFunc func(ctx context.Context, dir string) (ports.Transport, error) | |
| 36 | 36 | ||
| 37 | 37 | // spawnFunc launches a detached daemon process. Injected so tests never | |
| 38 | 38 | // re-exec a real binary. | |
| @@ -58,7 +58,10 @@ var defaultBackoff = backoffConfig{ | |||
| 58 | 58 | // spawner via an mkdir lock (losers simply wait), then retry-dials with | |
| 59 | 59 | // backoff until the socket is live or the budget expires. | |
| 60 | 60 | func ensureDaemon(ctx context.Context, dir string, dial dialFunc, spawn spawnFunc, cfg backoffConfig) (ports.Transport, error) { | |
| 61 | - | if t, err := dial(dir); err == nil { | |
| 61 | + | if err := ctx.Err(); err != nil { | |
| 62 | + | return nil, err | |
| 63 | + | } | |
| 64 | + | if t, err := dial(ctx, dir); err == nil { | |
| 62 | 65 | slog.Debug("daemon already reachable", "socket_dir", dir) | |
| 63 | 66 | return t, nil | |
| 64 | 67 | } | |
| @@ -120,7 +123,10 @@ func retryDial(ctx context.Context, dir string, dial dialFunc, cfg backoffConfig | |||
| 120 | 123 | deadline := time.Now().Add(cfg.total) | |
| 121 | 124 | backoff := cfg.initial | |
| 122 | 125 | for { | |
| 123 | - | if t, err := dial(dir); err == nil { | |
| 126 | + | if err := ctx.Err(); err != nil { | |
| 127 | + | return nil, err | |
| 128 | + | } | |
| 129 | + | if t, err := dial(ctx, dir); err == nil { | |
| 124 | 130 | return t, nil | |
| 125 | 131 | } | |
| 126 | 132 | if !time.Now().Before(deadline) { | |
| @@ -139,8 +145,8 @@ func retryDial(ctx context.Context, dir string, dial dialFunc, cfg backoffConfig | |||
| 139 | 145 | } | |
| 140 | 146 | ||
| 141 | 147 | // realDial is the production dialer. | |
| 142 | - | func realDial(dir string) (ports.Transport, error) { | |
| 143 | - | return ipc.Dial(dir) | |
| 148 | + | func realDial(ctx context.Context, dir string) (ports.Transport, error) { | |
| 149 | + | return ipc.DialContext(ctx, dir) | |
| 144 | 150 | } | |
| 145 | 151 | ||
| 146 | 152 | // realSpawn re-execs this binary as a detached daemon: a new session | |
| @@ -32,7 +32,7 @@ const staleLockAge = 10 * time.Second | |||||
| 32 | 32 | ||||
| 33 | // dialFunc dials the daemon socket in dir. Injected so tests can drive the | 33 | // dialFunc dials the daemon socket in dir. Injected so tests can drive the | ||
| 34 | // spawn/backoff logic without a real socket. | 34 | // spawn/backoff logic without a real socket. | ||
| 35 | - | type dialFunc func(dir string) (ports.Transport, error) | 35 | + | type dialFunc func(ctx context.Context, dir string) (ports.Transport, error) |
| 36 | 36 | ||||
| 37 | // spawnFunc launches a detached daemon process. Injected so tests never | 37 | // spawnFunc launches a detached daemon process. Injected so tests never | ||
| 38 | // re-exec a real binary. | 38 | // re-exec a real binary. | ||
| @@ -58,7 +58,10 @@ var defaultBackoff = backoffConfig{ | |||||
| 58 | // spawner via an mkdir lock (losers simply wait), then retry-dials with | 58 | // spawner via an mkdir lock (losers simply wait), then retry-dials with | ||
| 59 | // backoff until the socket is live or the budget expires. | 59 | // backoff until the socket is live or the budget expires. | ||
| 60 | func ensureDaemon(ctx context.Context, dir string, dial dialFunc, spawn spawnFunc, cfg backoffConfig) (ports.Transport, error) { | 60 | func ensureDaemon(ctx context.Context, dir string, dial dialFunc, spawn spawnFunc, cfg backoffConfig) (ports.Transport, error) { | ||
| 61 | - | if t, err := dial(dir); err == nil { | 61 | + | if err := ctx.Err(); err != nil { |
| 62 | + | return nil, err | |||
| 63 | + | } | |||
| 64 | + | if t, err := dial(ctx, dir); err == nil { | |||
| 62 | slog.Debug("daemon already reachable", "socket_dir", dir) | 65 | slog.Debug("daemon already reachable", "socket_dir", dir) | ||
| 63 | return t, nil | 66 | return t, nil | ||
| 64 | } | 67 | } | ||
| @@ -120,7 +123,10 @@ func retryDial(ctx context.Context, dir string, dial dialFunc, cfg backoffConfig | |||||
| 120 | deadline := time.Now().Add(cfg.total) | 123 | deadline := time.Now().Add(cfg.total) | ||
| 121 | backoff := cfg.initial | 124 | backoff := cfg.initial | ||
| 122 | for { | 125 | for { | ||
| 123 | - | if t, err := dial(dir); err == nil { | 126 | + | if err := ctx.Err(); err != nil { |
| 127 | + | return nil, err | |||
| 128 | + | } | |||
| 129 | + | if t, err := dial(ctx, dir); err == nil { | |||
| 124 | return t, nil | 130 | return t, nil | ||
| 125 | } | 131 | } | ||
| 126 | if !time.Now().Before(deadline) { | 132 | if !time.Now().Before(deadline) { | ||
| @@ -139,8 +145,8 @@ func retryDial(ctx context.Context, dir string, dial dialFunc, cfg backoffConfig | |||||
| 139 | } | 145 | } | ||
| 140 | 146 | ||||
| 141 | // realDial is the production dialer. | 147 | // realDial is the production dialer. | ||
| 142 | - | func realDial(dir string) (ports.Transport, error) { | 148 | + | func realDial(ctx context.Context, dir string) (ports.Transport, error) { |
| 143 | - | return ipc.Dial(dir) | 149 | + | return ipc.DialContext(ctx, dir) |
| 144 | } | 150 | } | ||
| 145 | 151 | ||||
| 146 | // realSpawn re-execs this binary as a detached daemon: a new session | 152 | // realSpawn re-execs this binary as a detached daemon: a new session | ||
M internal/app/spawn_test.go +31 -6
| @@ -91,7 +91,7 @@ func TestAcquireSpawnLockTakesOverStaleLock(t *testing.T) { | |||
| 91 | 91 | func TestRetryDialSucceedsAfterTransientFailures(t *testing.T) { | |
| 92 | 92 | want := portsmocks.NewMockTransport(t) | |
| 93 | 93 | var calls atomic.Int32 | |
| 94 | - | dial := func(string) (ports.Transport, error) { | |
| 94 | + | dial := func(context.Context, string) (ports.Transport, error) { | |
| 95 | 95 | if calls.Add(1) <= 3 { | |
| 96 | 96 | return nil, errDialFailed | |
| 97 | 97 | } | |
| @@ -111,7 +111,7 @@ func TestRetryDialSucceedsAfterTransientFailures(t *testing.T) { | |||
| 111 | 111 | } | |
| 112 | 112 | ||
| 113 | 113 | func TestRetryDialPermanentFailure(t *testing.T) { | |
| 114 | - | dial := func(string) (ports.Transport, error) { return nil, errDialFailed } | |
| 114 | + | dial := func(context.Context, string) (ports.Transport, error) { return nil, errDialFailed } | |
| 115 | 115 | _, err := retryDial(context.Background(), t.TempDir(), dial, fastBackoff) | |
| 116 | 116 | if !errors.Is(err, ErrDaemonUnreachable) { | |
| 117 | 117 | t.Fatalf("retryDial error = %v, want ErrDaemonUnreachable", err) | |
| @@ -121,18 +121,43 @@ func TestRetryDialPermanentFailure(t *testing.T) { | |||
| 121 | 121 | func TestRetryDialContextCancelled(t *testing.T) { | |
| 122 | 122 | ctx, cancel := context.WithCancel(context.Background()) | |
| 123 | 123 | cancel() | |
| 124 | - | dial := func(string) (ports.Transport, error) { return nil, errDialFailed } | |
| 124 | + | var calls atomic.Int32 | |
| 125 | + | dial := func(context.Context, string) (ports.Transport, error) { | |
| 126 | + | calls.Add(1) | |
| 127 | + | return nil, errDialFailed | |
| 128 | + | } | |
| 125 | 129 | _, err := retryDial(ctx, t.TempDir(), dial, fastBackoff) | |
| 126 | 130 | if !errors.Is(err, context.Canceled) { | |
| 127 | 131 | t.Fatalf("retryDial error = %v, want context.Canceled", err) | |
| 128 | 132 | } | |
| 133 | + | if calls.Load() != 0 { | |
| 134 | + | t.Fatalf("dial called %d times for canceled context, want 0", calls.Load()) | |
| 135 | + | } | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | func TestEnsureDaemonContextCancelledDoesNotDialOrSpawn(t *testing.T) { | |
| 139 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 140 | + | cancel() | |
| 141 | + | dial := func(context.Context, string) (ports.Transport, error) { | |
| 142 | + | t.Fatal("ensureDaemon dialed with a canceled context") | |
| 143 | + | return nil, nil | |
| 144 | + | } | |
| 145 | + | spawn := func() error { | |
| 146 | + | t.Fatal("ensureDaemon spawned with a canceled context") | |
| 147 | + | return nil | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | _, err := ensureDaemon(ctx, t.TempDir(), dial, spawn, fastBackoff) | |
| 151 | + | if !errors.Is(err, context.Canceled) { | |
| 152 | + | t.Fatalf("ensureDaemon error = %v, want context.Canceled", err) | |
| 153 | + | } | |
| 129 | 154 | } | |
| 130 | 155 | ||
| 131 | 156 | func TestEnsureDaemonSpawnsThenDials(t *testing.T) { | |
| 132 | 157 | dir := filepath.Join(t.TempDir(), "vev") | |
| 133 | 158 | want := portsmocks.NewMockTransport(t) | |
| 134 | 159 | var dialCalls, spawnCalls atomic.Int32 | |
| 135 | - | dial := func(string) (ports.Transport, error) { | |
| 160 | + | dial := func(context.Context, string) (ports.Transport, error) { | |
| 136 | 161 | // First (pre-spawn) dial fails; the post-spawn retry succeeds. | |
| 137 | 162 | if dialCalls.Add(1) == 1 { | |
| 138 | 163 | return nil, errDialFailed | |
| @@ -163,7 +188,7 @@ func TestEnsureDaemonSpawnsThenDials(t *testing.T) { | |||
| 163 | 188 | func TestEnsureDaemonReturnsExistingDaemon(t *testing.T) { | |
| 164 | 189 | want := portsmocks.NewMockTransport(t) | |
| 165 | 190 | var spawnCalls atomic.Int32 | |
| 166 | - | dial := func(string) (ports.Transport, error) { return want, nil } | |
| 191 | + | dial := func(context.Context, string) (ports.Transport, error) { return want, nil } | |
| 167 | 192 | spawn := func() error { spawnCalls.Add(1); return nil } | |
| 168 | 193 | ||
| 169 | 194 | got, err := ensureDaemon(context.Background(), t.TempDir(), dial, spawn, fastBackoff) | |
| @@ -180,7 +205,7 @@ func TestEnsureDaemonReturnsExistingDaemon(t *testing.T) { | |||
| 180 | 205 | ||
| 181 | 206 | func TestEnsureDaemonSpawnFailure(t *testing.T) { | |
| 182 | 207 | spawnErr := errors.New("exec boom") | |
| 183 | - | dial := func(string) (ports.Transport, error) { return nil, errDialFailed } | |
| 208 | + | dial := func(context.Context, string) (ports.Transport, error) { return nil, errDialFailed } | |
| 184 | 209 | spawn := func() error { return spawnErr } | |
| 185 | 210 | ||
| 186 | 211 | _, err := ensureDaemon(context.Background(), filepath.Join(t.TempDir(), "vev"), dial, spawn, fastBackoff) | |
| @@ -91,7 +91,7 @@ func TestAcquireSpawnLockTakesOverStaleLock(t *testing.T) { | |||||
| 91 | func TestRetryDialSucceedsAfterTransientFailures(t *testing.T) { | 91 | func TestRetryDialSucceedsAfterTransientFailures(t *testing.T) { | ||
| 92 | want := portsmocks.NewMockTransport(t) | 92 | want := portsmocks.NewMockTransport(t) | ||
| 93 | var calls atomic.Int32 | 93 | var calls atomic.Int32 | ||
| 94 | - | dial := func(string) (ports.Transport, error) { | 94 | + | dial := func(context.Context, string) (ports.Transport, error) { |
| 95 | if calls.Add(1) <= 3 { | 95 | if calls.Add(1) <= 3 { | ||
| 96 | return nil, errDialFailed | 96 | return nil, errDialFailed | ||
| 97 | } | 97 | } | ||
| @@ -111,7 +111,7 @@ func TestRetryDialSucceedsAfterTransientFailures(t *testing.T) { | |||||
| 111 | } | 111 | } | ||
| 112 | 112 | ||||
| 113 | func TestRetryDialPermanentFailure(t *testing.T) { | 113 | func TestRetryDialPermanentFailure(t *testing.T) { | ||
| 114 | - | dial := func(string) (ports.Transport, error) { return nil, errDialFailed } | 114 | + | dial := func(context.Context, string) (ports.Transport, error) { return nil, errDialFailed } |
| 115 | _, err := retryDial(context.Background(), t.TempDir(), dial, fastBackoff) | 115 | _, err := retryDial(context.Background(), t.TempDir(), dial, fastBackoff) | ||
| 116 | if !errors.Is(err, ErrDaemonUnreachable) { | 116 | if !errors.Is(err, ErrDaemonUnreachable) { | ||
| 117 | t.Fatalf("retryDial error = %v, want ErrDaemonUnreachable", err) | 117 | t.Fatalf("retryDial error = %v, want ErrDaemonUnreachable", err) | ||
| @@ -121,18 +121,43 @@ func TestRetryDialPermanentFailure(t *testing.T) { | |||||
| 121 | func TestRetryDialContextCancelled(t *testing.T) { | 121 | func TestRetryDialContextCancelled(t *testing.T) { | ||
| 122 | ctx, cancel := context.WithCancel(context.Background()) | 122 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 123 | cancel() | 123 | cancel() | ||
| 124 | - | dial := func(string) (ports.Transport, error) { return nil, errDialFailed } | 124 | + | var calls atomic.Int32 |
| 125 | + | dial := func(context.Context, string) (ports.Transport, error) { | |||
| 126 | + | calls.Add(1) | |||
| 127 | + | return nil, errDialFailed | |||
| 128 | + | } | |||
| 125 | _, err := retryDial(ctx, t.TempDir(), dial, fastBackoff) | 129 | _, err := retryDial(ctx, t.TempDir(), dial, fastBackoff) | ||
| 126 | if !errors.Is(err, context.Canceled) { | 130 | if !errors.Is(err, context.Canceled) { | ||
| 127 | t.Fatalf("retryDial error = %v, want context.Canceled", err) | 131 | t.Fatalf("retryDial error = %v, want context.Canceled", err) | ||
| 128 | } | 132 | } | ||
| 133 | + | if calls.Load() != 0 { | |||
| 134 | + | t.Fatalf("dial called %d times for canceled context, want 0", calls.Load()) | |||
| 135 | + | } | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | func TestEnsureDaemonContextCancelledDoesNotDialOrSpawn(t *testing.T) { | |||
| 139 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 140 | + | cancel() | |||
| 141 | + | dial := func(context.Context, string) (ports.Transport, error) { | |||
| 142 | + | t.Fatal("ensureDaemon dialed with a canceled context") | |||
| 143 | + | return nil, nil | |||
| 144 | + | } | |||
| 145 | + | spawn := func() error { | |||
| 146 | + | t.Fatal("ensureDaemon spawned with a canceled context") | |||
| 147 | + | return nil | |||
| 148 | + | } | |||
| 149 | + | ||||
| 150 | + | _, err := ensureDaemon(ctx, t.TempDir(), dial, spawn, fastBackoff) | |||
| 151 | + | if !errors.Is(err, context.Canceled) { | |||
| 152 | + | t.Fatalf("ensureDaemon error = %v, want context.Canceled", err) | |||
| 153 | + | } | |||
| 129 | } | 154 | } | ||
| 130 | 155 | ||||
| 131 | func TestEnsureDaemonSpawnsThenDials(t *testing.T) { | 156 | func TestEnsureDaemonSpawnsThenDials(t *testing.T) { | ||
| 132 | dir := filepath.Join(t.TempDir(), "vev") | 157 | dir := filepath.Join(t.TempDir(), "vev") | ||
| 133 | want := portsmocks.NewMockTransport(t) | 158 | want := portsmocks.NewMockTransport(t) | ||
| 134 | var dialCalls, spawnCalls atomic.Int32 | 159 | var dialCalls, spawnCalls atomic.Int32 | ||
| 135 | - | dial := func(string) (ports.Transport, error) { | 160 | + | dial := func(context.Context, string) (ports.Transport, error) { |
| 136 | // First (pre-spawn) dial fails; the post-spawn retry succeeds. | 161 | // First (pre-spawn) dial fails; the post-spawn retry succeeds. | ||
| 137 | if dialCalls.Add(1) == 1 { | 162 | if dialCalls.Add(1) == 1 { | ||
| 138 | return nil, errDialFailed | 163 | return nil, errDialFailed | ||
| @@ -163,7 +188,7 @@ func TestEnsureDaemonSpawnsThenDials(t *testing.T) { | |||||
| 163 | func TestEnsureDaemonReturnsExistingDaemon(t *testing.T) { | 188 | func TestEnsureDaemonReturnsExistingDaemon(t *testing.T) { | ||
| 164 | want := portsmocks.NewMockTransport(t) | 189 | want := portsmocks.NewMockTransport(t) | ||
| 165 | var spawnCalls atomic.Int32 | 190 | var spawnCalls atomic.Int32 | ||
| 166 | - | dial := func(string) (ports.Transport, error) { return want, nil } | 191 | + | dial := func(context.Context, string) (ports.Transport, error) { return want, nil } |
| 167 | spawn := func() error { spawnCalls.Add(1); return nil } | 192 | spawn := func() error { spawnCalls.Add(1); return nil } | ||
| 168 | 193 | ||||
| 169 | got, err := ensureDaemon(context.Background(), t.TempDir(), dial, spawn, fastBackoff) | 194 | got, err := ensureDaemon(context.Background(), t.TempDir(), dial, spawn, fastBackoff) | ||
| @@ -180,7 +205,7 @@ func TestEnsureDaemonReturnsExistingDaemon(t *testing.T) { | |||||
| 180 | 205 | ||||
| 181 | func TestEnsureDaemonSpawnFailure(t *testing.T) { | 206 | func TestEnsureDaemonSpawnFailure(t *testing.T) { | ||
| 182 | spawnErr := errors.New("exec boom") | 207 | spawnErr := errors.New("exec boom") | ||
| 183 | - | dial := func(string) (ports.Transport, error) { return nil, errDialFailed } | 208 | + | dial := func(context.Context, string) (ports.Transport, error) { return nil, errDialFailed } |
| 184 | spawn := func() error { return spawnErr } | 209 | spawn := func() error { return spawnErr } | ||
| 185 | 210 | ||||
| 186 | _, err := ensureDaemon(context.Background(), filepath.Join(t.TempDir(), "vev"), dial, spawn, fastBackoff) | 211 | _, err := ensureDaemon(context.Background(), filepath.Join(t.TempDir(), "vev"), dial, spawn, fastBackoff) | ||