github.com/argoproj/argo-cd/v2@v2.10.9/reposerver/repository/lock.go (about)

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