@@ -22,17 +22,29 @@ import (
22
22
23
23
//go:generate pegomock generate --package mocks -o mocks/mock_working_dir_locker.go WorkingDirLocker
24
24
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
+
25
34
// WorkingDirLocker is used to prevent multiple commands from executing
26
35
// at the same time for a single repo, pull, and workspace. We need to prevent
27
36
// this from happening because a specific repo/pull/workspace has a single workspace
28
37
// on disk and we haven't written Atlantis (yet) to handle concurrent execution
29
38
// within this workspace.
30
39
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.
32
41
// It returns a function that should be used to unlock the workspace and
33
42
// an error if the workspace is already locked. The error is expected to
34
43
// 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
+
36
48
// TryLockPull tries to acquire a lock for all the workspaces in this repo
37
49
// and pull.
38
50
// It returns a function that should be used to unlock the workspace and
@@ -56,8 +68,8 @@ type DefaultWorkingDirLocker struct {
56
68
57
69
// NewDefaultWorkingDirLocker is a constructor.
58
70
func NewDefaultWorkingDirLocker (lockAcquireTimeoutSeconds int ) * DefaultWorkingDirLocker {
59
- if lockAcquireTimeoutSeconds < 1 {
60
- lockAcquireTimeoutSeconds = 1
71
+ if lockAcquireTimeoutSeconds < 0 {
72
+ lockAcquireTimeoutSeconds = 0
61
73
}
62
74
63
75
return & DefaultWorkingDirLocker {
@@ -83,7 +95,7 @@ func (d *DefaultWorkingDirLocker) TryLockPull(repoFullName string, pullNum int)
83
95
}, nil
84
96
}
85
97
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 ) {
87
99
ticker := time .NewTicker (500 * time .Millisecond )
88
100
timeout := time .NewTimer (time .Duration (d .lockAcquireTimeoutSeconds ) * time .Second )
89
101
@@ -97,8 +109,21 @@ func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, work
97
109
" command that is running for this pull request.\n " +
98
110
"Wait until the previous command is complete and try again" , workspace , path )
99
111
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 {
102
127
return func () {
103
128
d .unlock (repoFullName , pullNum , workspace , path )
104
129
}, nil
@@ -107,22 +132,27 @@ func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, pullNum int, work
107
132
}
108
133
}
109
134
110
- func (d * DefaultWorkingDirLocker ) tryAcquireLock (pullKey string , workspaceKey string ) bool {
135
+ func (d * DefaultWorkingDirLocker ) tryAcquireLock (pullKey string , workspaceKey string ) ( bool , bool ) {
111
136
d .mutex .Lock ()
112
137
defer d .mutex .Unlock ()
113
138
114
- acquireLock := true
139
+ pullInUse := false
140
+ workspaceInUse := false
115
141
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
118
148
}
119
149
}
120
150
121
- if acquireLock {
151
+ if ! pullInUse && ! workspaceInUse {
122
152
d .locks = append (d .locks , workspaceKey )
123
153
}
124
154
125
- return acquireLock
155
+ return pullInUse , workspaceInUse
126
156
}
127
157
128
158
// Unlock unlocks the workspace for this pull.
0 commit comments