Skip to content

Commit ef5e5fa

Browse files
plugins: run plugin with new process group ID
Changes were made in 1554ac3 to provide a mechanism for the CLI to notify running plugin processes that they should exit, in order to improve the general CLI/plugin UX. The current implementation boils down to: 1. The CLI creates a socket 2. The CLI executes the plugin 3. The plugin connects to the socket 4. (When) the CLI receives a termination signal, it uses the socket to notify the plugin that it should exit 5. The plugin's gets notified via the socket, and cancels it's `cmd.Context`, which then gets handled appropriately This change works in most cases and fixes the issue it sets out to solve (see: docker/compose#11292) however, in the case where the user has a TTY attached and the plugin is not already handling received signals, steps 4+ changes: 4. (When) the CLI receives a termination signal, before it can use the socket to notify the plugin that it should exit, the plugin process also receives a signal due to sharing the pgid with the CLI Since we now have a proper "job control" mechanism, we can simplify the scenarios by executing the plugins with their own process group id, thereby removing the "double notification" issue and making it so that plugins can handle the same whether attached to a TTY or not. In order to make this change "plugin-binary" backwards-compatible, in the case that a plugin does not connect to the socket, the CLI passes the signal to the plugin process. Co-authored-by: Bjorn Neergaard <[email protected]> Signed-off-by: Laura Brehm <[email protected]> Signed-off-by: Bjorn Neergaard <[email protected]>
1 parent 7d92573 commit ef5e5fa

4 files changed

Lines changed: 25 additions & 1 deletion

File tree

cli-plugins/manager/manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
232232
cmd.Stdin = os.Stdin
233233
cmd.Stdout = os.Stdout
234234
cmd.Stderr = os.Stderr
235+
configureOSSpecificCommand(cmd)
235236

236237
cmd.Env = os.Environ()
237238
cmd.Env = append(cmd.Env, ReexecEnvvar+"="+os.Args[0])

cli-plugins/manager/manager_unix.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@
22

33
package manager
44

5+
import (
6+
"os/exec"
7+
"syscall"
8+
)
9+
510
var defaultSystemPluginDirs = []string{
611
"/usr/local/lib/docker/cli-plugins", "/usr/local/libexec/docker/cli-plugins",
712
"/usr/lib/docker/cli-plugins", "/usr/libexec/docker/cli-plugins",
813
}
14+
15+
func configureOSSpecificCommand(cmd *exec.Cmd) {
16+
// Spawn the plugin process in a new process group, so that signals are not forwarded by the OS.
17+
// The foreground process group is e.g. sent a SIGINT when Ctrl-C is input to the TTY, but we
18+
// implement our own job control for the plugin.
19+
cmd.SysProcAttr = &syscall.SysProcAttr{
20+
Setpgid: true,
21+
}
22+
}

cli-plugins/manager/manager_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package manager
22

33
import (
44
"os"
5+
"os/exec"
56
"path/filepath"
67
)
78

89
var defaultSystemPluginDirs = []string{
910
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"),
1011
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
1112
}
13+
14+
func configureOSSpecificCommand(cmd *exec.Cmd) {
15+
// no-op
16+
}

cmd/docker/docker.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,16 @@ func tryPluginRun(dockerCli command.Cli, cmd *cobra.Command, subcommand string,
230230
// we send a SIGKILL to the plugin process and exit
231231
go func() {
232232
retries := 0
233-
for range signals {
233+
for s := range signals {
234234
if conn != nil {
235235
if err := conn.Close(); err != nil {
236236
_, _ = fmt.Fprintf(dockerCli.Err(), "failed to signal plugin to close: %v\n", err)
237237
}
238238
conn = nil
239+
} else {
240+
// When the plugin is communicating via socket with the host CLI, we perform job control via the socket.
241+
// However, if the plugin is an old version that is not socket-aware, we need to explicitly forward termination signals.
242+
plugincmd.Process.Signal(s)
239243
}
240244
retries++
241245
if retries >= exitLimit {

0 commit comments

Comments
 (0)