mirror of
https://github.com/containers/podman.git
synced 2025-06-19 08:09:12 +08:00
remote: always send resize before the container starts
There is race condition in the remote client attach logic. Because the resize api call was handled in an extra goroutine the container was started before the resize call happend. To fix this we have to call resize in the same goroutine as attach. When the first resize is done start a goroutine to listen on SIGWINCH in the background and resize again if the signal is received. Fixes #9859 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
@ -46,20 +46,13 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.ContainerNotFound(w, name, err)
|
utils.ContainerNotFound(w, name, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if state, err := ctnr.State(); err != nil {
|
|
||||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain container state"))
|
|
||||||
return
|
|
||||||
} else if state != define.ContainerStateRunning && !query.IgnoreNotRunning {
|
|
||||||
utils.Error(w, "Container not running", http.StatusConflict,
|
|
||||||
fmt.Errorf("container %q in wrong state %q", name, state.String()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If container is not running, ignore since this can be a race condition, and is expected
|
|
||||||
if err := ctnr.AttachResize(sz); err != nil {
|
if err := ctnr.AttachResize(sz); err != nil {
|
||||||
if errors.Cause(err) != define.ErrCtrStateInvalid || !query.IgnoreNotRunning {
|
if errors.Cause(err) != define.ErrCtrStateInvalid {
|
||||||
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
|
utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container"))
|
||||||
return
|
} else {
|
||||||
|
utils.Error(w, "Container not running", http.StatusConflict, err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// This is not a 204, even though we write nothing, for compatibility
|
// This is not a 204, even though we write nothing, for compatibility
|
||||||
// reasons.
|
// reasons.
|
||||||
|
@ -1364,6 +1364,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
|||||||
// $ref: "#/responses/ok"
|
// $ref: "#/responses/ok"
|
||||||
// 404:
|
// 404:
|
||||||
// $ref: "#/responses/NoSuchContainer"
|
// $ref: "#/responses/NoSuchContainer"
|
||||||
|
// 409:
|
||||||
|
// $ref: "#/responses/ConflictError"
|
||||||
// 500:
|
// 500:
|
||||||
// $ref: "#/responses/InternalError"
|
// $ref: "#/responses/InternalError"
|
||||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeTTY)).Methods(http.MethodPost)
|
||||||
|
@ -138,7 +138,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri
|
|||||||
winCtx, winCancel := context.WithCancel(ctx)
|
winCtx, winCancel := context.WithCancel(ctx)
|
||||||
defer winCancel()
|
defer winCancel()
|
||||||
|
|
||||||
go attachHandleResize(ctx, winCtx, winChange, false, nameOrID, file)
|
attachHandleResize(ctx, winCtx, winChange, false, nameOrID, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are attaching around a start, we need to "signal"
|
// If we are attaching around a start, we need to "signal"
|
||||||
@ -327,32 +327,38 @@ func (f *rawFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|||||||
return append(buffer, '\r'), nil
|
return append(buffer, '\r'), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is intended to be run as a goroutine, handling resizing for a container
|
// This is intended to not be run as a goroutine, handling resizing for a container
|
||||||
// or exec session.
|
// or exec session. It will call resize once and then starts a goroutine which calls resize on winChange
|
||||||
func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, isExec bool, id string, file *os.File) {
|
func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, isExec bool, id string, file *os.File) {
|
||||||
// Prime the pump, we need one reset to ensure everything is ready
|
resize := func() {
|
||||||
winChange <- sig.SIGWINCH
|
w, h, err := terminal.GetSize(int(file.Fd()))
|
||||||
for {
|
if err != nil {
|
||||||
select {
|
logrus.Warnf("failed to obtain TTY size: %v", err)
|
||||||
case <-winCtx.Done():
|
}
|
||||||
return
|
|
||||||
case <-winChange:
|
|
||||||
w, h, err := terminal.GetSize(int(file.Fd()))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("failed to obtain TTY size: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resizeErr error
|
var resizeErr error
|
||||||
if isExec {
|
if isExec {
|
||||||
resizeErr = ResizeExecTTY(ctx, id, new(ResizeExecTTYOptions).WithHeight(h).WithWidth(w))
|
resizeErr = ResizeExecTTY(ctx, id, new(ResizeExecTTYOptions).WithHeight(h).WithWidth(w))
|
||||||
} else {
|
} else {
|
||||||
resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w))
|
resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w))
|
||||||
}
|
}
|
||||||
if resizeErr != nil {
|
if resizeErr != nil {
|
||||||
logrus.Warnf("failed to resize TTY: %v", resizeErr)
|
logrus.Warnf("failed to resize TTY: %v", resizeErr)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resize()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-winCtx.Done():
|
||||||
|
return
|
||||||
|
case <-winChange:
|
||||||
|
resize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the given terminal for raw mode
|
// Configure the given terminal for raw mode
|
||||||
@ -457,7 +463,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar
|
|||||||
winCtx, winCancel := context.WithCancel(ctx)
|
winCtx, winCancel := context.WithCancel(ctx)
|
||||||
defer winCancel()
|
defer winCancel()
|
||||||
|
|
||||||
go attachHandleResize(ctx, winCtx, winChange, true, sessionID, terminalFile)
|
attachHandleResize(ctx, winCtx, winChange, true, sessionID, terminalFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.GetAttachInput() {
|
if options.GetAttachInput() {
|
||||||
|
@ -49,7 +49,7 @@ class APITestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
APITestCase.podman.run("run", "alpine", "/bin/ls", check=True)
|
APITestCase.podman.run("run", "-d", "alpine", "top", check=True)
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
APITestCase.podman.run("pod", "rm", "--all", "--force", check=True)
|
APITestCase.podman.run("pod", "rm", "--all", "--force", check=True)
|
||||||
|
@ -12,7 +12,7 @@ class ContainerTestCase(APITestCase):
|
|||||||
r = requests.get(self.uri("/containers/json"), timeout=5)
|
r = requests.get(self.uri("/containers/json"), timeout=5)
|
||||||
self.assertEqual(r.status_code, 200, r.text)
|
self.assertEqual(r.status_code, 200, r.text)
|
||||||
obj = r.json()
|
obj = r.json()
|
||||||
self.assertEqual(len(obj), 0)
|
self.assertEqual(len(obj), 1)
|
||||||
|
|
||||||
def test_list_all(self):
|
def test_list_all(self):
|
||||||
r = requests.get(self.uri("/containers/json?all=true"))
|
r = requests.get(self.uri("/containers/json?all=true"))
|
||||||
@ -36,7 +36,7 @@ class ContainerTestCase(APITestCase):
|
|||||||
self.assertId(r.content)
|
self.assertId(r.content)
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
r = requests.delete(self.uri(self.resolve_container("/containers/{}")))
|
r = requests.delete(self.uri(self.resolve_container("/containers/{}?force=true")))
|
||||||
self.assertEqual(r.status_code, 204, r.text)
|
self.assertEqual(r.status_code, 204, r.text)
|
||||||
|
|
||||||
def test_stop(self):
|
def test_stop(self):
|
||||||
|
@ -56,8 +56,7 @@ function teardown() {
|
|||||||
stty rows $rows cols $cols <$PODMAN_TEST_PTY
|
stty rows $rows cols $cols <$PODMAN_TEST_PTY
|
||||||
|
|
||||||
# ...and make sure stty under podman reads that.
|
# ...and make sure stty under podman reads that.
|
||||||
# FIXME: 'sleep 1' is needed for podman-remote; without it, there's
|
run_podman run -it --name mystty $IMAGE stty size <$PODMAN_TEST_PTY
|
||||||
run_podman run -it --name mystty $IMAGE sh -c 'sleep 1;stty size' <$PODMAN_TEST_PTY
|
|
||||||
is "$output" "$rows $cols" "stty under podman reads the correct dimensions"
|
is "$output" "$rows $cols" "stty under podman reads the correct dimensions"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user