diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 2a7f14267..e81f84b4b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -31,6 +31,9 @@ jobs: - name: Test run: mkdir .cover && CARAPACE_COVERDIR="$(pwd)/.cover" go test -v -coverpkg ./... -coverprofile=unit.cov ./... ./example-nonposix/... + - name: Bench + run: go test -bench ./... + - name: Convert coverage run: go tool covdata textfmt -i .cover/ -o integration.cov diff --git a/carapace.go b/carapace.go index 8e81670a7..714139f69 100644 --- a/carapace.go +++ b/carapace.go @@ -72,7 +72,11 @@ func (c Carapace) DashAnyCompletion(action Action) { // FlagCompletion defines completion for flags using a map consisting of name and Action. func (c Carapace) FlagCompletion(actions ActionMap) { - if e := storage.get(c.cmd); e.flag == nil { + e := storage.get(c.cmd) + e.flagMutex.Lock() + defer e.flagMutex.Unlock() + + if e.flag == nil { e.flag = actions } else { for name, action := range actions { diff --git a/storage.go b/storage.go index c46fa1a7f..fe4b8c22e 100644 --- a/storage.go +++ b/storage.go @@ -15,6 +15,7 @@ import ( type entry struct { flag ActionMap + flagMutex sync.RWMutex positional []Action positionalAny Action dash []Action @@ -27,20 +28,22 @@ type entry struct { type _storage map[*cobra.Command]*entry -var storageMutex sync.Mutex +var storageMutex sync.RWMutex -func (s _storage) get(cmd *cobra.Command) (e *entry) { - var ok bool - if e, ok = s[cmd]; !ok { +func (s _storage) get(cmd *cobra.Command) *entry { + storageMutex.RLock() + e, ok := s[cmd] + storageMutex.RUnlock() + + if !ok { storageMutex.Lock() defer storageMutex.Unlock() - if e, ok = s[cmd]; !ok { e = &entry{} s[cmd] = e } } - return + return e } var bridgeMutex sync.Mutex diff --git a/storage_test.go b/storage_test.go index 464863316..729baf3bf 100644 --- a/storage_test.go +++ b/storage_test.go @@ -75,3 +75,23 @@ func TestCheck(t *testing.T) { t.Error("check should fail") } } + +// BenchmarkStorage tests for concurrent map read/write +func BenchmarkStorage(b *testing.B) { + cmd := &cobra.Command{} + cmd2 := &cobra.Command{} + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + Gen(cmd).FlagCompletion(ActionMap{ + "flag1": ActionValues("a", "b"), + }) + Gen(cmd).PositionalCompletion(ActionValues("a", "b")) + + Gen(cmd2).FlagCompletion(ActionMap{ + "flag2": ActionValues("a", "b"), + }) + Gen(cmd2).PositionalCompletion(ActionValues("a", "b")) + } + }) + +}