github.com/argoproj/argo-cd@v1.8.7/reposerver/repository/lock.go (about) 1 package repository 2 3 import ( 4 "io" 5 "sync" 6 7 ioutil "github.com/argoproj/argo-cd/util/io" 8 ) 9 10 func NewRepositoryLock() *repositoryLock { 11 return &repositoryLock{stateByKey: map[string]*repositoryState{}} 12 } 13 14 type repositoryLock struct { 15 lock sync.Mutex 16 stateByKey map[string]*repositoryState 17 } 18 19 // Lock acquires lock unless lock is already acquired with the same commit and allowConcurrent is set to true 20 func (r *repositoryLock) Lock(path string, revision string, allowConcurrent bool, init func() error) (io.Closer, error) { 21 r.lock.Lock() 22 state, ok := r.stateByKey[path] 23 if !ok { 24 state = &repositoryState{cond: &sync.Cond{L: &sync.Mutex{}}} 25 r.stateByKey[path] = state 26 } 27 r.lock.Unlock() 28 29 closer := ioutil.NewCloser(func() error { 30 state.cond.L.Lock() 31 notify := false 32 state.processCount-- 33 if state.processCount == 0 { 34 notify = true 35 state.revision = "" 36 } 37 state.cond.L.Unlock() 38 if notify { 39 state.cond.Broadcast() 40 } 41 return nil 42 }) 43 44 for { 45 state.cond.L.Lock() 46 if state.revision == "" { 47 // no in progress operation for that repo. Go ahead. 48 if err := init(); err != nil { 49 state.cond.L.Unlock() 50 return nil, err 51 } 52 53 state.revision = revision 54 state.processCount = 1 55 state.allowConcurrent = allowConcurrent 56 state.cond.L.Unlock() 57 return closer, nil 58 } else if state.revision == revision && state.allowConcurrent && allowConcurrent { 59 // same revision already processing and concurrent processing allowed. Increment process count and go ahead. 60 state.processCount++ 61 state.cond.L.Unlock() 62 return closer, nil 63 } else { 64 state.cond.Wait() 65 // wait when all in-flight processes of this revision complete and try again 66 state.cond.L.Unlock() 67 } 68 } 69 } 70 71 type repositoryState struct { 72 cond *sync.Cond 73 revision string 74 processCount int 75 allowConcurrent bool 76 }