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

Ignore Sync errors on pipes when doing CheckAttributeReader.CheckPath, fix the hang of git cat-file #17096

Merged
merged 8 commits into from
Sep 20, 2021

Conversation

wxiaoguang
Copy link
Contributor

@wxiaoguang wxiaoguang commented Sep 19, 2021

It is not necessary to call Sync on pipes. And the Sync usually results in error: "sync |1: invalid argument".

Ref: https://man7.org/linux/man-pages/man2/fdatasync.2.html :

EROFS, EINVAL:  fd is bound to a special file (e.g., a pipe, FIFO, or socket) which does not support synchronization.

If we return early from CheckPath when Sync returns error, the GetLanguageStats will hang forever (because the stdin's buffer will be fully filled, then Write in CheckPath blocks)

But I can not confirm the Sync behaviors on different platforms, so I think it's OK to keep it while ignore the returned error.

Related issues:

Bug report:

Add caller to cat-file batch calls for debug:

@codecov-commenter

This comment has been minimized.

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Sep 19, 2021
@zeripath
Copy link
Contributor

zeripath commented Sep 19, 2021

This was my worry when we added the error check here in the first place.

But I couldn't force it to happen myself - Did you actually get this error reported? If you didn't - it's possible something else is actually happening:

I added the Sync because I was also seeing an unusual hold-up likely because the \x00 is not recognised as a line and because the buffer on a pipe can be up to 64Kb. The Sync appeared to cause a flush in my testing so the unexpected hold-up disappeared.

Now if you're experiencing some block then the Sync may not be enough to solve the problem - especially if you're not actually seeing the above error that you were expecting.

@zeripath
Copy link
Contributor

Looking again at the code - I think you're likely suffering from the problem I was experiencing earlier.

Can you repeatedly replicate this issue?

@zeripath
Copy link
Contributor

diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 0bd7d7e49..f001e65e3 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -112,13 +112,15 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
 
 	if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
 		cmdArgs = append(cmdArgs, "--cached")
-		c.env = []string{"GIT_INDEX_FILE=" + c.IndexFile}
+		c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile)
 	}
 
 	if len(c.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
-		c.env = []string{"GIT_WORK_TREE=" + c.WorkTree}
+		c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree)
 	}
 
