Skip to content

Commit 2471c31

Browse files
committed
Add TryLockWithRetry in favor of TryLock
1 parent a588210 commit 2471c31

5 files changed

+55
-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

+43-13
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,29 @@ import (
2222

2323
//go:generate pegomock generate --package mocks -o mocks/mock_working_dir_locker.go WorkingDirLocker
2424

25+
type WorkingDirLockerRetryPolicy string
26+
27+
const (
28+
WorkingDirLockerRetryPolicyNoneLocked WorkingDirLockerRetryPolicy = "none-locked"
29+
WorkingDirLockerRetryPolicyAnyLocked WorkingDirLockerRetryPolicy = "any-locked"
30+
WorkingDirLockerRetryPolicyPullLocked WorkingDirLockerRetryPolicy = "pull-locked"
31+
WorkingDirLockerRetryPolicyWorkspaceLocked WorkingDirLockerRetryPolicy = "workspace-locked"
32+
)
33+
2534
// WorkingDirLocker is used to prevent multiple commands from executing
2635
// at the same time for a single repo, pull, and workspace. We need to prevent
2736
// this from happening because a specific repo/pull/workspace has a single workspace
2837
// on disk and we haven't written Atlantis (yet) to handle concurrent execution
2938
// within this workspace.
3039
type WorkingDirLocker interface {
31-
// TryLock tries to acquire a lock for this repo, pull, workspace, and path.
40+
// TryLockWithRetry tries to acquire a lock for this repo, pull, workspace, and path.
3241
// It returns a function that should be used to unlock the workspace and
3342
// an error if the workspace is already locked. The error is expected to
3443
// be printed to the pull request.
35-
TryLock(repoFullName string, pullNum int, workspace string, path string) (func(), error)
44+
// The caller can define if the function should automatically retry to acquire the lock
45+
// if either the pull request or the workspace is locked already.
46+
TryLockWithRetry(repoFullName string, pullNum int, workspace string, path string, retryWorkspaceLocked bool, retryPullLocked bool) (func(), error)
47+
3648
// TryLockPull tries to acquire a lock for all the workspaces in this repo
3749
// and pull.
3850
// It returns a function that should be used to unlock the workspace and
@@ -56,8 +68,8 @@ type DefaultWorkingDirLocker struct {
5668

5769
// NewDefaultWorkingDirLocker is a constructor.
5870
func NewDefaultWorkingDirLocker(lockAcquireTimeoutSeconds int) *DefaultWorkingDirLocker {
59-
if lockAcquireTimeoutSeconds < 1 {
60-
lockAcquireTimeoutSeconds = 1
71+
if lockAcquireTimeoutSeconds < 0 {
72+
lockAcquireTimeoutSeconds = 0
6173
}
6274

6375
return &DefaultWorkingDirLocker{
@@ -83,7 +95,7 @@ func (d *DefaultWorkingDirLocker) TryLockPull(repoFullName string, pullNum int)
8395
}, nil
8496
}
8597

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

@@ -97,8 +109,21 @@ func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, work
97109
" command that is running for this pull request.\n"+
98110
"Wait until the previous command is complete and try again", workspace, path)
99111
case <-ticker.C:
100-
lockAcquired := d.tryAcquireLock(pullKey, workspaceKey)
101-
if lockAcquired {
112+
pullInUse, workspaceInUse := d.tryAcquireLock(pullKey, workspaceKey)
113+
114+
if pullInUse && !retryPullLocked {
115+
return func() {}, fmt.Errorf("the %s workspace at path %s is currently locked by another"+
116+
" command that is running for this pull request.\n"+
117+
"Wait until the previous command is complete and try again", workspace, path)
118+
}
119+
120+
if workspaceInUse && !retryWorkspaceLocked{
121+
return func() {}, fmt.Errorf("the %s workspace at path %s is currently locked by another"+
122+
" command that is running for this pull request.\n"+
123+
"Wait until the previous command is complete and try again", workspace, path)
124+
}
125+
126+
if !workspaceInUse && !pullInUse {
102127
return func() {
103128
d.unlock(repoFullName, pullNum, workspace, path)
104129
}, nil
@@ -107,22 +132,27 @@ func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, work
107132
}
108133
}
109134

110-
func (d *DefaultWorkingDirLocker) tryAcquireLock(pullKey string, workspaceKey string) bool {
135+
func (d *DefaultWorkingDirLocker) tryAcquireLock(pullKey string, workspaceKey string) (bool, bool) {
111136
d.mutex.Lock()
112137
defer d.mutex.Unlock()
113138

114-
acquireLock := true
139+
pullInUse := false
140+
workspaceInUse := false
115141
for _, l := range d.locks {
116-
if l == pullKey || l == workspaceKey {
117-
acquireLock = false
142+
if l == pullKey {
143+
pullInUse = true
144+
}
145+
146+
if l == workspaceKey {
147+
workspaceInUse = true
118148
}
119149
}
120150

121-
if acquireLock {
151+
if !pullInUse && !workspaceInUse {
122152
d.locks = append(d.locks, workspaceKey)
123153
}
124154

125-
return acquireLock
155+
return pullInUse, workspaceInUse
126156
}
127157

128158
// Unlock unlocks the workspace for this pull.

0 commit comments

Comments
 (0)