Skip to content

Commit bafb4b1

Browse files
Merge pull request #55 from Azure/release/v1.3.0
Merge release/v1.3.0 to main 09/28
2 parents 04a15f0 + 65824a4 commit bafb4b1

25 files changed

Lines changed: 970 additions & 528 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
matrix:
1919
os: [ubuntu-latest, macos-latest, windows-latest]
20-
go-version: ["1.21", "1.22", "1.23", "1.24"]
20+
go-version: ["1.22", "1.23", "1.24", "1.25"]
2121

2222
steps:
2323
- name: Check out code

azureappconfiguration/azureappconfiguration.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"strings"
2727
"sync"
2828
"sync/atomic"
29+
"time"
2930

3031
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/jsonc"
3132
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/refresh"
@@ -140,7 +141,7 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
140141
}
141142
}
142143

143-
if err := azappcfg.load(ctx); err != nil {
144+
if err := azappcfg.startupWithRetry(ctx, options.StartupOptions.Timeout, azappcfg.load); err != nil {
144145
return nil, err
145146
}
146147
// Set the initial load finished flag
@@ -480,6 +481,11 @@ func (azappcfg *AzureAppConfiguration) loadFeatureFlags(ctx context.Context, set
480481

481482
dedupFeatureFlags := make(map[string]any, len(settingsResponse.settings))
482483
for _, setting := range settingsResponse.settings {
484+
// Skip non-feature flag settings
485+
if setting.ContentType == nil || *setting.ContentType != featureFlagContentType {
486+
continue
487+
}
488+
483489
if setting.Key != nil {
484490
var v map[string]any
485491
if err := json.Unmarshal([]byte(*setting.Value), &v); err != nil {
@@ -676,6 +682,59 @@ func (azappcfg *AzureAppConfiguration) executeFailoverPolicy(ctx context.Context
676682
return fmt.Errorf("failed to get settings from all clients: %v", errors)
677683
}
678684

685+
// startupWithRetry implements retry logic for startup loading with timeout and exponential backoff
686+
func (azappcfg *AzureAppConfiguration) startupWithRetry(ctx context.Context, timeout time.Duration, operation func(context.Context) error) error {
687+
// If no timeout is specified, use the default startup timeout
688+
if timeout <= 0 {
689+
timeout = defaultStartupTimeout
690+
}
691+
692+
// Create a context with timeout for the entire startup process
693+
startupCtx, cancel := context.WithTimeout(ctx, timeout)
694+
defer cancel()
695+
696+
attempt := 0
697+
startTime := time.Now()
698+
699+
for {
700+
attempt++
701+
702+
// Try to load with the current context
703+
err := operation(startupCtx)
704+
if err == nil {
705+
return nil
706+
}
707+
708+
// Check if the error is retriable
709+
if !(isFailoverable(err) ||
710+
strings.Contains(err.Error(), "no client is available") ||
711+
strings.Contains(err.Error(), "failed to get settings from all clients")) {
712+
return fmt.Errorf("load from Azure App Configuration failed with non-retriable error: %w", err)
713+
}
714+
715+
// Calculate backoff duration
716+
timeElapsed := time.Since(startTime)
717+
backoffDuration := getFixedBackoffDuration(timeElapsed)
718+
if backoffDuration == 0 {
719+
backoffDuration = calculateBackoffDuration(attempt)
720+
}
721+
722+
// Check if we have enough time left to wait and retry
723+
timeRemaining := timeout - timeElapsed
724+
if timeRemaining <= backoffDuration {
725+
return fmt.Errorf("load from Azure App Configuration failed after %d attempts within timeout %v: %w", attempt, timeout, err)
726+
}
727+
728+
// Wait for the backoff duration before retrying
729+
select {
730+
case <-startupCtx.Done():
731+
return fmt.Errorf("load from Azure App Configuration timed out: %w", startupCtx.Err())
732+
case <-time.After(backoffDuration):
733+
// Continue to next retry attempt
734+
}
735+
}
736+
}
737+
679738
func (azappcfg *AzureAppConfiguration) trimPrefix(key string) string {
680739
result := key
681740
for _, prefix := range azappcfg.trimPrefixes {
@@ -725,7 +784,9 @@ func deduplicateSelectors(selectors []Selector) []Selector {
725784

726785
func getFeatureFlagSelectors(selectors []Selector) []Selector {
727786
for i := range selectors {
728-
selectors[i].KeyFilter = featureFlagKeyPrefix + selectors[i].KeyFilter
787+
if selectors[i].SnapshotName == "" {
788+
selectors[i].KeyFilter = featureFlagKeyPrefix + selectors[i].KeyFilter
789+
}
729790
}
730791

731792
return selectors
@@ -760,6 +821,7 @@ func configureTracingOptions(options *Options) tracing.Options {
760821
}
761822

762823
tracingOption.Host = tracing.GetHostType()
824+
tracingOption.FMVersion = tracing.GetFeatureManagementVersion()
763825

764826
if !(options.KeyVaultOptions.SecretResolver == nil && options.KeyVaultOptions.Credential == nil) {
765827
tracingOption.KeyVaultConfigured = true

azureappconfiguration/client_manager.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,17 +335,17 @@ func (client *configurationClientWrapper) updateBackoffStatus(success bool) {
335335
client.backOffEndTime = time.Time{}
336336
} else {
337337
client.failedAttempts++
338-
client.backOffEndTime = time.Now().Add(client.getBackoffDuration())
338+
client.backOffEndTime = time.Now().Add(calculateBackoffDuration(client.failedAttempts))
339339
}
340340
}
341341

342-
func (client *configurationClientWrapper) getBackoffDuration() time.Duration {
343-
if client.failedAttempts <= 1 {
342+
func calculateBackoffDuration(failedAttempts int) time.Duration {
343+
if failedAttempts <= 1 {
344344
return minBackoffDuration
345345
}
346346

347347
// Cap the exponent to prevent overflow
348-
exponent := math.Min(float64(client.failedAttempts-1), float64(safeShiftLimit))
348+
exponent := math.Min(float64(failedAttempts-1), float64(safeShiftLimit))
349349
calculatedMilliseconds := float64(minBackoffDuration.Milliseconds()) * math.Pow(2, exponent)
350350
if calculatedMilliseconds > float64(maxBackoffDuration.Milliseconds()) || calculatedMilliseconds <= 0 {
351351
calculatedMilliseconds = float64(maxBackoffDuration.Milliseconds())
@@ -355,6 +355,21 @@ func (client *configurationClientWrapper) getBackoffDuration() time.Duration {
355355
return jitter(calculatedDuration)
356356
}
357357

358+
func getFixedBackoffDuration(timeElapsed time.Duration) time.Duration {
359+
if timeElapsed < time.Second*100 {
360+
return time.Second * 5
361+
}
362+
if timeElapsed < time.Second*200 {
363+
return time.Second * 10
364+
}
365+
366+
if timeElapsed < time.Second*600 {
367+
return minBackoffDuration
368+
}
369+
370+
return 0
371+
}
372+
358373
func jitter(duration time.Duration) time.Duration {
359374
// Calculate the amount of jitter to add to the duration
360375
jitter := float64(duration) * jitterRatio

azureappconfiguration/constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,8 @@ const (
6969
jitterRatio float64 = 0.25
7070
safeShiftLimit int = 63
7171
)
72+
73+
// Startup constants
74+
const (
75+
defaultStartupTimeout time.Duration = 100 * time.Second
76+
)

0 commit comments

Comments
 (0)