Skip to content
This repository was archived by the owner on Dec 17, 2025. It is now read-only.

Commit 5f01ae3

Browse files
committed
Trying a file lock strategy to handle autoupdate
1 parent 5f67be8 commit 5f01ae3

File tree

3 files changed

+56
-33
lines changed

3 files changed

+56
-33
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/BurntSushi/toml v1.5.0
77
github.com/Masterminds/semver/v3 v3.4.0
88
github.com/creativeprojects/go-selfupdate v1.5.1
9+
github.com/gofrs/flock v0.13.0
910
github.com/google/uuid v1.6.0
1011
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1112
github.com/schollz/progressbar/v3 v3.18.0
@@ -24,8 +25,10 @@ require (
2425
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
2526
github.com/hashicorp/go-version v1.7.0 // indirect
2627
github.com/inconshreveable/mousetrap v1.1.0 // indirect
28+
github.com/kr/text v0.2.0 // indirect
2729
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
2830
github.com/rivo/uniseg v0.4.7 // indirect
31+
github.com/rogpeppe/go-internal v1.14.1 // indirect
2932
github.com/spf13/pflag v1.0.10 // indirect
3033
github.com/ulikunitz/xz v0.5.14 // indirect
3134
github.com/xanzy/go-gitlab v0.115.0 // indirect

go.sum

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
99
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
1010
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
1111
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
12+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
1213
github.com/creativeprojects/go-selfupdate v1.5.1 h1:fuyEGFFfqcC8SxDGolcEPYPLXGQ9Mcrc5uRyRG2Mqnk=
1314
github.com/creativeprojects/go-selfupdate v1.5.1/go.mod h1:2uY75rP8z/D/PBuDn6mlBnzu+ysEmwOJfcgF8np0JIM=
1415
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -19,6 +20,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
1920
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
2021
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
2122
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
23+
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
24+
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
2225
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2326
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
2427
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -39,6 +42,10 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
3942
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
4043
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
4144
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
45+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
46+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
47+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
48+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
4249
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
4350
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
4451
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -53,6 +60,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
5360
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5461
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
5562
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
63+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
64+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
5665
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5766
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
5867
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
@@ -94,8 +103,9 @@ golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
94103
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
95104
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
96105
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
97-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
98106
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
107+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
108+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
99109
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
100110
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
101111
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

internal/repository/gitrepo.go

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import (
1111
"path/filepath"
1212
"strconv"
1313
"strings"
14-
"sync"
1514
"text/template"
15+
"time"
1616

17+
"github.com/gofrs/flock"
1718
"github.com/sleuth-io/skills/internal/cache"
1819
"github.com/sleuth-io/skills/internal/constants"
1920
"github.com/sleuth-io/skills/internal/git"
@@ -38,27 +39,6 @@ const (
3839
readmeTemplateVersion = "1"
3940
)
4041

41-
var (
42-
// Global mutex map to protect concurrent access to git repositories
43-
// Key is the repository URL
44-
repoMutexes = make(map[string]*sync.Mutex)
45-
repoMutexLock sync.Mutex
46-
)
47-
48-
// getRepoMutex returns a mutex for the given repository URL
49-
func getRepoMutex(repoURL string) *sync.Mutex {
50-
repoMutexLock.Lock()
51-
defer repoMutexLock.Unlock()
52-
53-
if mu, exists := repoMutexes[repoURL]; exists {
54-
return mu
55-
}
56-
57-
mu := &sync.Mutex{}
58-
repoMutexes[repoURL] = mu
59-
return mu
60-
}
61-
6242
// GitRepository implements Repository for Git repositories
6343
type GitRepository struct {
6444
repoURL string
@@ -67,7 +47,6 @@ type GitRepository struct {
6747
httpHandler *HTTPSourceHandler
6848
pathHandler *PathSourceHandler
6949
gitHandler *GitSourceHandler
70-
mu *sync.Mutex // Per-repo mutex for git operations
7150
}
7251

7352
// NewGitRepository creates a new Git repository
@@ -88,7 +67,6 @@ func NewGitRepository(repoURL string) (*GitRepository, error) {
8867
httpHandler: NewHTTPSourceHandler(""), // No auth token for git repos
8968
pathHandler: NewPathSourceHandler(repoPath), // Use repo path for relative paths
9069
gitHandler: NewGitSourceHandler(gitClient),
91-
mu: getRepoMutex(repoURL), // Get or create mutex for this repo
9270
}, nil
9371
}
9472

@@ -100,11 +78,37 @@ func (g *GitRepository) Authenticate(ctx context.Context) (string, error) {
10078
return "", nil
10179
}
10280

81+
// acquireFileLock acquires a file lock for the git repository to prevent cross-process conflicts
82+
func (g *GitRepository) acquireFileLock(ctx context.Context) (*flock.Flock, error) {
83+
lockFile := filepath.Join(g.repoPath, ".lock")
84+
85+
// Ensure parent directory exists
86+
if err := os.MkdirAll(filepath.Dir(lockFile), 0755); err != nil {
87+
return nil, fmt.Errorf("failed to create lock directory: %w", err)
88+
}
89+
90+
fileLock := flock.New(lockFile)
91+
92+
// Try to acquire the lock with a timeout
93+
locked, err := fileLock.TryLockContext(ctx, 100*time.Millisecond)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to acquire file lock: %w", err)
96+
}
97+
if !locked {
98+
return nil, fmt.Errorf("could not acquire file lock (timeout)")
99+
}
100+
101+
return fileLock, nil
102+
}
103+
103104
// GetLockFile retrieves the lock file from the Git repository
104105
func (g *GitRepository) GetLockFile(ctx context.Context, cachedETag string) (content []byte, etag string, notModified bool, err error) {
105-
// Lock to prevent concurrent git operations on the same repository
106-
g.mu.Lock()
107-
defer g.mu.Unlock()
106+
// Acquire file lock to prevent concurrent git operations (both in-process and cross-process)
107+
fileLock, err := g.acquireFileLock(ctx)
108+
if err != nil {
109+
return nil, "", false, fmt.Errorf("failed to acquire lock: %w", err)
110+
}
111+
defer func() { _ = fileLock.Unlock() }()
108112

109113
// Clone or update repository
110114
if err := g.cloneOrUpdate(ctx); err != nil {
@@ -131,8 +135,11 @@ func (g *GitRepository) GetLockFile(ctx context.Context, cachedETag string) (con
131135
func (g *GitRepository) GetArtifact(ctx context.Context, artifact *lockfile.Artifact) ([]byte, error) {
132136
// Lock only for path-based artifacts that read from the repository
133137
if artifact.GetSourceType() == "path" {
134-
g.mu.Lock()
135-
defer g.mu.Unlock()
138+
fileLock, err := g.acquireFileLock(ctx)
139+
if err != nil {
140+
return nil, fmt.Errorf("failed to acquire lock: %w", err)
141+
}
142+
defer func() { _ = fileLock.Unlock() }()
136143
}
137144

138145
// Dispatch to appropriate source handler based on artifact source type
@@ -150,9 +157,12 @@ func (g *GitRepository) GetArtifact(ctx context.Context, artifact *lockfile.Arti
150157

151158
// AddArtifact uploads an artifact to the Git repository
152159
func (g *GitRepository) AddArtifact(ctx context.Context, artifact *lockfile.Artifact, zipData []byte) error {
153-
// Lock to prevent concurrent git operations
154-
g.mu.Lock()
155-
defer g.mu.Unlock()
160+
// Acquire file lock to prevent concurrent git operations
161+
fileLock, err := g.acquireFileLock(ctx)
162+
if err != nil {
163+
return fmt.Errorf("failed to acquire lock: %w", err)
164+
}
165+
defer func() { _ = fileLock.Unlock() }()
156166

157167
// Clone or update repository
158168
if err := g.cloneOrUpdate(ctx); err != nil {

0 commit comments

Comments
 (0)