You've already forked runc
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:
@@ -189,16 +189,13 @@ 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 {
|
|
||||||
c.state.transition(&nullState{
|
|
||||||
c: c,
|
|
||||||
s: Running,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if c.config.Hooks != nil {
|
if c.config.Hooks != nil {
|
||||||
s := configs.HookState{
|
s := configs.HookState{
|
||||||
Version: c.config.Version,
|
Version: c.config.Version,
|
||||||
@@ -215,6 +212,7 @@ func (c *linuxContainer) Start(process *Process) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
if pid > 0 {
|
||||||
for _, ns := range c.config.Namespaces {
|
for _, ns := range c.config.Namespaces {
|
||||||
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())
|
state.NamespacePaths[ns.Type] = ns.GetPath(pid)
|
||||||
}
|
}
|
||||||
for _, nsType := range configs.NamespaceTypes() {
|
for _, nsType := range configs.NamespaceTypes() {
|
||||||
if _, ok := state.NamespacePaths[nsType]; !ok {
|
if _, ok := state.NamespacePaths[nsType]; !ok {
|
||||||
ns := configs.Namespace{Type: nsType}
|
ns := configs.Namespace{Type: nsType}
|
||||||
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())
|
state.NamespacePaths[ns.Type] = ns.GetPath(pid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state, nil
|
return state, nil
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
|
Reference in New Issue
Block a user