(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command, subcommand string, envs []string)
| 320 | } |
| 321 | |
| 322 | func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command, subcommand string, envs []string) error { |
| 323 | plugincmd, err := pluginmanager.PluginRunCommand(dockerCli, subcommand, cmd) |
| 324 | if err != nil { |
| 325 | return err |
| 326 | } |
| 327 | |
| 328 | // Establish the plugin socket, adding it to the environment under a |
| 329 | // well-known key if successful. |
| 330 | srv, err := socket.NewPluginServer(nil) |
| 331 | if err == nil { |
| 332 | plugincmd.Env = append(plugincmd.Env, socket.EnvKey+"="+srv.Addr().String()) |
| 333 | defer func() { |
| 334 | // Close the server when plugin execution is over, so that in case |
| 335 | // it's still open, any sockets on the filesystem are cleaned up. |
| 336 | _ = srv.Close() |
| 337 | }() |
| 338 | } |
| 339 | |
| 340 | // Set additional environment variables specified by the caller. |
| 341 | plugincmd.Env = append(plugincmd.Env, envs...) |
| 342 | |
| 343 | // Background signal handling logic: block on the signals channel, and |
| 344 | // notify the plugin via the PluginServer (or signal) as appropriate. |
| 345 | const exitLimit = 2 |
| 346 | |
| 347 | // forceExitCh is closed by the signal goroutine just before it SIGKILLs |
| 348 | // the plugin. The main goroutine checks this after plugincmd.Run() returns |
| 349 | // and owns the final os.Exit(1) call, keeping exit-code ownership in one |
| 350 | // place and avoiding a race between two concurrent os.Exit calls. |
| 351 | forceExitCh := make(chan struct{}) |
| 352 | |
| 353 | tryTerminatePlugin := func(force bool) { |
| 354 | // If stdin is a TTY, the kernel will forward |
| 355 | // signals to the subprocess because the shared |
| 356 | // pgid makes the TTY a controlling terminal. |
| 357 | // |
| 358 | // The plugin should have its own copy of this |
| 359 | // termination logic, and exit after 3 retries |
| 360 | // on its own. |
| 361 | if dockerCli.Out().IsTerminal() { |
| 362 | return |
| 363 | } |
| 364 | |
| 365 | // Terminate the plugin server, which closes |
| 366 | // all connections with plugin subprocesses, |
| 367 | // and signal them to exit. |
| 368 | // |
| 369 | // Repeated invocations result in EINVAL or EBADF, |
| 370 | // but that is fine for our purposes. |
| 371 | if srv != nil { |
| 372 | _ = srv.Close() |
| 373 | } |
| 374 | |
| 375 | // force the process to terminate if it hasn't already |
| 376 | if force { |
| 377 | // Close forceExitCh before Kill so the channel is guaranteed |
| 378 | // to be closed by the time plugincmd.Run() returns: the plugin |
| 379 | // can only exit after Kill() delivers SIGKILL, and Run() only |
no test coverage detected
searching dependent graphs…