Skip to content

Commit 2ffd1ef

Browse files
committed
Add TryLockWithRetry in favor of TryLock
1 parent a588210 commit 2ffd1ef

5 files changed

+46
-25
lines changed

server/events/post_workflow_hooks_command_runner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (w *DefaultPostWorkflowHooksCommandRunner) RunPostHooks(ctx *command.Contex
5252

5353
ctx.Log.Info("Post-workflow hooks configured, running...")
5454

55-
unlockFn, err := w.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, DefaultWorkspace, DefaultRepoRelDir)
55+
unlockFn, err := w.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, DefaultWorkspace, DefaultRepoRelDir, false, false)
5656
if err != nil {
5757
return err
5858
}

server/events/pre_workflow_hooks_command_runner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (w *DefaultPreWorkflowHooksCommandRunner) RunPreHooks(ctx *command.Context,
5252

5353
ctx.Log.Info("Pre-workflow hooks configured, running...")
5454

55-
unlockFn, err := w.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, DefaultWorkspace, DefaultRepoRelDir)
55+
unlockFn, err := w.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, DefaultWorkspace, DefaultRepoRelDir, false, false)
5656
if err != nil {
5757
return err
5858
}

server/events/project_command_builder.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ func (p *DefaultProjectCommandBuilder) buildAllCommandsByCfg(ctx *command.Contex
389389
// Need to lock the workspace we're about to clone to.
390390
workspace := DefaultWorkspace
391391

392-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir)
392+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir, false, false)
393393
if err != nil {
394394
ctx.Log.Warn("workspace was locked")
395395
return nil, err
@@ -587,7 +587,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectPlanCommand(ctx *command.Cont
587587
var pcc []command.ProjectContext
588588

589589
ctx.Log.Debug("building plan command")
590-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir)
590+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir, false, false)
591591
if err != nil {
592592
return pcc, err
593593
}
@@ -806,7 +806,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommand(ctx *command.Context,
806806
}
807807

808808
var projCtx []command.ProjectContext
809-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir)
809+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, workspace, DefaultRepoRelDir, false, false)
810810
if err != nil {
811811
return projCtx, err
812812
}

