Skip to content

Commit d5fd0c5

Browse files
authored
Merge pull request moby#51925 from fuweid/fix-46212
daemon: ignore duplicate task exit events in daemon state
2 parents 6566904 + d5e23b6 commit d5fd0c5

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

daemon/container/state.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,11 @@ func (s *State) SetRestarting(exitStatus *ExitStatus) {
299299
s.Restarting = true
300300
s.Paused = false
301301
s.Pid = 0
302-
s.FinishedAt = time.Now().UTC()
302+
finishedAt := exitStatus.ExitedAt
303+
if finishedAt.IsZero() {
304+
finishedAt = time.Now().UTC()
305+
}
306+
s.FinishedAt = finishedAt
303307
s.ExitCode = exitStatus.ExitCode
304308

305309
s.notifyAndClear(&s.stopWaiters)

daemon/monitor.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontaine
4848
return nil
4949
}
5050

51+
// Ignore duplicate exit event that may arrive after the first one.
52+
// See moby/moby#46212.
53+
if daemon.shouldIgnoreExitEventWithLock(c, e) {
54+
c.Unlock()
55+
return nil
56+
}
57+
5158
cfg := daemon.config()
5259

5360
// Health checks will be automatically restarted if/when the
@@ -339,3 +346,50 @@ func (daemon *Daemon) autoRemove(cfg *config.Config, c *container.Container) {
339346
log.G(context.TODO()).WithFields(log.Fields{"error": err, "container": c.ID}).Error("error removing container")
340347
}
341348
}
349+
350+
func (daemon *Daemon) shouldIgnoreExitEventWithLock(c *container.Container, e *libcontainerdtypes.EventInfo) (ret bool) {
351+
if e == nil {
352+
return false
353+
}
354+
355+
curState := c.State.State()
356+
357+
defer func() {
358+
if ret {
359+
log.G(context.TODO()).
360+
WithFields(log.Fields{
361+
"container": c.ID,
362+
"state": c.State.String(),
363+
"exitCode": e.ExitCode,
364+
"exitedAt": e.ExitedAt,
365+
}).Info("ignoring duplicate container exit event")
366+
}
367+
}()
368+
369+
switch curState {
370+
case containertypes.StateRemoving,
371+
containertypes.StateExited,
372+
containertypes.StateDead:
373+
374+
return true
375+
376+
case containertypes.StateRunning:
377+
// If the container is running, but the exit event is from
378+
// before it was started, ignore it. This can happen when a
379+
// duplicate exit arrives while the restart path holds the
380+
// container lock; by the time we process it, a new task is
381+
// already running, so the exit belongs to the previous task.
382+
return !e.ExitedAt.IsZero() && e.ExitedAt.Before(c.StartedAt)
383+
384+
case containertypes.StateRestarting:
385+
// The restart path acquires and holds the container lock before
386+
// processing; on failure it transitions the container to exited,
387+
// and on success it transitions to running. Therefore, any exit
388+
// event observed while still restarting is a late duplicate from
389+
// the previous task and should be ignored.
390+
return !e.ExitedAt.IsZero() && e.ExitedAt.After(c.FinishedAt)
391+
392+
default:
393+
return false
394+
}
395+
}

0 commit comments

Comments
 (0)