Skip to content

Commit 1d09c8c

Browse files
authored
fix(tests): race condition creating the sync id (#23481)
Signed-off-by: Alexandre Gaudreault <[email protected]>
1 parent bee2362 commit 1d09c8c

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

controller/sync.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"strconv"
99
"strings"
10-
"sync/atomic"
1110
"time"
1211

1312
"k8s.io/apimachinery/pkg/util/strategicpatch"
@@ -29,6 +28,7 @@ import (
2928
"k8s.io/kubectl/pkg/util/openapi"
3029

3130
"github.com/argoproj/argo-cd/v3/controller/metrics"
31+
"github.com/argoproj/argo-cd/v3/controller/syncid"
3232
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
3333
listersv1alpha1 "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
3434
applog "github.com/argoproj/argo-cd/v3/util/app/log"
@@ -38,11 +38,8 @@ import (
3838
kubeutil "github.com/argoproj/argo-cd/v3/util/kube"
3939
logutils "github.com/argoproj/argo-cd/v3/util/log"
4040
"github.com/argoproj/argo-cd/v3/util/lua"
41-
"github.com/argoproj/argo-cd/v3/util/rand"
4241
)
4342

44-
var syncIdPrefix uint64
45-
4643
const (
4744
// EnvVarSyncWaveDelay is an environment variable which controls the delay in seconds between
4845
// each sync-wave
@@ -96,6 +93,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
9693
// concrete git commit SHA, the SHA is remembered in the status.operationState.syncResult field.
9794
// This ensures that when resuming an operation, we sync to the same revision that we initially
9895
// started with.
96+
9997
var revision string
10098
var syncOp v1alpha1.SyncOperation
10199
var syncRes *v1alpha1.SyncOperationResult
@@ -248,15 +246,12 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
248246
return
249247
}
250248

251-
atomic.AddUint64(&syncIdPrefix, 1)
252-
randSuffix, err := rand.String(5)
249+
syncId, err := syncid.Generate()
253250
if err != nil {
254251
state.Phase = common.OperationError
255-
state.Message = fmt.Sprintf("Failed generate random sync ID: %v", err)
252+
state.Message = fmt.Sprintf("Failed to generate sync ID: %v", err)
256253
return
257254
}
258-
syncId := fmt.Sprintf("%05d-%s", syncIdPrefix, randSuffix)
259-
260255
logEntry := log.WithFields(applog.GetAppLogFields(app)).WithField("syncId", syncId)
261256
initialResourcesRes := make([]common.ResourceSyncResult, len(syncRes.Resources))
262257
for i, res := range syncRes.Resources {

controller/syncid/id.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package syncid
2+
3+
import (
4+
"fmt"
5+
"sync/atomic"
6+
7+
"github.com/argoproj/argo-cd/v3/util/rand"
8+
)
9+
10+
var globalCount = &atomic.Uint64{}
11+
12+
// Generate generates a new ID
13+
func Generate() (string, error) {
14+
randSuffix, err := rand.String(5)
15+
if err != nil {
16+
return "", fmt.Errorf("failed to generate random suffix: %w", err)
17+
}
18+
prefix := globalCount.Add(1)
19+
return fmt.Sprintf("%05d-%s", prefix, randSuffix), nil
20+
}

controller/syncid/id_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package syncid
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestGenerate(t *testing.T) {
11+
t.Parallel()
12+
const goroutines = 10
13+
const idsPerGoroutine = 50
14+
idsCh := make(chan string, goroutines*idsPerGoroutine)
15+
errCh := make(chan error, goroutines*idsPerGoroutine)
16+
17+
// Reset globalCount for deterministic test (not strictly necessary, but can help in CI)
18+
globalCount.Store(0)
19+
20+
// Run goroutines in parallel to test for race conditions
21+
for g := 0; g < goroutines; g++ {
22+
go func() {
23+
for i := 0; i < idsPerGoroutine; i++ {
24+
id, err := Generate()
25+
if err != nil {
26+
errCh <- err
27+
continue
28+
}
29+
idsCh <- id
30+
}
31+
}()
32+
}
33+
34+
ids := make(map[string]any)
35+
for i := 0; i < goroutines*idsPerGoroutine; i++ {
36+
select {
37+
case err := <-errCh:
38+
require.NoError(t, err)
39+
case id := <-idsCh:
40+
assert.Regexp(t, `^\d{5}-[a-zA-Z0-9]{5}$`, id, "ID should match the expected format")
41+
_, exists := ids[id]
42+
assert.False(t, exists, "ID should be unique")
43+
ids[id] = id
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)