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:
+31
-5
@@ -30,6 +30,14 @@ type Server struct {
|
||||
done chan struct{}
|
||||
accept sync.Mutex // guards wg.Add vs Close's wg.Wait sequence
|
||||
|
||||
// ctx — server-scoped, cancelled by Close, and the parent of every request
|
||||
// context. serveConn dispatched under context.Background() until V-638, so
|
||||
// a dispatch in flight during shutdown could not be told to stop and the
|
||||
// closeGrace below could only abandon it. Cancelling gives a handler that
|
||||
// respects its context the chance to return instead.
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// conns — every accepted connection still being served. Close needs these
|
||||
// because closing the listener does nothing to a connection already
|
||||
// accepted: serveConn is parked in readFrame waiting for a peer that may
|
||||
@@ -208,11 +216,14 @@ func Listen(path string, api CoreAPI) (*Server, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s := &Server{
|
||||
path: path,
|
||||
addr: addr,
|
||||
ln: ln,
|
||||
done: make(chan struct{}),
|
||||
path: path,
|
||||
addr: addr,
|
||||
ln: ln,
|
||||
done: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
s.api.Store(api)
|
||||
return s, nil
|
||||
@@ -250,7 +261,10 @@ func (s *Server) Serve() error {
|
||||
|
||||
func (s *Server) serveConn(c net.Conn) {
|
||||
caller, callerOK := peerCaller(c)
|
||||
ctx := context.Background()
|
||||
// Derived from the server's, so Close cancels a dispatch in flight, and
|
||||
// cancelled when this conn ends so nothing a handler spawned outlives it.
|
||||
ctx, cancel := context.WithCancel(s.serverContext())
|
||||
defer cancel()
|
||||
if callerOK {
|
||||
ctx = WithCaller(ctx, caller)
|
||||
}
|
||||
@@ -274,6 +288,15 @@ func (s *Server) serveConn(c net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
// serverContext is s.ctx, or Background for a Server built as a zero value
|
||||
// rather than by Listen (the wiring tests do that).
|
||||
func (s *Server) serverContext() context.Context {
|
||||
if s.ctx == nil {
|
||||
return context.Background()
|
||||
}
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
func (s *Server) safeDispatch(ctx context.Context, req Request) (result json.RawMessage, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -720,6 +743,9 @@ func (s *Server) Close() error {
|
||||
default:
|
||||
close(s.done)
|
||||
}
|
||||
if s.cancel != nil {
|
||||
s.cancel()
|
||||
}
|
||||
err := s.ln.Close()
|
||||
// Closing the listener stops new connections; it does nothing to the ones
|
||||
// already accepted. Close those too, or every serveConn parked in readFrame
|
||||
|
||||
Reference in New Issue
Block a user