1
0
mirror of https://github.com/opencontainers/runc.git synced 2025-08-08 12:42:06 +03:00

Fix various state bugs for pause and destroy

There were issues where a process could die before pausing completed
leaving the container in an inconsistent state and unable to be
destoryed.  This makes sure that if the container is paused and the
process is dead it will unfreeze the cgroup before removing them.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby
2016-01-21 16:43:33 -08:00
parent 27132f2e51
commit 556f798a19
4 changed files with 68 additions and 50 deletions

View File

@@ -189,29 +189,27 @@ func (c *linuxContainer) Start(process *Process) error {
} }
return newSystemError(err) return newSystemError(err)
} }
c.state = &runningState{
c: c,
}
if doInit { if doInit {
if err := c.updateState(parent); err != nil { if err := c.updateState(parent); err != nil {
return err return err
} }
} else { if c.config.Hooks != nil {
c.state.transition(&nullState{ s := configs.HookState{
c: c, Version: c.config.Version,
s: Running, ID: c.id,
}) Pid: parent.pid(),
} Root: c.config.Rootfs,
if c.config.Hooks != nil { }
s := configs.HookState{ for _, hook := range c.config.Hooks.Poststart {
Version: c.config.Version, if err := hook.Run(s); err != nil {
ID: c.id, if err := parent.terminate(); err != nil {
Pid: parent.pid(), logrus.Warn(err)
Root: c.config.Rootfs, }
} return newSystemError(err)
for _, hook := range c.config.Hooks.Poststart {
if err := hook.Run(s); err != nil {
if err := parent.terminate(); err != nil {
logrus.Warn(err)
} }
return newSystemError(err)
} }
} }
} }
@@ -340,6 +338,13 @@ func (c *linuxContainer) Destroy() error {
func (c *linuxContainer) Pause() error { func (c *linuxContainer) Pause() error {
c.m.Lock() c.m.Lock()
defer c.m.Unlock() defer c.m.Unlock()
status, err := c.currentStatus()
if err != nil {
return err
}
if status != Running {
return newGenericError(fmt.Errorf("container not running"), ContainerNotRunning)
}
if err := c.cgroupManager.Freeze(configs.Frozen); err != nil { if err := c.cgroupManager.Freeze(configs.Frozen); err != nil {
return err return err
} }
@@ -351,6 +356,13 @@ func (c *linuxContainer) Pause() error {
func (c *linuxContainer) Resume() error { func (c *linuxContainer) Resume() error {
c.m.Lock() c.m.Lock()
defer c.m.Unlock() defer c.m.Unlock()
status, err := c.currentStatus()
if err != nil {
return err
}
if status != Paused {
return newGenericError(fmt.Errorf("container not paused"), ContainerNotPaused)
}
if err := c.cgroupManager.Freeze(configs.Thawed); err != nil { if err := c.cgroupManager.Freeze(configs.Thawed); err != nil {
return err return err
} }
@@ -939,9 +951,6 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc
func (c *linuxContainer) updateState(process parentProcess) error { func (c *linuxContainer) updateState(process parentProcess) error {
c.initProcess = process c.initProcess = process
if err := c.refreshState(); err != nil {
return err
}
state, err := c.currentState() state, err := c.currentState()
if err != nil { if err != nil {
return err return err
@@ -1017,35 +1026,36 @@ func (c *linuxContainer) isPaused() (bool, error) {
} }
func (c *linuxContainer) currentState() (*State, error) { func (c *linuxContainer) currentState() (*State, error) {
status, err := c.currentStatus() var (
if err != nil { startTime string
return nil, err externalDescriptors []string
} pid = -1
if status == Destroyed { )
return nil, newGenericError(fmt.Errorf("container destroyed"), ContainerNotExists) if c.initProcess != nil {
} pid = c.initProcess.pid()
startTime, err := c.initProcess.startTime() startTime, _ = c.initProcess.startTime()
if err != nil { externalDescriptors = c.initProcess.externalDescriptors()
return nil, newSystemError(err)
} }
state := &State{ state := &State{
BaseState: BaseState{ BaseState: BaseState{
ID: c.ID(), ID: c.ID(),
Config: *c.config, Config: *c.config,
InitProcessPid: c.initProcess.pid(), InitProcessPid: pid,
InitProcessStartTime: startTime, InitProcessStartTime: startTime,
}, },
CgroupPaths: c.cgroupManager.GetPaths(), CgroupPaths: c.cgroupManager.GetPaths(),
NamespacePaths: make(map[configs.NamespaceType]string), NamespacePaths: make(map[configs.NamespaceType]string),
ExternalDescriptors: c.initProcess.externalDescriptors(), ExternalDescriptors: externalDescriptors,
} }
for _, ns := range c.config.Namespaces { if pid > 0 {
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) for _, ns := range c.config.Namespaces {
} state.NamespacePaths[ns.Type] = ns.GetPath(pid)
for _, nsType := range configs.NamespaceTypes() { }
if _, ok := state.NamespacePaths[nsType]; !ok { for _, nsType := range configs.NamespaceTypes() {
ns := configs.Namespace{Type: nsType} if _, ok := state.NamespacePaths[nsType]; !ok {
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) ns := configs.Namespace{Type: nsType}
state.NamespacePaths[ns.Type] = ns.GetPath(pid)
}
} }
} }
return state, nil return state, nil

View File

@@ -16,6 +16,7 @@ const (
ContainerPaused ContainerPaused
ContainerNotStopped ContainerNotStopped
ContainerNotRunning ContainerNotRunning
ContainerNotPaused
// Process errors // Process errors
ProcessNotExecuted ProcessNotExecuted
@@ -46,6 +47,8 @@ func (c ErrorCode) String() string {
return "Container is not running" return "Container is not running"
case ConsoleExists: case ConsoleExists:
return "Console exists for process" return "Console exists for process"
case ContainerNotPaused:
return "Container is not paused"
default: default:
return "Unknown error" return "Unknown error"
} }

View File

@@ -49,6 +49,7 @@ func destroy(c *linuxContainer) error {
if herr := runPoststopHooks(c); err == nil { if herr := runPoststopHooks(c); err == nil {
err = herr err = herr
} }
c.state = &stoppedState{c: c}
return err return err
} }
@@ -116,10 +117,10 @@ func (r *runningState) transition(s containerState) error {
} }
r.c.state = s r.c.state = s
return nil return nil
case *pausedState: case *pausedState, *nullState:
r.c.state = s r.c.state = s
return nil return nil
case *runningState, *nullState: case *runningState:
return nil return nil
} }
return newStateTransitionError(r, s) return newStateTransitionError(r, s)
@@ -148,7 +149,7 @@ func (p *pausedState) status() Status {
func (p *pausedState) transition(s containerState) error { func (p *pausedState) transition(s containerState) error {
switch s.(type) { switch s.(type) {
case *runningState: case *runningState, *stoppedState:
p.c.state = s p.c.state = s
return nil return nil
case *pausedState: case *pausedState:
@@ -158,6 +159,16 @@ func (p *pausedState) transition(s containerState) error {
} }
func (p *pausedState) destroy() error { func (p *pausedState) destroy() error {
isRunning, err := p.c.isRunning()
if err != nil {
return err
}
if !isRunning {
if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil {
return err
}
return destroy(p.c)
}
return newGenericError(fmt.Errorf("container is paused"), ContainerPaused) return newGenericError(fmt.Errorf("container is paused"), ContainerPaused)
} }

View File

@@ -49,19 +49,13 @@ func TestPausedStateTransition(t *testing.T) {
valid := []containerState{ valid := []containerState{
&pausedState{}, &pausedState{},
&runningState{}, &runningState{},
&stoppedState{},
} }
for _, v := range valid { for _, v := range valid {
if err := s.transition(v); err != nil { if err := s.transition(v); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
err := s.transition(&stoppedState{})
if err == nil {
t.Fatal("transition to stopped state should fail")
}
if !isStateTransitionError(err) {
t.Fatal("expected stateTransitionError")
}
} }
func TestRestoredStateTransition(t *testing.T) { func TestRestoredStateTransition(t *testing.T) {