fix(exec): Handle context cancellation server-side

This commit is contained in:
Anton Ovchinnikov
2025-11-09 18:19:41 +01:00
parent 7bff706aa0
commit 30bb33f1a0
+15 -7
View File
@@ -1049,6 +1049,13 @@ func (s *Server) handleServerExecInput(
defer attachConn.CloseWrite() defer attachConn.CloseWrite()
for { for {
select {
case <-ctx.Done():
slog.Debug("Input goroutine context canceled via context", "exec_id", execID)
return nil
default:
}
req, err := stream.Recv() req, err := stream.Recv()
switch { switch {
case errors.Is(err, io.EOF): case errors.Is(err, io.EOF):
@@ -1209,9 +1216,9 @@ func (s *Server) ExecContainer(stream pb.Docker_ExecContainerServer) error {
} }
defer attachConn.Close() defer attachConn.Close()
// Create a cancelable context for the handlers // Create a cancelable context for the input handler
handlerCtx, cancel := context.WithCancel(ctx) handlerCtx, cancelInput := context.WithCancel(ctx)
defer cancel() // Ensure handlers are canceled when we return defer cancelInput()
// Create a channel to wait for output completion // Create a channel to wait for output completion
outputDone := make(chan error, 1) outputDone := make(chan error, 1)
@@ -1236,13 +1243,14 @@ func (s *Server) ExecContainer(stream pb.Docker_ExecContainerServer) error {
}() }()
// Wait for the output goroutine to complete (it signals when done) // Wait for the output goroutine to complete (it signals when done)
// We only wait for output, not for stdin goroutine. // We only wait for the output handler goroutine, not for the stdin one.
if err := <-outputDone; err != nil { if err := <-outputDone; err != nil {
slog.Warn("Error in exec output handler", "err", err, "exec_id", execResp.ID) slog.Warn("Error in exec output handler", "err", err, "exec_id", execResp.ID)
} }
// The stdin goroutine may still be blocked in stream.Recv() waiting for client data, // Do a best-effort cancellation of the stdin handler.
// so cancel it explicitly. // We can't guarantee immediate exit because it may be blocked on stream.Recv(), but at
cancel() // least we want to send a cancel signal explicitly.
cancelInput()
inspectResp, err := s.client.ContainerExecInspect(ctx, execResp.ID) inspectResp, err := s.client.ContainerExecInspect(ctx, execResp.ID)
if err != nil { if err != nil {