Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v5.4] podman exec: correctly support detaching #25197

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions libpod/container_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,16 +284,17 @@ func (c *Container) ExecStart(sessionID string) error {
return c.save()
}

func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize) error {
return c.execStartAndAttach(sessionID, streams, newSize, false)
}

// ExecStartAndAttach starts and attaches to an exec session in a container.
// execStartAndAttach starts and attaches to an exec session in a container.
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize, isHealthcheck bool) error {
unlock := true
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
defer func() {
if unlock {
c.lock.Unlock()
}
}()

if err := c.syncContainer(); err != nil {
return err
Expand Down Expand Up @@ -348,6 +349,12 @@ func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachS
}

tmpErr := <-attachChan
// user detached
if errors.Is(tmpErr, define.ErrDetach) {
// ensure we the defer does not unlock as we are not locked here
unlock = false
return tmpErr
}
if lastErr != nil {
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
}
Expand Down Expand Up @@ -435,9 +442,14 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w
close(hijackDone)
}()

unlock := true
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
defer func() {
if unlock {
c.lock.Unlock()
}
}()

if err := c.syncContainer(); err != nil {
return err
Expand Down Expand Up @@ -518,6 +530,12 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w
}

tmpErr := <-attachChan
// user detached
if errors.Is(tmpErr, define.ErrDetach) {
// ensure we the defer does not unlock as we are not locked here
unlock = false
return tmpErr
}
if lastErr != nil {
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
}
Expand Down Expand Up @@ -769,11 +787,14 @@ func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resi
if err != nil {
return -1, err
}
cleanup := true
defer func() {
if err := c.ExecRemove(sessionID, false); err != nil {
if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) {
exitCode = -1
retErr = err
if cleanup {
if err := c.ExecRemove(sessionID, false); err != nil {
if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) {
exitCode = -1
retErr = err
}
}
}
}()
Expand Down Expand Up @@ -807,6 +828,11 @@ func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resi
}

if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
// user detached, there will be no exit just exit without reporting an error
if errors.Is(err, define.ErrDetach) {
cleanup = false
return 0, nil
}
return -1, err
}

Expand Down
2 changes: 1 addition & 1 deletion libpod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func checkDependencyContainer(depCtr, ctr *Container) error {

// hijackWriteError writes an error to a hijacked HTTP session.
func hijackWriteError(toWrite error, cid string, terminal bool, httpBuf *bufio.ReadWriter) {
if toWrite != nil {
if toWrite != nil && !errors.Is(toWrite, define.ErrDetach) {
errString := []byte(fmt.Sprintf("Error: %v\n", toWrite))
if !terminal {
// We need a header.
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/handlers/compat/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
t := r.Context().Value(api.IdleTrackerKey).(*idle.Tracker)
defer t.Close()

if err != nil {
if err != nil && !errors.Is(err, define.ErrDetach) {
// Cannot report error to client as a 500 as the Upgrade set status to 101
logErr(err)
}
Expand Down
36 changes: 30 additions & 6 deletions pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, sigPro
go func() {
err := containers.Attach(ic.ClientCtx, name, input, output, errput, attachReady, options)
attachErr <- err
close(attachErr)
}()
// Wait for the attach to actually happen before starting
// the container.
Expand All @@ -683,13 +684,36 @@ func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, sigPro
return -1, err
}

// call wait immediately after start to avoid racing against container removal when it was created with --rm
exitCode, err := containers.Wait(cancelCtx, name, nil)
if err != nil {
return -1, err
}
code = int(exitCode)
// Call wait immediately after start to avoid racing against container removal when it was created with --rm.
// It must be run in a separate goroutine to so we do not block when attach returns early, i.e. user
// detaches in which case wait would not return.
waitChan := make(chan error)
go func() {
defer close(waitChan)

exitCode, err := containers.Wait(cancelCtx, name, nil)
if err != nil {
waitChan <- fmt.Errorf("wait for container: %w", err)
return
}
code = int(exitCode)
}()

select {
case err := <-waitChan:
if err != nil {
return -1, err
}
case err := <-attachErr:
if err != nil {
return -1, err
}
// also wait for the wait to be complete in this case
err = <-waitChan
if err != nil {
return -1, err
}
}
case err := <-attachErr:
return -1, err
}
Expand Down