a request context on the ipc server, cancelled by Close (V-638)
serveConn dispatched under context.Background(), so a dispatch in flight during shutdown could not be told to stop and closeGrace could only abandon it. The server now carries a context, Close cancels it, and each conn derives its own so nothing outlives the connection. A zero-value Server built outside Listen falls back to Background; two wiring tests do that. Client.Close read c.conn with no lock while roundtrip re-dialed and dropped it, which -race caught on the new test. The conn field now has its own mutex, held only across a read or an assignment, so Close and the watchdog reach the connection without queueing behind the call they are interrupting.
This commit is contained in:
@@ -117,3 +117,54 @@ func TestClientDeadlineBoundsACall(t *testing.T) {
|
||||
t.Errorf("Ping took %v, want the context deadline to bound it", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
// blockingAPI parks Presence until its context is cancelled and records what
|
||||
// cancelled it. Every other method is the unimplemented floor.
|
||||
type blockingAPI struct {
|
||||
UnimplementedCoreAPI
|
||||
entered chan struct{}
|
||||
err chan error
|
||||
}
|
||||
|
||||
func (b *blockingAPI) Presence(ctx context.Context) (Presence, error) {
|
||||
close(b.entered)
|
||||
<-ctx.Done()
|
||||
b.err <- ctx.Err()
|
||||
return Presence{}, ctx.Err()
|
||||
}
|
||||
|
||||
// serveConn dispatched under context.Background() until V-638, so Close could
|
||||
// only abandon a dispatch in flight and never tell it to stop.
|
||||
func TestServerCloseCancelsADispatchInFlight(t *testing.T) {
|
||||
api := &blockingAPI{entered: make(chan struct{}), err: make(chan error, 1)}
|
||||
srv, err := Listen(filepath.Join(t.TempDir(), "core.sock"), api)
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
served := make(chan struct{})
|
||||
go func() { _ = srv.Serve(); close(served) }()
|
||||
|
||||
cli, err := Dial(srv.Path())
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
defer cli.Close()
|
||||
go func() { _, _ = cli.Presence(context.Background()) }()
|
||||
|
||||
select {
|
||||
case <-api.entered:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("the handler was never dispatched")
|
||||
}
|
||||
|
||||
_ = srv.Close()
|
||||
<-served
|
||||
select {
|
||||
case got := <-api.err:
|
||||
if !errors.Is(got, context.Canceled) {
|
||||
t.Errorf("handler saw %v, want context.Canceled", got)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Close did not cancel the dispatch")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user