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

Fix data race #162

Merged
merged 1 commit into from
Oct 12, 2017
Merged

Fix data race #162

merged 1 commit into from
Oct 12, 2017

Conversation

kevinburke
Copy link
Contributor

We try to read cmd.Process in the thread that handles incoming
signals, but there's no synchronization between that goroutine and the
one that sets cmd.Process to a non-nil value.

This might be overkill, e.g. there may be a way to write this with
less code, but I confirmed that this patch addresses the
race when shutting down the program.

Fixes #158.

@lox
Copy link
Collaborator

lox commented Oct 12, 2017

Thanks! How about something like this to avoid the mutex's?

	cmd := exec.Command(input.Command, input.Args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err = cmd.Start(); err != nil {
		app.Fatalf("%v", err)
		return
	}

	// wait for the command to finish
	waitCh := make(chan error)
	go func() {
		waitCh <- cmd.Wait()
	}()

	for {
		select {
		case sig := <-input.Signals:
			if err = cmd.Process.Signal(sig); err != nil {
				app.Errorf("%v", err)
				return
			}
		case err := <-waitCh:
			var waitStatus syscall.WaitStatus
			if exitError, ok := err.(*exec.ExitError); ok {
				waitStatus = exitError.Sys().(syscall.WaitStatus)
				os.Exit(waitStatus.ExitStatus())
			}
			if err != nil {
				app.Errorf("%v", err)
				return
			}
		}
	}

@kevinburke kevinburke force-pushed the fix-data-race branch 2 times, most recently from 2ffc675 to 761159b Compare October 12, 2017 03:53
@kevinburke
Copy link
Contributor Author

Done - I think we don't want the for loop, since every branch of the select terminates - the for loop causes one of the examples to run forever.

@lox
Copy link
Collaborator

lox commented Oct 12, 2017

Hmmmm, I suspect we will need the for loop, otherwise receiving any signal before the process finishes will cause it to terminate. Theoretically it should support multiple signals too, although the current implementation doesn't.

Which example will run forever?

@kevinburke
Copy link
Contributor Author

This one:

func ExampleExecCommand() {
	awsConfigFile = &vault.FileConfig{}
	keyringImpl = keyring.NewArrayKeyring([]keyring.Item{
		{Key: "llamas", Data: []byte(`{"AccessKeyID":"ABC","SecretAccessKey":"XYZ"}`)},
	})

	app := kingpin.New("aws-vault", "")
	ConfigureGlobals(app)
	ConfigureExecCommand(app)
	kingpin.MustParse(app.Parse([]string{
		"--debug", "exec", "--no-session", "llamas", "--", "sh", "-c", "echo $AWS_ACCESS_KEY_ID",
	}))

	// Output:
	// ABC
}

@lox
Copy link
Collaborator

lox commented Oct 12, 2017

Hmmm. Ah right, it needs a break at the end of the <-waitCh case even if the error is nil.

	cmd := exec.Command(input.Command, input.Args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err = cmd.Start(); err != nil {
		app.Fatalf("%v", err)
		return
	}

	// wait for the command to finish
	waitCh := make(chan error)
	go func() {
		waitCh <- cmd.Wait()
	}()

	for {
		select {
                // listen for signals and send them to the process
		case sig := <-input.Signals:
			if err = cmd.Process.Signal(sig); err != nil {
				app.Errorf("%v", err)
				break
			}
                // wait for return to exit
		case err := <-waitCh:
			var waitStatus syscall.WaitStatus
			if exitError, ok := err.(*exec.ExitError); ok {
				waitStatus = exitError.Sys().(syscall.WaitStatus)
				os.Exit(waitStatus.ExitStatus())
			}
			if err != nil {
				app.Errorf("%v", err)
			}
                        break
		}
	}

@kevinburke
Copy link
Contributor Author

Apologies; if we try the code you pasted, I still get a hang in the test.

I think what we need is to break/continue processing after we send a signal, but to always return if the underlying program exits/ we receive a value on waitCh. The diff I just pushed does that.

We try to read cmd.Process in the thread that handles incoming
signals, but there's no synchronization between that goroutine and the
one that sets cmd.Process to a non-nil value.

This might be overkill, e.g. there may be a way to write this with
less code, but I confirmed that this patch addresses the
race when shutting down the program.

Fixes 99designs#158.
@lox
Copy link
Collaborator

lox commented Oct 12, 2017

Looks good, thanks!

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.

2 participants