Skip to content

Commit f8a73cc

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. Given that I could not find any existing test for the detach key functionality not even for exec I added some. This seems to reveal more issues with on podman-remote, podman-remote run detach was broken which I fixed here as well but for podman-remote exec something bigger is needed. While I thought I fixed most problems there there was a strange race condition which caused the process to just hang. Thus I skipped the remote exec test for now and filled containers#25089 to track that. Fixes containers#24895 Signed-off-by: Paul Holzinger <[email protected]>
1 parent 392840d commit f8a73cc

File tree

5 files changed

+117
-15
lines changed

5 files changed

+117
-15
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: 23 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,28 @@ 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+
errorChan := make(chan error)
688+
go func() {
689+
defer close(errorChan)
690+
// call wait immediately after start to avoid racing against container removal when it was created with --rm
691+
exitCode, err := containers.Wait(cancelCtx, name, nil)
692+
if err != nil {
693+
errorChan <- err
694+
return
695+
}
696+
code = int(exitCode)
697+
}()
692698

699+
select {
700+
case err := <-errorChan:
701+
if err != nil {
702+
return -1, err
703+
}
704+
case err := <-attachErr:
705+
if err != nil {
706+
return -1, err
707+
}
708+
}
693709
case err := <-attachErr:
694710
return -1, err
695711
}
@@ -904,6 +920,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
904920
}
905921

906922
code, err := startAndAttach(ic, con.ID, &opts.DetachKeys, opts.SigProxy, opts.InputStream, opts.OutputStream, opts.ErrorStream)
923+
logrus.Error(4)
907924
if err != nil {
908925
if err == define.ErrDetach {
909926
return &report, nil

test/system/450-interactive.bats

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function teardown() {
3939
kill $PODMAN_SOCAT_PID
4040
PODMAN_SOCAT_PID=
4141
fi
42-
rm -f $PODMAN_TEST_PTY $PODMAN_DUMMY_PTY
42+
rm -f $PODMAN_TEST_PTY $PODMAN_DUMMY
4343

4444
basic_teardown
4545
}
@@ -124,4 +124,59 @@ function teardown() {
124124
is "$output" "$expected_tty" "passthrough-tty: tty matches"
125125
}
126126

127+
@test "podman run detach keys" {
128+
local cname1=c1-$(safename)
129+
local cname2=c2-$(safename)
130+
131+
local msg=$(random_string)
132+
# First "type" a command then send CTRL-p,CTRL-q sequence to the run command.
133+
# We must send that sequence in two echo commands if I use a single echo it
134+
# doesn't work, I don't know why.
135+
# If the detach does not work podman run will hang and timeout.
136+
# The sleep is needed to ensure the output can be printed before we detach.
137+
(echo "echo $msg" > $PODMAN_DUMMY; sleep 1; echo -n $'\cp' > $PODMAN_DUMMY; echo -n $'\cq' > $PODMAN_DUMMY ) &
138+
run_podman run -it --name $cname1 $IMAGE sh <$PODMAN_TEST_PTY
139+
# Because we print to a terminal it appends "\r"
140+
assert "$output" =~ "$msg"$'\r' "run output contains message"
141+
142+
# The container should still be running
143+
run_podman inspect --format {{.State.Status}} $cname1
144+
assert "$output" == "running" "container status"
145+
146+
# Now a second test with --detach-keys to make sure the cli option works
147+
(echo "echo $msg" > $PODMAN_DUMMY; sleep 1; echo -n $'\cc' > $PODMAN_DUMMY; echo -n $'J' > $PODMAN_DUMMY ) &
148+
run_podman run -it --name $cname2 --detach-keys ctrl-c,J $IMAGE sh <$PODMAN_TEST_PTY
149+
assert "$output" =~ "$msg"$'\r' "run output with --detach-keys contains message"
150+
151+
run_podman rm -f -t0 $cname1 $cname2
152+
}
153+
154+
@test "podman exec detach keys" {
155+
skip_if_remote "FIXME #25089: podman-remote exec detach broken"
156+
157+
local cname=c-$(safename)
158+
run_podman run -d --name $cname $IMAGE sleep inf
159+
160+
local msg=$(random_string)
161+
# First "type" a command then send CTRL-p,CTRL-q sequence to the exec command.
162+
# If the detach does not work podman exec will hang and timeout.
163+
# The sleep is needed to ensure the output can be printed before we detach.
164+
(echo "echo $msg" > $PODMAN_DUMMY; sleep 1; echo -n $'\cp' > $PODMAN_DUMMY; echo -n $'\cq' > $PODMAN_DUMMY ) &
165+
run_podman exec -it $cname sh <$PODMAN_TEST_PTY
166+
# Because we print to a terminal it appends "\r"
167+
assert "$output" =~ "$msg"$'\r' "exec output contains message"
168+
169+
# The previous exec session/process should still be running
170+
run_podman exec $cname ps aux
171+
# Match PID=2 USER=root and COMMAND=sh from the ps output
172+
assert "$output" =~ "2 root.*sh" "sh exec process still running"
173+
174+
# Now a second test with --detach-keys to make sure the cli option works
175+
(echo "echo $msg" > $PODMAN_DUMMY; sleep 1; echo -n $'\ct' > $PODMAN_DUMMY; echo -n $'f' > $PODMAN_DUMMY ) &
176+
run_podman exec -it --detach-keys ctrl-t,f $cname sh <$PODMAN_TEST_PTY
177+
assert "$output" =~ "$msg"$'\r' "exec output with --detach-keys contains message"
178+
179+
run_podman rm -f -t0 $cname
180+
}
181+
127182
# vim: filetype=sh

0 commit comments

Comments
 (0)