mirror of https://github.com/containers/podman.git
cmd/podman/utils.go: Cancel-able resize writes
On Thu, Jun 28, 2018 at 03:48:26AM -0700, Marco Vedovati wrote [1]:
> The root cause is a deadlock between two channel writes made by two
> different goroutines:
>
> 1. `resizeTty() : go func(){} : sendUpdate()` is sending a resize
> message thru `resize` right at the beginning, but the channel is
> never read if some startup error occurs.
>
> 2. Upon program termination, `startAttachCtr() : defer func(){} ` is
> telling the goroutine in "1." to stop via the `resizeTerminate`
> channel. But that guy is still waiting for the write to `resize`
> to complete so the the termination message is never read.
>
> I think the go deadlock detection does not kick in because not all
> goroutines are seen as asleep. E.g. `os/signal Notify()` is enough
> to have the deadlock not detected.
333ab8c2
(Fix podman hangs when detecting startup error in container
attached mode, 2018-06-27, #1010) addressed this with a deferred
drain. This commit adjusts that approach to use a single select to
cover "have we been canceled?", "has there been a resize signal?", and
(when we have one) "can we write the most recent resize event to the
resize channel?".
A side benefit to this approach is that if we have a slow resize
consumer and several resize signals, the resizeTty function will keep
updating its local resizeEvent. Once the resize channel is able to
accept, only the most-recent event will be written. Previously we'd
have written one resize event for every received signal, even if the
resize consumer was falling behind.
[1]: https://github.com/projectatomic/libpod/pull/1010#issuecomment-400994436
Signed-off-by: W. Trevor King <wking@tremily.us>
Closes: #1018
Approved by: rhatdan
This commit is contained in:
parent
333ab8c211
commit
c82166afab
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
gosignal "os/signal"
|
gosignal "os/signal"
|
||||||
|
@ -19,6 +20,7 @@ type RawTtyFormatter struct {
|
||||||
|
|
||||||
// Attach to a container
|
// Attach to a container
|
||||||
func attachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool) error {
|
func attachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool) error {
|
||||||
|
ctx := context.Background()
|
||||||
resize := make(chan remotecommand.TerminalSize)
|
resize := make(chan remotecommand.TerminalSize)
|
||||||
|
|
||||||
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
||||||
|
@ -28,12 +30,10 @@ func attachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys
|
||||||
if haveTerminal && ctr.Spec().Process.Terminal {
|
if haveTerminal && ctr.Spec().Process.Terminal {
|
||||||
logrus.Debugf("Handling terminal attach")
|
logrus.Debugf("Handling terminal attach")
|
||||||
|
|
||||||
resizeTerminate := make(chan interface{})
|
subCtx, cancel := context.WithCancel(ctx)
|
||||||
defer func() {
|
defer cancel()
|
||||||
resizeTerminate <- true
|
|
||||||
close(resizeTerminate)
|
resizeTty(subCtx, resize)
|
||||||
}()
|
|
||||||
resizeTty(resize, resizeTerminate)
|
|
||||||
|
|
||||||
oldTermState, err := term.SaveState(os.Stdin.Fd())
|
oldTermState, err := term.SaveState(os.Stdin.Fd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,19 +46,6 @@ func attachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys
|
||||||
defer restoreTerminal(oldTermState)
|
defer restoreTerminal(oldTermState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// There may be on the other size of the resize channel a goroutine trying to send.
|
|
||||||
// So consume all data inside the channel before exiting to avoid a deadlock.
|
|
||||||
defer func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-resize:
|
|
||||||
logrus.Debugf("Consumed resize command from channel")
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
streams := new(libpod.AttachStreams)
|
streams := new(libpod.AttachStreams)
|
||||||
streams.OutputStream = stdout
|
streams.OutputStream = stdout
|
||||||
streams.ErrorStream = stderr
|
streams.ErrorStream = stderr
|
||||||
|
@ -89,6 +76,7 @@ func attachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys
|
||||||
|
|
||||||
// Start and attach to a container
|
// Start and attach to a container
|
||||||
func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool) error {
|
func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool) error {
|
||||||
|
ctx := context.Background()
|
||||||
resize := make(chan remotecommand.TerminalSize)
|
resize := make(chan remotecommand.TerminalSize)
|
||||||
|
|
||||||
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
|
||||||
|
@ -98,12 +86,10 @@ func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detac
|
||||||
if haveTerminal && ctr.Spec().Process.Terminal {
|
if haveTerminal && ctr.Spec().Process.Terminal {
|
||||||
logrus.Debugf("Handling terminal attach")
|
logrus.Debugf("Handling terminal attach")
|
||||||
|
|
||||||
resizeTerminate := make(chan interface{})
|
subCtx, cancel := context.WithCancel(ctx)
|
||||||
defer func() {
|
defer cancel()
|
||||||
resizeTerminate <- true
|
|
||||||
close(resizeTerminate)
|
resizeTty(subCtx, resize)
|
||||||
}()
|
|
||||||
resizeTty(resize, resizeTerminate)
|
|
||||||
|
|
||||||
oldTermState, err := term.SaveState(os.Stdin.Fd())
|
oldTermState, err := term.SaveState(os.Stdin.Fd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,19 +102,6 @@ func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detac
|
||||||
defer restoreTerminal(oldTermState)
|
defer restoreTerminal(oldTermState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// There may be on the other size of the resize channel a goroutine trying to send.
|
|
||||||
// So consume all data inside the channel before exiting to avoid a deadlock.
|
|
||||||
defer func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-resize:
|
|
||||||
logrus.Debugf("Consumed resize command from channel")
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
streams := new(libpod.AttachStreams)
|
streams := new(libpod.AttachStreams)
|
||||||
streams.OutputStream = stdout
|
streams.OutputStream = stdout
|
||||||
streams.ErrorStream = stderr
|
streams.ErrorStream = stderr
|
||||||
|
@ -171,33 +144,45 @@ func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detac
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for prepareAttach - set up a goroutine to generate terminal resize events
|
// getResize returns a TerminalSize command matching stdin's current
|
||||||
func resizeTty(resize chan remotecommand.TerminalSize, resizeTerminate chan interface{}) {
|
// size on success, and nil on errors.
|
||||||
sigchan := make(chan os.Signal, 1)
|
func getResize() *remotecommand.TerminalSize {
|
||||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
|
||||||
sendUpdate := func() {
|
|
||||||
winsize, err := term.GetWinsize(os.Stdin.Fd())
|
winsize, err := term.GetWinsize(os.Stdin.Fd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Could not get terminal size %v", err)
|
logrus.Warnf("Could not get terminal size %v", err)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
resize <- remotecommand.TerminalSize{
|
return &remotecommand.TerminalSize{
|
||||||
Width: winsize.Width,
|
Width: winsize.Width,
|
||||||
Height: winsize.Height,
|
Height: winsize.Height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper for prepareAttach - set up a goroutine to generate terminal resize events
|
||||||
|
func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(resize)
|
defer close(resize)
|
||||||
// Update the terminal size immediately without waiting
|
// Update the terminal size immediately without waiting
|
||||||
// for a SIGWINCH to get the correct initial size.
|
// for a SIGWINCH to get the correct initial size.
|
||||||
sendUpdate()
|
resizeEvent := getResize()
|
||||||
for {
|
for {
|
||||||
|
if resizeEvent == nil {
|
||||||
select {
|
select {
|
||||||
case <-resizeTerminate:
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-sigchan:
|
case <-sigchan:
|
||||||
sendUpdate()
|
resizeEvent = getResize()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-sigchan:
|
||||||
|
resizeEvent = getResize()
|
||||||
|
case resize <- *resizeEvent:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
Loading…
Reference in New Issue