Skip to content

Commit 18bcaa4

Browse files
committed
fix
1 parent c88e71c commit 18bcaa4

File tree

4 files changed

+96
-15
lines changed

4 files changed

+96
-15
lines changed

modules/git/command.go

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type Command struct {
4646
args []string
4747
globalArgsLength int
4848
brokenArgs []string
49+
cmd *exec.Cmd // for debug purpose only
4950
}
5051

5152
func logArgSanitize(arg string) string {
@@ -314,6 +315,7 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
314315
startTime := time.Now()
315316

316317
cmd := exec.CommandContext(ctx, c.prog, c.args...)
318+
c.cmd = cmd // for debug purpose only
317319
if opts.Env == nil {
318320
cmd.Env = os.Environ()
319321
} else {

modules/git/repo_attribute.go

+32-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"fmt"
1010
"io"
1111
"os"
12+
"path/filepath"
13+
"time"
1214

1315
"code.gitea.io/gitea/modules/log"
1416
)
@@ -102,7 +104,7 @@ type CheckAttributeReader struct {
102104

103105
stdinReader io.ReadCloser
104106
stdinWriter *os.File
105-
stdOut attributeWriter
107+
stdOut *nulSeparatedAttributeWriter
106108
cmd *Command
107109
env []string
108110
ctx context.Context
@@ -152,7 +154,6 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
152154
return nil
153155
}
154156

155-
// Run run cmd
156157
func (c *CheckAttributeReader) Run() error {
157158
defer func() {
158159
_ = c.stdinReader.Close()
@@ -176,7 +177,7 @@ func (c *CheckAttributeReader) Run() error {
176177
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
177178
defer func() {
178179
if err != nil && err != c.ctx.Err() {
179-
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
180+
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
180181
}
181182
}()
182183

@@ -191,9 +192,31 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
191192
return nil, err
192193
}
193194

195+
reportTimeout := func() error {
196+
stdOutClosed := false
197+
select {
198+
case <-c.stdOut.closed:
199+
stdOutClosed = true
200+
default:
201+
}
202+
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
203+
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
204+
if c.cmd.cmd != nil {
205+
debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String())
206+
}
207+
_ = c.Close()
208+
return fmt.Errorf("CheckPath timeout: %s", debugMsg)
209+
}
210+
194211
rs = make(map[string]string)
195212
for range c.Attributes {
196213
select {
214+
case <-time.After(5 * time.Second):
215+
// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath
216+
// So add a timeout here to mitigate the problem, and output more logs for debug purpose
217+
// In real world, if CheckPath runs long than seconds, it blocks the end user's operation,
218+
// and at the moment the CheckPath result is not so important, so we can just ignore it.
219+
return nil, reportTimeout()
197220
case attr, ok := <-c.stdOut.ReadAttribute():
198221
if !ok {
199222
return nil, c.ctx.Err()
@@ -206,18 +229,12 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
206229
return rs, nil
207230
}
208231

209-
// Close close pip after use
210232
func (c *CheckAttributeReader) Close() error {
211233
c.cancel()
212234
err := c.stdinWriter.Close()
213235
return err
214236
}
215237

216-
type attributeWriter interface {
217-
io.WriteCloser
218-
ReadAttribute() <-chan attributeTriple
219-
}
220-
221238
type attributeTriple struct {
222239
Filename string
223240
Attribute string
@@ -281,7 +298,7 @@ func (wr *nulSeparatedAttributeWriter) Close() error {
281298
return nil
282299
}
283300

284-
// Create a check attribute reader for the current repository and provided commit ID
301+
// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID
285302
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
286303
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
287304
if err != nil {
@@ -303,21 +320,21 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
303320
}
304321
ctx, cancel := context.WithCancel(repo.Ctx)
305322
if err := checker.Init(ctx); err != nil {
306-
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
323+
log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err)
307324
} else {
308325
go func() {
309326
err := checker.Run()
310-
if err != nil && err != ctx.Err() {
311-
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
327+
if err != nil && !IsErrCanceledOrKilled(err) {
328+
log.Error("Attribute checker for commit %s exits with error: %v", commitID, err)
312329
}
313330
cancel()
314331
}()
315332
}
316-
deferable := func() {
333+
deferrable := func() {
317334
_ = checker.Close()
318335
cancel()
319336
deleteTemporaryFile()
320337
}
321338

322-
return checker, deferable
339+
return checker, deferrable
323340
}

modules/git/repo_attribute_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
package git
55

66
import (
7+
"context"
8+
mathRand "math/rand/v2"
9+
"path/filepath"
10+
"slices"
11+
"sync"
712
"testing"
813
"time"
914

1015
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
1117
)
1218

1319
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
@@ -95,3 +101,57 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
95101
Value: "unspecified",
96102
}, attr)
97103
}
104+
105+
func TestAttributeReader(t *testing.T) {
106+
t.Skip() // for debug purpose only, do not run in CI
107+
108+
ctx := t.Context()
109+
110+
timeout := 1 * time.Second
111+
repoPath := filepath.Join(testReposDir, "language_stats_repo")
112+
commitRef := "HEAD"
113+
114+
oneRound := func(t *testing.T, roundIdx int) {
115+
ctx, cancel := context.WithTimeout(ctx, timeout)
116+
_ = cancel
117+
gitRepo, err := OpenRepository(ctx, repoPath)
118+
require.NoError(t, err)
119+
defer gitRepo.Close()
120+
121+
commit, err := gitRepo.GetCommit(commitRef)
122+
require.NoError(t, err)
123+
124+
files, err := gitRepo.LsFiles()
125+
require.NoError(t, err)
126+
127+
randomFiles := slices.Clone(files)
128+
randomFiles = append(randomFiles, "any-file-1", "any-file-2")
129+
130+
t.Logf("Round %v with %d files", roundIdx, len(randomFiles))
131+
132+
attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String())
133+
defer deferrable()
134+
135+
wg := sync.WaitGroup{}
136+
wg.Add(1)
137+
138+
go func() {
139+
for {
140+
file := randomFiles[mathRand.IntN(len(randomFiles))]
141+
_, err := attrReader.CheckPath(file)
142+
if err != nil {
143+
for i := 0; i < 10; i++ {
144+
_, _ = attrReader.CheckPath(file)
145+
}
146+
break
147+
}
148+
}
149+
wg.Done()
150+
}()
151+
wg.Wait()
152+
}
153+
154+
for i := 0; i < 100; i++ {
155+
oneRound(t, i)
156+
}
157+
}

services/gitdiff/gitdiff.go

+2
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,8 @@ func GetDiffForRender(ctx context.Context, gitRepo *git.Repository, opts *DiffOp
12531253
if language.Has() {
12541254
diffFile.Language = language.Value()
12551255
}
1256+
} else {
1257+
checker = nil // CheckPath fails, it's not impossible to "check" anymore
12561258
}
12571259
}
12581260

0 commit comments

Comments
 (0)