-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
os/exec: race on cmd.Wait() might lead to panic #28461
Comments
I'm unclear as to whether every standard library function that isn't safe for concurrent use needs to be documented as such. For this particular case it seems fine to me, as it wasn't obvious by reading the godoc, and I can imagine someone making the mistake. /cc @bradfitz @ianlancetaylor as per https://dev.golang.org/owners/ for a decision. |
In general we only document the abnormal cases. The assumption when unstated is that things are not safe for concurrent use, that zero values are not usable, that implementations implement interfaces faithfully, and that only one return values is non-zero and meaningful. That said, in this case I agree it would be a reasonable assumption for users to think that two concurrent Wait calls are valid, so I wouldn't object to docs or a fix here to allow concurrent calls. |
Fixing this to fully permit concurrent wait calls would require us to save the |
Why do you dismiss that option? Just curious. It's not a huge struct, and the lifetime of *exec.Cmd isn't typically long. It's not usually retained anywhere. |
It just doesn't feel like the right approach to me. I'm open to counter-argument. |
Thanks @bradfitz for clarifying assumptions, I just pointed this out as this might be a peculiar case due to the synchronizing nature of the One more question: should the doc change be on About fixing it I agree with @ianlancetaylor. I think that external processes should be treated as "outside of go boundaries". More specifically: if synchronization or orchestration is needed w.r.t. an external process, interactions with the process should be proxied by a goroutine. It would then use channels and other primitives in an idiomatic way. I wouldn't like to read some Go code using external processes and wait on them instead of a |
I think I'm missing something: don't we already save the |
@bcmills In this issue we're talking about the os package, not the os/exec package. |
I'm still not sure that I follow. As @empijei notes, it seems like we could address the reported race with a mutex in func (c *Cmd) Wait() error {
if c.Process == nil {
return errors.New("exec: not started")
}
c.waitMu.Lock()
defer c.waitMu.Unlock()
if c.waitErr != nil {
return c.waitErr
}
if c.ProcessState == nil {
if c.finished {
return errors.New("exec: ProcessState is nil after Wait was called")
}
c.ProcessState, c.waitErr := c.Process.Wait()
if c.waitDone != nil {
close(c.waitDone)
}
for range c.goroutine {
if err := <-c.errch; err != nil && c.waitErr == nil && state.Success() {
c.waitErr = err
}
}
c.closeDescriptors(c.closeAfterWait)
c.finished = true
}
if c.waitErr == nil && !c.ProcessState.Success() {
return &ExitError{ProcessState: c.ProcessState}
}
return c.waitErr
} |
I suppose that I have mentally transmuted this issue to applying to |
Maybe, although I think there is a qualitative difference: calling |
Sure, agreed, but presumably if we fix the os package we will get the os/exec fix for free. So, why not? |
I don't think we can get the And then there is the question of whether |
The If we support concurrent |
Leaning toward making it safe for multiple goroutines to call both Waits, but for now let's leave things alone - don't document it, don't change it. It's been this way for 12 releases. |
I know this is an old one, sorry. I also find out it is problematic to wait for finishing with multiple threads because only once it returns exit code, then just error. Little workaround - not perfect, but working type errorWrap struct {
err error
}
type Cmd struct {
exec.Cmd
waitLock *sync.Mutex
errWrap *errorWrap
}
func (c *Cmd) WaitTS() error {
c.waitLock.Lock()
defer c.waitLock.Unlock()
if c.errWrap == nil {
c.errWrap = &errorWrap{
err: c.Wait(),
}
}
return c.errWrap.err
} |
Hey folks! I am working on a project where I start a child application in my go program with the There I get race condition warning about my code reading the Both of these actions(checking the state and |
The current implementation of cmd.Wait has a race condition: if multiple goroutines call it, it might cause a panic.
In the first part of the method, copied below, two concurrent calls might check
c.finished
, get false, set it to true, invokec.Process.Wait()
and closec.waitDone
before any error checking is performed.c.waitDone
is achan struct{}
, and a double close will cause a panic.Since waiting is a synchronization primitive I'd expect one of the two:
c.finished
(but I'm not a fan of atomics and would require to change type to some sort of int) or protectc
with a mutex, which would be my suggested solution for this caseI would happily send in CLs in both cases.
The text was updated successfully, but these errors were encountered: