@@ -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
6343type 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
104105func (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
131135func (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
152159func (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