@@ -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