Skip to content

Commit f0ef791

Browse files
committed
podman exec: correctly support detaching
podman exec support detaching early via the detach key sequence. In that case the podman process should exit successfully but the container exec process keeps running. Now I wrote automated test for both podman run and exec detach but this uncovered several larger issues: - detach sequence parsing is broken[1] - podman-remote exec detach is broken[2] - detach in general seems to be buggy/racy, seeing lot of flakes that fail to restore the terminal and get an EIO instead, i.e. "Unable to restore terminal: input/output error" Thus I cannot add tests for now but this commit should at least fix the obvoius case as reported by the user so I like to get this in regardless and I will work through the other issues once I have more time. Fixes containers#24895 [1] containers/common#2302 [2] containers#25089 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
1 parent 3eb0e1e commit f0ef791

File tree

4 files changed

+68
-14
lines changed

4 files changed

+68
-14
lines changed

libpod/container_exec.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,14 @@ func (c *Container) ExecStart(sessionID string) error {
287287
// execStartAndAttach starts and attaches to an exec session in a container.
288288
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
289289
func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize, isHealthcheck bool) error {
290+
unlock := true
290291
if !c.batched {
291292
c.lock.Lock()
292-
defer c.lock.Unlock()
293+
defer func() {
294+
if unlock {
295+
c.lock.Unlock()
296+
}
297+
}()
293298

294299
if err := c.syncContainer(); err != nil {
295300
return err
@@ -344,6 +349,12 @@ func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachS
344349
}
345350

346351
tmpErr := <-attachChan
352+
// user detached
353+
if errors.Is(tmpErr, define.ErrDetach) {
354+
// ensure we the defer does not unlock as we are not locked here
355+
unlock = false
356+
return tmpErr
357+
}
347358
if lastErr != nil {
348359
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
349360
}
@@ -431,9 +442,14 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w
431442
close(hijackDone)
432443
}()
433444

445+
unlock := true
434446
if !c.batched {
435447
c.lock.Lock()
436-
defer c.lock.Unlock()
448+
defer func() {
449+
if unlock {
450+
c.lock.Unlock()
451+
}
452+
}()
437453

438454
if err := c.syncContainer(); err != nil {
439455
return err
@@ -514,6 +530,12 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w
514530
}
515531

516532
tmpErr := <-attachChan
533+
// user detached
534+
if errors.Is(tmpErr, define.ErrDetach) {
535+
// ensure we the defer does not unlock as we are not locked here
536+
unlock = false
537+
return tmpErr
538+
}
517539
if lastErr != nil {
518540
logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
519541
}
@@ -765,11 +787,14 @@ func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resi
765787
if err != nil {
766788
return -1, err
767789
}
790+
cleanup := true
768791
defer func() {
769-
if err := c.ExecRemove(sessionID, false); err != nil {
770-
if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) {
771-
exitCode = -1
772-
retErr = err
792+
if cleanup {
793+
if err := c.ExecRemove(sessionID, false); err != nil {
794+
if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) {
795+
exitCode = -1
796+
retErr = err
797+
}
773798
}
774799
}
775800
}()
@@ -803,6 +828,11 @@ func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resi
803828
}
804829

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

libpod/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func checkDependencyContainer(depCtr, ctr *Container) error {
149149

150150
// hijackWriteError writes an error to a hijacked HTTP session.
151151
func hijackWriteError(toWrite error, cid string, terminal bool, httpBuf *bufio.ReadWriter) {
152-
if toWrite != nil {
152+
if toWrite != nil && !errors.Is(toWrite, define.ErrDetach) {
153153
errString := []byte(fmt.Sprintf("Error: %v\n", toWrite))
154154
if !terminal {
155155
// We need a header.

pkg/api/handlers/compat/exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
200200
t := r.Context().Value(api.IdleTrackerKey).(*idle.Tracker)
201201
defer t.Close()
202202

203-
if err != nil {
203+
if err != nil && !errors.Is(err, define.ErrDetach) {
204204
// Cannot report error to client as a 500 as the Upgrade set status to 101
205205
logErr(err)
206206
}

pkg/domain/infra/tunnel/containers.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, sigPro
666666
go func() {
667667
err := containers.Attach(ic.ClientCtx, name, input, output, errput, attachReady, options)
668668
attachErr <- err
669+
close(attachErr)
669670
}()
670671
// Wait for the attach to actually happen before starting
671672
// the container.
@@ -683,13 +684,36 @@ func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, sigPro
683684
return -1, err
684685
}
685686

686-
// call wait immediately after start to avoid racing against container removal when it was created with --rm
687-
exitCode, err := containers.Wait(cancelCtx, name, nil)
688-
if err != nil {
689-
return -1, err
690-
}
691-
code = int(exitCode)
687+
// Call wait immediately after start to avoid racing against container removal when it was created with --rm.
688+
// It must be run in a separate goroutine to so we do not block when attach returns early, i.e. user
689+
// detaches in which case wait would not return.
690+
waitChan := make(chan error)
691+
go func() {
692+
defer close(waitChan)
693+
694+
exitCode, err := containers.Wait(cancelCtx, name, nil)
695+
if err != nil {
696+
waitChan <- fmt.Errorf("wait for container: %w", err)
697+
return
698+
}
699+
code = int(exitCode)
700+
}()
692701

702+
select {
703+
case err := <-waitChan:
704+
if err != nil {
705+
return -1, err
706+
}
707+
case err := <-attachErr:
708+
if err != nil {
709+
return -1, err
710+
}
711+
// also wait for the wait to be complete in this case
712+
err = <-waitChan
713+
if err != nil {
714+
return -1, err
715+
}
716+
}
693717
case err := <-attachErr:
694718
return -1, err
695719
}

0 commit comments

Comments
 (0)