@@ -28,12 +28,20 @@ import (
2828)
2929
3030func main () {
31- ctx := context .Background ()
31+ statusCode := dockerMain ()
32+ if statusCode != 0 {
33+ os .Exit (statusCode )
34+ }
35+ }
36+
37+ func dockerMain () int {
38+ ctx , cancelNotify := signal .NotifyContext (context .Background (), platformsignals .TerminationSignals ... )
39+ defer cancelNotify ()
3240
3341 dockerCli , err := command .NewDockerCli (command .WithBaseContext (ctx ))
3442 if err != nil {
3543 fmt .Fprintln (os .Stderr , err )
36- os . Exit ( 1 )
44+ return 1
3745 }
3846 logrus .SetOutput (dockerCli .Err ())
3947 otel .SetErrorHandler (debug .OTELErrorHandler )
@@ -46,16 +54,17 @@ func main() {
4654 // StatusError should only be used for errors, and all errors should
4755 // have a non-zero exit status, so never exit with 0
4856 if sterr .StatusCode == 0 {
49- os . Exit ( 1 )
57+ return 1
5058 }
51- os . Exit ( sterr .StatusCode )
59+ return sterr .StatusCode
5260 }
5361 if errdefs .IsCancelled (err ) {
54- os . Exit ( 0 )
62+ return 0
5563 }
5664 fmt .Fprintln (dockerCli .Err (), err )
57- os . Exit ( 1 )
65+ return 1
5866 }
67+ return 0
5968}
6069
6170func newDockerCommand (dockerCli * command.DockerCli ) * cli.TopLevelCommand {
@@ -224,7 +233,7 @@ func setValidateArgs(dockerCli command.Cli, cmd *cobra.Command) {
224233 })
225234}
226235
227- func tryPluginRun (dockerCli command.Cli , cmd * cobra.Command , subcommand string , envs []string ) error {
236+ func tryPluginRun (ctx context. Context , dockerCli command.Cli , cmd * cobra.Command , subcommand string , envs []string ) error {
228237 plugincmd , err := pluginmanager .PluginRunCommand (dockerCli , subcommand , cmd )
229238 if err != nil {
230239 return err
@@ -242,40 +251,56 @@ func tryPluginRun(dockerCli command.Cli, cmd *cobra.Command, subcommand string,
242251
243252 // Background signal handling logic: block on the signals channel, and
244253 // notify the plugin via the PluginServer (or signal) as appropriate.
245- const exitLimit = 3
246- signals := make (chan os.Signal , exitLimit )
247- signal .Notify (signals , platformsignals .TerminationSignals ... )
254+ const exitLimit = 2
255+
256+ tryTerminatePlugin := func (force bool ) {
257+ // If stdin is a TTY, the kernel will forward
258+ // signals to the subprocess because the shared
259+ // pgid makes the TTY a controlling terminal.
260+ //
261+ // The plugin should have it's own copy of this
262+ // termination logic, and exit after 3 retries
263+ // on it's own.
264+ if dockerCli .Out ().IsTerminal () {
265+ return
266+ }
267+
268+ // Terminate the plugin server, which will
269+ // close all connections with plugin
270+ // subprocesses, and signal them to exit.
271+ //
272+ // Repeated invocations will result in EINVAL,
273+ // or EBADF; but that is fine for our purposes.
274+ _ = srv .Close ()
275+
276+ // force the process to terminate if it hasn't already
277+ if force {
278+ _ = plugincmd .Process .Kill ()
279+ _ , _ = fmt .Fprint (dockerCli .Err (), "got 3 SIGTERM/SIGINTs, forcefully exiting\n " )
280+ os .Exit (1 )
281+ }
282+ }
283+
248284 go func () {
249285 retries := 0
250- for range signals {
251- // If stdin is a TTY, the kernel will forward
252- // signals to the subprocess because the shared
253- // pgid makes the TTY a controlling terminal.
254- //
255- // The plugin should have it's own copy of this
256- // termination logic, and exit after 3 retries
257- // on it's own.
258- if dockerCli .Out ().IsTerminal () {
259- continue
260- }
286+ force := false
287+ // catch the first signal through context cancellation
288+ <- ctx .Done ()
289+ tryTerminatePlugin (force )
261290
262- // Terminate the plugin server, which will
263- // close all connections with plugin
264- // subprocesses, and signal them to exit.
265- //
266- // Repeated invocations will result in EINVAL,
267- // or EBADF; but that is fine for our purposes.
268- _ = srv .Close ()
291+ // register subsequent signals
292+ signals := make (chan os.Signal , exitLimit )
293+ signal .Notify (signals , platformsignals .TerminationSignals ... )
269294
295+ for range signals {
296+ retries ++
270297 // If we're still running after 3 interruptions
271298 // (SIGINT/SIGTERM), send a SIGKILL to the plugin as a
272299 // final attempt to terminate, and exit.
273- retries ++
274300 if retries >= exitLimit {
275- _ , _ = fmt .Fprintf (dockerCli .Err (), "got %d SIGTERM/SIGINTs, forcefully exiting\n " , retries )
276- _ = plugincmd .Process .Kill ()
277- os .Exit (1 )
301+ force = true
278302 }
303+ tryTerminatePlugin (force )
279304 }
280305 }()
281306
@@ -338,7 +363,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
338363 ccmd , _ , err := cmd .Find (args )
339364 subCommand = ccmd
340365 if err != nil || pluginmanager .IsPluginCommand (ccmd ) {
341- err := tryPluginRun (dockerCli , cmd , args [0 ], envs )
366+ err := tryPluginRun (ctx , dockerCli , cmd , args [0 ], envs )
342367 if err == nil {
343368 if dockerCli .HooksEnabled () && dockerCli .Out ().IsTerminal () && ccmd != nil {
344369 pluginmanager .RunPluginHooks (ctx , dockerCli , cmd , ccmd , args )
0 commit comments