+	c.env = append(c.env, "GIT_FLUSH=1")
+
 	if len(c.Attributes) > 0 {
 		cmdArgs = append(cmdArgs, c.Attributes...)
 		cmdArgs = append(cmdArgs, "--")

Could you check if this helps?

@wxiaoguang
Copy link
Contributor Author

wxiaoguang commented Sep 20, 2021

Your patch is good, but it doesn't help about this problem. They are different problems.

Linux document has said that a pipe should NOT be fsync-ed ( https://man7.org/linux/man-pages/man2/fdatasync.2.html )

So I think my patch makes the problem better than before.

On you side, the Sync is still been called, so you do not worry about buffered data.

On my side, the Sync error is ignored, the whole logic is correct.

If one day we have enough confidence about the pipe's behavior on all platforms, we can optimize this logic again.

ps: I have written a test program to test Sync, the result is:

2021/09/20 12:44:32 GOOS=darwin
2021/09/20 12:44:32 pipeWriter.Sync error: sync |1: bad file descriptor
2021/09/20 12:52:25 GOOS=linux
2021/09/20 12:52:25 pipeWriter.Sync error: sync |1: invalid argument

It's obviously wrong to call Sync on pipes in Linux / macOS.

Test Program:

package main

import (
	"log"
	"os"
	"os/exec"
	"runtime"
)

func main() {
	log.Printf("GOOS=%v\n", runtime.GOOS)

	var err error
	pipeReader, pipeWriter, err := os.Pipe()
	if err != nil {
		log.Fatalf("os.Pipe error: %v", err)
	}

	cmd := exec.Command("/bin/cat")
	cmd.Stdin = pipeReader
	err = cmd.Start()
	if err != nil {
		log.Fatalf("cmd.Start error: %v", err)
	}

	_, err = pipeWriter.WriteString("test")
	if err != nil {
		log.Fatalf("pipeWriter.Write error: %v", err)
	}

	err = pipeWriter.Sync()
	if err != nil {
		log.Fatalf("pipeWriter.Sync error: %v", err)
	}
}

@wxiaoguang
Copy link
Contributor Author

Did you actually get this error reported? If you didn't - it's possible something else is actually happening:
Can you repeatedly replicate this issue?

Both yes, I added many debug logs and I am sure that ignore the Sync error will make the logic correct, and CheckPath can successfully ReadAttribute without Sync on my side.

And you can refer the test program to test Sync on pipes.

@zeripath
Copy link
Contributor

I too am running on Linux but didn't get the sync error you do - otherwise I'd have never put it in. I even tested in a Windows virtual machine.

Hmm, are you building with go 1.17? I know that at some point go moved to use pipe2 - maybe that doesn't error on sync. Or maybe it's a kernel difference.


I also don't understand how an err at the Sync causes a hang. The logic there should abort and cancel the reader before a hang was possible at Read - at least that was how I intended it - so if there's a hang despite the error then that logic is failing somehow.

So I'm doubly confused and that makes me concerned that I've got the channel logic wrong somewhere in there - but I just can't see it.


Anyway if GIT_FLUSH env reliably prevents this hang along with ignoring the error on sync then I am happy.

But if the actual slow down/hang is not occurring because of the sync but at read - then you're likely suffering the same issue that the original native git implementation of commit info was suffering. That is, that although the write has been sent it's not reaching the git program or the git program's output is not reaching the read. If GIT_FLUSH prevents this then that was due to git buffering, if not then...

Perhaps placing a runtime.Gosched() in between the write and the read is needed? Another is changing the code to make things non-synchronous - that is push all of the paths in to git attribute channel, closing the writer at the end and reading them all. This would both fill the buffer and force go to schedule the other goroutines.

@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Sep 20, 2021
@wxiaoguang
Copy link
Contributor Author

wxiaoguang commented Sep 20, 2021

About environment

I have tested the Sync on pipes with:

  • Debian 10 Linux 4.19.0-16-amd64 SMP Debian 4.19.181-1 (2021-03-19) x86_64 GNU/Linux
    go version go1.16.2 linux/amd64
  • Debian 11 Linux 5.10.0-8-amd64 SMP Debian 5.10.46-4 (2021-08-03) x86_64 GNU/Linux
    go version go1.17 darwin/amd64 cross-compile with GOOS=linux
  • macOS 11.5.2
    go version go1.17 darwin/amd64

Sync on pipes always fails and reports error sync |1: bad file descriptor for macOS or sync |1: invalid argument for Linux.

I believe that since Linux document says that fsync on pipes will fail, then here is a problem.

Why there is a hang

It seems that there are other flaws when running git cat-file or git check-attr, eg: when the git process is killed by timeout, the go routine still exists, then I still can see a background git process has been running for over 3 hours (in the process.Manager), but I can not find the process in system's process list by ps.

Because there may be unknown bugs, when Sync returns an error and CheckPath returns early, there are more calls to CheckPath and continue to fill the stdin buffer, then the Write in CheckPath blocks.

zeripath and others added 3 commits September 20, 2021 14:22
Signed-off-by: Andrew Thornton <art27@cantab.net>
Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
@6543 6543 added the issue/regression Issue needs no code to be fixed, only a description on how to fix it yourself label Sep 20, 2021
@6543 6543 added this to the 1.16.0 milestone Sep 20, 2021
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Sep 20, 2021
@zeripath zeripath merged commit b231d0d into go-gitea:main Sep 20, 2021
@wxiaoguang wxiaoguang deleted the fix-check-path-sync branch September 22, 2021 06:07
@go-gitea go-gitea locked and limited conversation to collaborators Oct 19, 2021
@zeripath zeripath added the skip-changelog This PR is irrelevant for the (next) changelog, for example bug fixes for unreleased features. label Jan 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
issue/regression Issue needs no code to be fixed, only a description on how to fix it yourself lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. skip-changelog This PR is irrelevant for the (next) changelog, for example bug fixes for unreleased features.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CheckAttributeReader.CheckPath hangs forever, and make the background process never end
7 participants