@@ -37,7 +37,8 @@ func main() {
3737 dockerCli , err := command .NewDockerCli (command .WithBaseContext (ctx ))
3838 if err != nil {
3939 fmt .Fprintln (os .Stderr , err )
40- os .Exit (1 )
40+ defer os .Exit (1 )
41+ return
4142 }
4243 logrus .SetOutput (dockerCli .Err ())
4344 otel .SetErrorHandler (debug .OTELErrorHandler )
@@ -50,15 +51,19 @@ func main() {
5051 // StatusError should only be used for errors, and all errors should
5152 // have a non-zero exit status, so never exit with 0
5253 if sterr .StatusCode == 0 {
53- os .Exit (1 )
54+ defer os .Exit (1 )
55+ return
5456 }
55- os .Exit (sterr .StatusCode )
57+ defer os .Exit (sterr .StatusCode )
58+ return
5659 }
5760 if errdefs .IsCancelled (err ) {
58- os .Exit (0 )
61+ defer os .Exit (0 )
62+ return
5963 }
6064 fmt .Fprintln (dockerCli .Err (), err )
61- os .Exit (1 )
65+ defer os .Exit (1 )
66+ return
6267 }
6368}
6469
@@ -248,42 +253,56 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command
248253 // notify the plugin via the PluginServer (or signal) as appropriate.
249254 const exitLimit = 2
250255
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+ // If we're still running after 3 interruptions
277+ // (SIGINT/SIGTERM), send a SIGKILL to the plugin as a
278+ // final attempt to terminate, and exit.
279+ if force {
280+ _ , _ = fmt .Fprint (dockerCli .Err (), "got 3 SIGTERM/SIGINTs, forcefully exiting\n " )
281+ _ = plugincmd .Process .Kill ()
282+ os .Exit (1 )
283+ }
284+ }
285+
251286 go func () {
252287 retries := 0
288+ force := false
289+ // catch the first signal through context cancellation
253290 <- ctx .Done ()
291+ tryTerminatePlugin (force )
254292
293+ // register subsequent signals
255294 signals := make (chan os.Signal , exitLimit )
256295 signal .Notify (signals , platformsignals .TerminationSignals ... )
257296
258297 for range signals {
259- // If stdin is a TTY, the kernel will forward
260- // signals to the subprocess because the shared
261- // pgid makes the TTY a controlling terminal.
262- //
263- // The plugin should have it's own copy of this
264- // termination logic, and exit after 3 retries
265- // on it's own.
266- if dockerCli .Out ().IsTerminal () {
267- continue
268- }
269-
270- // Terminate the plugin server, which will
271- // close all connections with plugin
272- // subprocesses, and signal them to exit.
273- //
274- // Repeated invocations will result in EINVAL,
275- // or EBADF; but that is fine for our purposes.
276- _ = srv .Close ()
277-
298+ retries ++
278299 // If we're still running after 3 interruptions
279300 // (SIGINT/SIGTERM), send a SIGKILL to the plugin as a
280301 // final attempt to terminate, and exit.
281- retries ++
282302 if retries >= exitLimit {
283- _ , _ = fmt .Fprint (dockerCli .Err (), "got 3 SIGTERM/SIGINTs, forcefully exiting\n " )
284- _ = plugincmd .Process .Kill ()
285- os .Exit (1 )
303+ force = true
286304 }
305+ tryTerminatePlugin (force )
287306 }
288307 }()
289308
0 commit comments