-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
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
Prevent dangling command calls #19448
Conversation
there are other places that need similar changes but this is the most important of these |
Then there is only one // LogNameStatusRepo opens git log --raw in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, paths ...string) (*bufio.Reader, func()) {
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
// so let's create a batch stdin and stdout
stdoutReader, stdoutWriter := nio.Pipe(buffer.New(32 * 1024)) |
Yes this was a planned follow-up, however: There were (sometimes quite significant) performance improvements when the We're going to need to double check with some large files and large repositories to see whether a There are a few options:
Unfortunately, I don't know how Windows and BSD are going to respond to the loss of the buffering There's another approach to solving the dangling file problem which makes fewer changes and you may prefer that to be merged and backported first. See #19454 |
If an `os/exec.Command` is passed non `*os.File` as an input/output, go will create `os.Pipe`s and wait for their closure in `cmd.Wait()`. If the code following this is responsible for closing `io.Pipe`s or other handlers then on process death from context cancellation the `Wait` can hang. There are two possible solutions: 1. use `os.Pipe` as the input/output as `cmd.Wait` does not wait for these. 2. create a goroutine waiting on the context cancellation that will close the inputs. This PR provides the second option - which is a simpler change that can be more easily backported. Closes go-gitea#19448 Signed-off-by: Andrew Thornton <[email protected]>
The Pipe is a very general mechanism provided by OS, in most cases it could be treated as a socket (indeed they have the similar performance). Since we never add a buffer to a socket when copying streams, I don't think it's necessary to add a buffer to a pipe, either. When downloading a large file, if I prefer to drop the |
If an `os/exec.Command` is passed non `*os.File` as an input/output, go will create `os.Pipe`s and wait for their closure in `cmd.Wait()`. If the code following this is responsible for closing `io.Pipe`s or other handlers then on process death from context cancellation the `Wait` can hang. There are two possible solutions: 1. use `os.Pipe` as the input/output as `cmd.Wait` does not wait for these. 2. create a goroutine waiting on the context cancellation that will close the inputs. This PR provides the second option - which is a simpler change that can be more easily backported. Closes go-gitea#19448 Signed-off-by: Andrew Thornton <[email protected]>
If an `os/exec.Command` is passed non `*os.File` as an input/output, go will create `os.Pipe`s and wait for their closure in `cmd.Wait()`. If the code following this is responsible for closing `io.Pipe`s or other handlers then on process death from context cancellation the `Wait` can hang. There are two possible solutions: 1. use `os.Pipe` as the input/output as `cmd.Wait` does not wait for these. 2. create a goroutine waiting on the context cancellation that will close the inputs. This PR changes the inputs to the `CatFileBatch` calls to use `os.Pipe`s preventing dangling cat-file calls. Signed-off-by: Andrew Thornton <[email protected]>
Co-authored-by: Gusted <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
backed by *os.File Signed-off-by: Andrew Thornton <[email protected]>
9e051f4
to
7f8174b
Compare
return err | ||
} | ||
defer fi.Close() | ||
_, _, err = NewCommand(ctx, "bundle", "create", "-", "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env, Stdout: out}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am uncertain why this code was written to use a temporary file in the first place. I suspect it may have been affected by this dangling problem and a temporary file was used to get around this?
OK, I've made substantial changes and created a common git.Pipe command with an implementation of PipeWriter and PipeReader that is based on the os.Pipe files. If a git.PipeWriter or git.PipeReader (as appropriate) is passed in to a git.Command RunContext then the File() will be passed to the underlying os/exec process transparently. I'm not sure if this will/could have effects on error propagation but I think it will be fine. (Although I will check.) I've removed the Most of the cases switching to using a git.Pipe won't have been affected by the dangling file case - certainly the pipeline functions I think were safe. However, it's still likely to be a performance improvement switching to use these. There are also a few cases where there was unnecessary usage of pipes and one case where I've dropped using a temporary file in preference to feeding the output down straight down the writer. As an aside we need to come up with a succinct way of expressing when this kind of deadlock can occur and how to ensure it does not happen. Although we could simply change the Command RunContexts to only take git.PipeWriter and git.PipeReader as appropriate it might be better to express when exactly you need to have the additional: go func() {
<-ctx.Done()
cancel()
_ = stdin.Close()
_ = stdout.Close()
}() Perhaps actually Command should do this itself? EDIT: The Command now does this. |
This comment was marked as resolved.
This comment was marked as resolved.
as per #19454 (review) |
If an `os/exec.Command` is passed non `*os.File` as an input/output, go will create `os.Pipe`s and wait for their closure in `cmd.Wait()`. If the code following this is responsible for closing `io.Pipe`s or other handlers then on process death from context cancellation the `Wait` can hang. There are two possible solutions: 1. use `os.Pipe` as the input/output as `cmd.Wait` does not wait for these. 2. create a goroutine waiting on the context cancellation that will close the inputs. This PR provides the second option - which is a simpler change that can be more easily backported. Closes go-gitea#19448 Signed-off-by: Andrew Thornton <[email protected]>
) If an `os/exec.Command` is passed non `*os.File` as an input/output, go will create `os.Pipe`s and wait for their closure in `cmd.Wait()`. If the code following this is responsible for closing `io.Pipe`s or other handlers then on process death from context cancellation the `Wait` can hang. There are two possible solutions: 1. use `os.Pipe` as the input/output as `cmd.Wait` does not wait for these. 2. create a goroutine waiting on the context cancellation that will close the inputs. This PR provides the second option - which is a simpler change that can be more easily backported. Closes #19448 Signed-off-by: Andrew Thornton <[email protected]> Co-authored-by: zeripath <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
Signed-off-by: Andrew Thornton <[email protected]>
a33ada4
to
441c932
Compare
…osed It may be prudent to add runtime finalizers to the git.Repository and git.blobReader objects to absolutely ensure that these are both properly cancelled, cleaned and closed out. This commit is an extract from go-gitea#19448 Signed-off-by: Andrew Thornton <[email protected]>
…osed It may be prudent to add runtime finalizers to the git.Repository and git.blobReader objects to absolutely ensure that these are both properly cancelled, cleaned and closed out. This commit is a backport of an extract from go-gitea#19448 Signed-off-by: Andrew Thornton <[email protected]>
5075b10
to
051fe40
Compare
Looks like the race detector doesn't like the PipePair.Close() function because it has to close both ends of the pipes in different go-routines to the ones that read them. This is frustrating. |
Use |
We'd need to lock the reads as well. |
…osed (#19495) (#19496) It may be prudent to add runtime finalizers to the git.Repository and git.blobReader objects to absolutely ensure that these are both properly cancelled, cleaned and closed out. This commit is a backport of an extract from #19448 Signed-off-by: Andrew Thornton <[email protected]>
If an `os/exec.Command` is passed non `*os.File` as an input/output, go will create `os.Pipe`s and wait for their closure in `cmd.Wait()`. If the code following this is responsible for closing `io.Pipe`s or other handlers then on process death from context cancellation the `Wait` can hang. There are two possible solutions: 1. use `os.Pipe` as the input/output as `cmd.Wait` does not wait for these. 2. create a goroutine waiting on the context cancellation that will close the inputs. This PR provides the second option - which is a simpler change that can be more easily backported. Closes go-gitea#19448 Signed-off-by: Andrew Thornton <[email protected]>
If an
os/exec.Command
is passed non*os.File
as an input/output, gowill create
os.Pipe
s and wait for their closure incmd.Wait()
.If the code following this is responsible for closing
io.Pipe
s orother handlers then on process death from context cancellation the
Wait
can hang.There are two possible solutions:
os.Pipe
as the input/output ascmd.Wait
does not wait for these.This PR changes the inputs to the
CatFileBatch
calls to useos.Pipe
s preventingdangling cat-file calls.
iterate on #19454
Signed-off-by: Andrew Thornton [email protected]