@@ -3,15 +3,18 @@ package container // import "github.com/docker/docker/integration/container"
33import (
44 "context"
55 "fmt"
6+ "runtime"
67 "testing"
78 "time"
89
910 "github.com/docker/docker/api/types"
1011 "github.com/docker/docker/api/types/container"
12+ "github.com/docker/docker/api/types/filters"
1113 "github.com/docker/docker/client"
1214 testContainer "github.com/docker/docker/integration/internal/container"
1315 "github.com/docker/docker/testutil/daemon"
1416 "gotest.tools/v3/assert"
17+ is "gotest.tools/v3/assert/cmp"
1518 "gotest.tools/v3/poll"
1619 "gotest.tools/v3/skip"
1720)
@@ -208,3 +211,72 @@ func TestContainerWithAutoRemoveCanBeRestarted(t *testing.T) {
208211 })
209212 }
210213}
214+
215+ // TestContainerRestartWithCancelledRequest verifies that cancelling a restart
216+ // request does not cancel the restart operation, and still starts the container
217+ // after it was stopped.
218+ //
219+ // Regression test for https://github.com/moby/moby/discussions/46682
220+ func TestContainerRestartWithCancelledRequest (t * testing.T ) {
221+ ctx := context .TODO ()
222+ defer setupTest (t )()
223+ apiClient := testEnv .APIClient ()
224+
225+ // Create a container that ignores SIGTERM and doesn't stop immediately,
226+ // giving us time to cancel the request.
227+ //
228+ // Restarting a container is "stop" (and, if needed, "kill"), then "start"
229+ // the container. We're trying to create the scenario where the "stop" is
230+ // handled, but the request was cancelled and therefore the "start" not
231+ // taking place.
232+ cID := testContainer .Run (ctx , t , apiClient , testContainer .WithCmd ("sh" , "-c" , "trap 'echo received TERM' TERM; while true; do usleep 10; done" ))
233+ defer func () {
234+ err := apiClient .ContainerRemove (ctx , cID , types.ContainerRemoveOptions {Force : true })
235+ if t .Failed () && err != nil {
236+ t .Logf ("Cleaning up test container failed with error: %v" , err )
237+ }
238+ }()
239+
240+ // Start listening for events.
241+ messages , errs := apiClient .Events (ctx , types.EventsOptions {
242+ Filters : filters .NewArgs (
243+ filters .Arg ("container" , cID ),
244+ filters .Arg ("event" , "restart" ),
245+ ),
246+ })
247+
248+ // Make restart request, but cancel the request before the container
249+ // is (forcibly) killed.
250+ ctx2 , cancel := context .WithTimeout (ctx , 100 * time .Millisecond )
251+ stopTimeout := 1
252+ err := apiClient .ContainerRestart (ctx2 , cID , container.StopOptions {
253+ Timeout : & stopTimeout ,
254+ })
255+ assert .Check (t , is .ErrorIs (err , context .DeadlineExceeded ))
256+ cancel ()
257+
258+ // Validate that the restart event occurred, which is emitted
259+ // after the restart (stop (kill) start) finished.
260+ //
261+ // Note that we cannot use RestartCount for this, as that's only
262+ // used for restart-policies.
263+ restartTimeout := 2 * time .Second
264+ if runtime .GOOS == "windows" {
265+ // hcs can sometimes take a long time to stop container.
266+ restartTimeout = StopContainerWindowsPollTimeout
267+ }
268+ select {
269+ case m := <- messages :
270+ assert .Check (t , is .Equal (m .Actor .ID , cID ))
271+ assert .Check (t , is .Equal (m .Action , "restart" ))
272+ case err := <- errs :
273+ assert .NilError (t , err )
274+ case <- time .After (restartTimeout ):
275+ t .Errorf ("timeout waiting for restart event" )
276+ }
277+
278+ // Container should be restarted (running).
279+ inspect , err := apiClient .ContainerInspect (ctx , cID )
280+ assert .NilError (t , err )
281+ assert .Check (t , is .Equal (inspect .State .Status , "running" ))
282+ }
0 commit comments