@@ -28,12 +28,20 @@ import (
28
28
)
29
29
30
30
func 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 ()
32
40
33
41
dockerCli , err := command .NewDockerCli (command .WithBaseContext (ctx ))
34
42
if err != nil {
35
43
fmt .Fprintln (os .Stderr , err )
36
- os . Exit ( 1 )
44
+ return 1
37
45
}
38
46
logrus .SetOutput (dockerCli .Err ())
39
47
otel .SetErrorHandler (debug .OTELErrorHandler )
@@ -46,16 +54,17 @@ func main() {
46
54
// StatusError should only be used for errors, and all errors should
47
55
// have a non-zero exit status, so never exit with 0
48
56
if sterr .StatusCode == 0 {
49
- os . Exit ( 1 )
57
+ return 1
50
58
}
51
- os . Exit ( sterr .StatusCode )
59
+ return sterr .StatusCode
52
60
}
53
61
if errdefs .IsCancelled (err ) {
54
- os . Exit ( 0 )
62
+ return 0
55
63
}
56
64
fmt .Fprintln (dockerCli .Err (), err )
57
- os . Exit ( 1 )
65
+ return 1
58
66
}
67
+ return 0
59
68
}
60
69
61
70
func newDockerCommand (dockerCli * command.DockerCli ) * cli.TopLevelCommand {
@@ -224,7 +233,7 @@ func setValidateArgs(dockerCli command.Cli, cmd *cobra.Command) {
224
233
})
225
234
}
226
235
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 {
228
237
plugincmd , err := pluginmanager .PluginRunCommand (dockerCli , subcommand , cmd )
229
238
if err != nil {
230
239
return err
@@ -242,40 +251,56 @@ func tryPluginRun(dockerCli command.Cli, cmd *cobra.Command, subcommand string,
242
251
243
252
// Background signal handling logic: block on the signals channel, and
244
253
// 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
+
248
284
go func () {
249
285
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 )
261
290
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 ... )
269
294
295
+ for range signals {
296
+ retries ++
270
297
// If we're still running after 3 interruptions
271
298
// (SIGINT/SIGTERM), send a SIGKILL to the plugin as a
272
299
// final attempt to terminate, and exit.
273
- retries ++
274
300
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
278
302
}
303
+ tryTerminatePlugin (force )
279
304
}
280
305
}()
281
306
@@ -338,7 +363,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
338
363
ccmd , _ , err := cmd .Find (args )
339
364
subCommand = ccmd
340
365
if err != nil || pluginmanager .IsPluginCommand (ccmd ) {
341
- err := tryPluginRun (dockerCli , cmd , args [0 ], envs )
366
+ err := tryPluginRun (ctx , dockerCli , cmd , args [0 ], envs )
342
367
if err == nil {
343
368
if dockerCli .HooksEnabled () && dockerCli .Out ().IsTerminal () && ccmd != nil {
344
369
pluginmanager .RunPluginHooks (ctx , dockerCli , cmd , ccmd , args )
0 commit comments