server/events/project_command_runner.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ func (p *DefaultProjectCommandRunner) doApprovePolicies(ctx command.ProjectConte
354354
ctx.Log.Debug("acquired lock for project")
355355

356356
// Acquire internal lock for the directory we're going to operate in.
357-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
357+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
358358
if err != nil {
359359
return nil, "", err
360360
}
@@ -458,7 +458,7 @@ func (p *DefaultProjectCommandRunner) doPolicyCheck(ctx command.ProjectContext)
458458
// Acquire internal lock for the directory we're going to operate in.
459459
// We should refactor this to keep the lock for the duration of plan and policy check since as of now
460460
// there is a small gap where we don't have the lock and if we can't get this here, we should just unlock the PR.
461-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
461+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
462462
if err != nil {
463463
return nil, "", err
464464
}
@@ -574,7 +574,7 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx command.ProjectContext) (*model
574574
ctx.Log.Debug("acquired lock for project")
575575

576576
// Acquire internal lock for the directory we're going to operate in.
577-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
577+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
578578
if err != nil {
579579
return nil, "", err
580580
}
@@ -651,7 +651,7 @@ func (p *DefaultProjectCommandRunner) doApply(ctx command.ProjectContext) (apply
651651
ctx.Log.Debug("acquired lock for project")
652652

653653
// Acquire internal lock for the directory we're going to operate in.
654-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
654+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
655655
if err != nil {
656656
return "", "", err
657657
}
@@ -689,7 +689,7 @@ func (p *DefaultProjectCommandRunner) doVersion(ctx command.ProjectContext) (ver
689689
}
690690

691691
// Acquire internal lock for the directory we're going to operate in.
692-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
692+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
693693
if err != nil {
694694
return "", "", err
695695
}
@@ -730,7 +730,7 @@ func (p *DefaultProjectCommandRunner) doImport(ctx command.ProjectContext) (out
730730
ctx.Log.Debug("acquired lock for project")
731731

732732
// Acquire internal lock for the directory we're going to operate in.
733-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
733+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
734734
if err != nil {
735735
return nil, "", err
736736
}
@@ -771,7 +771,7 @@ func (p *DefaultProjectCommandRunner) doStateRm(ctx command.ProjectContext) (out
771771
ctx.Log.Debug("acquired lock for project")
772772

773773
// Acquire internal lock for the directory we're going to operate in.
774-
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir)
774+
unlockFn, err := p.WorkingDirLocker.TryLockWithRetry(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace, ctx.RepoRelDir, false, false)
775775
if err != nil {
776776
return nil, "", err
777777
}

server/events/working_dir_locker.go

+34-13
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ import (
2828
// on disk and we haven't written Atlantis (yet) to handle concurrent execution
2929
// within this workspace.
3030
type WorkingDirLocker interface {
31-
// TryLock tries to acquire a lock for this repo, pull, workspace, and path.
31+
// TryLockWithRetry tries to acquire a lock for this repo, pull, workspace, and path.
3232
// It returns a function that should be used to unlock the workspace and
3333
// an error if the workspace is already locked. The error is expected to
3434
// be printed to the pull request.
35-
TryLock(repoFullName string, pullNum int, workspace string, path string) (func(), error)
35+
// The caller can define if the function should automatically retry to acquire the lock
36+
// if either the pull request or the workspace is locked already.
37+
TryLockWithRetry(repoFullName string, pullNum int, workspace string, path string, retryWorkspaceLocked bool, retryPullLocked bool) (func(), error)
38+
3639
// TryLockPull tries to acquire a lock for all the workspaces in this repo
3740
// and pull.
3841
// It returns a function that should be used to unlock the workspace and
@@ -56,8 +59,8 @@ type DefaultWorkingDirLocker struct {
5659

5760
// NewDefaultWorkingDirLocker is a constructor.
5861
func NewDefaultWorkingDirLocker(lockAcquireTimeoutSeconds int) *DefaultWorkingDirLocker {
59-
if lockAcquireTimeoutSeconds < 1 {
60-
lockAcquireTimeoutSeconds = 1
62+
if lockAcquireTimeoutSeconds < 0 {
63+
lockAcquireTimeoutSeconds = 0
6164
}
6265

6366
return &DefaultWorkingDirLocker{
@@ -83,7 +86,7 @@ func (d *DefaultWorkingDirLocker) TryLockPull(repoFullName string, pullNum int)
8386
}, nil
8487
}
8588

86-
func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, workspace string, path string) (func(), error) {
89+
func (d *DefaultWorkingDirLocker) TryLockWithRetry(repoFullName string, pullNum int, workspace string, path string, retryWorkspaceLocked bool, retryPullLocked bool) (func(), error) {
8790
ticker := time.NewTicker(500 * time.Millisecond)
8891
timeout := time.NewTimer(time.Duration(d.lockAcquireTimeoutSeconds) * time.Second)
8992

@@ -97,8 +100,21 @@ func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, work
97100
" command that is running for this pull request.\n"+
98101
"Wait until the previous command is complete and try again", workspace, path)
99102
case <-ticker.C:
100-
lockAcquired := d.tryAcquireLock(pullKey, workspaceKey)
101-
if lockAcquired {
103+
pullInUse, workspaceInUse := d.tryAcquireLock(pullKey, workspaceKey)
104+
105+
if pullInUse && !retryPullLocked {
106+
return func() {}, fmt.Errorf("the %s workspace at path %s is currently locked by another"+
107+
" command that is running for this pull request.\n"+
108+
"Wait until the previous command is complete and try again", workspace, path)
109+
}
110+
111+
if workspaceInUse && !retryWorkspaceLocked{
112+
return func() {}, fmt.Errorf("the %s workspace at path %s is currently locked by another"+
113+
" command that is running for this pull request.\n"+
114+
"Wait until the previous command is complete and try again", workspace, path)
115+
}
116+
117+
if !workspaceInUse && !pullInUse {
102118
return func() {
103119
d.unlock(repoFullName, pullNum, workspace, path)
104120
}, nil
@@ -107,22 +123,27 @@ func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, work
107123
}
108124
}
109125

110-
func (d *DefaultWorkingDirLocker) tryAcquireLock(pullKey string, workspaceKey string) bool {
126+
func (d *DefaultWorkingDirLocker) tryAcquireLock(pullKey string, workspaceKey string) (bool, bool) {
111127
d.mutex.Lock()
112128
defer d.mutex.Unlock()
113129

114-
acquireLock := true
130+
pullInUse := false
131+
workspaceInUse := false
115132
for _, l := range d.locks {
116-
if l == pullKey || l == workspaceKey {
117-
acquireLock = false
133+
if l == pullKey {
134+
pullInUse = true
135+
}
136+
137+
if l == workspaceKey {
138+
workspaceInUse = true
118139
}
119140
}
120141

121-
if acquireLock {
142+
if !pullInUse && !workspaceInUse {
122143
d.locks = append(d.locks, workspaceKey)
123144
}
124145

125-
return acquireLock
146+
return pullInUse, workspaceInUse
126147
}
127148

128149
// Unlock unlocks the workspace for this pull.

0 commit comments

Comments
 (0)