Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up subprocess handling and make shell use optional #3509

Merged
merged 20 commits into from
Oct 4, 2017

Conversation

kyhavlov
Copy link
Contributor

This aims at fixing the issues in #2999 around Consul's handling of child processes spawned from the exec/lock/watch commands and checks/watches run by the consul agent.

Copy link
Contributor

@slackpad slackpad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a quick pass through with some initial thoughts.

"os"
"os/exec"

"os/signal"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort up.

select {
case sig := <-signalCh:
if err := cmd.Process.Signal(sig); err != nil {
fmt.Println("Error relaying signal to subprocess: ", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be fed a logger?

@@ -9,6 +9,8 @@ import (
"os"
"strconv"

"os/exec"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort up.

script, isScript := handler.(string)
args, isArgs := handler.([]string)
if isArgs {
script = fmt.Sprintf("%v", args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nasty since we don't use this (and we super don't want to treat the args as a big string. Can you maybe always run the below code based on an args array, so there's less switching around, like this:

var args []string
switch h := hander.(type) {
case string:
    // figure out the shell and stuff from the environment
    args = []string{theShell, "-c", h...}
case []string:
    args = h
}

// then down below you just always use ExecSubprocess with the args local

command/lock.go Outdated
@@ -101,6 +104,9 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
"is generated based on the provided child command.")
f.BoolVar(&passStdin, "pass-stdin", false,
"Pass stdin to the child process.")
f.BoolVar(&shell, "shell", false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to move health checks away from using the shell (via having the user put the shell in themselves via args then we could omit this part of the change.

h := wp.Exempt["handler"]
if _, ok := h.(string); h == nil || !ok {
// Get the handler and subprocess arguments
handler, hasHandler := wp.Exempt["handler"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about issuing a deprecation warning for handler here and in health checks?

// If enabled, start a goroutine to relay signals to the subprocess and
// another to watch for its shutdown.
if echoSignals {
signalCh := make(chan os.Signal, 4)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you come up with 4 as the buffer size here?

Asking because the documentation on signal.Notify states that Package signal will not block sending to c: the caller must ensure that c has sufficient buffer space to keep up with the expected signal rate. 4 seemed like an oddly specific choice, e.g what If I sent the subprocess a bunch of SIGHUPs in quick succession?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the same value we use in listening for signals on the agent: https://github.com/hashicorp/consul/blob/master/command/agent.go#L366

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That begs the question of why its set to 4 there as well.

I would set this buffer size to something like 10. Not that we can hypothesize anything about the incoming signal rate in how people would use this, but atleast 10 is conservatively large enough. If you are sending more than 10 signals at a time to the subprocess, you are probably doing something wrong.

With leaving it as 4, there is the possibility of missing a signal because Signal.Notify won't block when the buffer is full, and thus your go routine below will never see it.

@@ -3344,6 +3387,7 @@ func TestFullConfig(t *testing.T) {
}

// check the warnings
t.Log(b.Warnings)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like some debug code got left around.

@@ -59,7 +60,7 @@ func (c *CheckType) IsTTL() bool {

// IsMonitor checks if this is a Monitor type
func (c *CheckType) IsMonitor() bool {
return c.Script != "" && c.DockerContainerID == "" && c.Interval != 0
return (c.Script != "" || len(c.ScriptArgs) > 0) && c.DockerContainerID == "" && c.Interval != 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should call IsScript() to be more clear / dedup.

// another to watch for its shutdown.
if echoSignals {
signalCh := make(chan os.Signal, 4)
shutdownCh := make(chan struct{}, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need the , 0 argument here.

func makeWatchHandler(logOutput io.Writer, handler interface{}) watch.HandlerFunc {
var args []string

// Figure out whether to run in shell or raw subprocess mode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh I'm sorry I just realized that ExecScript has special stuff on Windows (there's util_windows.go). I know there are some open issues for Windows that will likely be cleaned up by just using args, so we should probably keep using ExecScript in this path as well until we just deprecate it, that way we don't break more than we have to. Sorry to flip back on this.

command/lock.go Outdated
@@ -9,6 +9,8 @@ import (
"syscall"
"time"

"os/exec"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should sort up into the previous list.

command/watch.go Outdated
@@ -8,6 +8,8 @@ import (
"strconv"
"strings"

"os/exec"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should sort up into the previous list.

command/exec.go Outdated
@@ -161,10 +168,15 @@ func (c *ExecCommand) Run(args []string) int {
return 1
}
c.conf.script = buf.Bytes()
} else {
if !c.conf.shell {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be else if !c.conf.shell { on line 171, I think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@slackpad slackpad merged commit 198ed60 into master Oct 4, 2017
@slackpad slackpad deleted the subprocess-cleanup branch October 4, 2017 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants