Skip to content

Commit 47a3924

Browse files
committed
fire and forget goroutine for discover fallback clients
1 parent bd54389 commit 47a3924

File tree

1 file changed

+44
-18
lines changed

1 file changed

+44
-18
lines changed

azureappconfiguration/client_manager.go

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net/url"
1414
"strconv"
1515
"strings"
16+
"sync/atomic"
1617
"time"
1718

1819
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -25,14 +26,17 @@ type configurationClientManager struct {
2526
replicaDiscoveryEnabled bool
2627
clientOptions *azappconfig.ClientOptions
2728
staticClient *configurationClientWrapper
28-
dynamicClients []*configurationClientWrapper
29+
dynamicClients atomic.Pointer[[]*configurationClientWrapper] // atomic pointer to slice
2930
endpoint string
3031
validDomain string
3132
credential azcore.TokenCredential
3233
secret string
3334
id string
3435
lastFallbackClientAttempt time.Time
3536
lastFallbackClientRefresh time.Time
37+
38+
// discoveryInProgress prevents concurrent replica discovery operations
39+
discoveryInProgress atomic.Bool
3640
}
3741

3842
// configurationClientWrapper wraps an Azure App Configuration client with additional metadata
@@ -110,7 +114,12 @@ func (manager *configurationClientManager) initializeClient(authOptions Authenti
110114

111115
func (manager *configurationClientManager) getClients(ctx context.Context) ([]*configurationClientWrapper, error) {
112116
currentTime := time.Now()
113-
clients := make([]*configurationClientWrapper, 0, 1+len(manager.dynamicClients))
117+
dynamicClients := manager.dynamicClients.Load() // atomic read
118+
clientsCapacity := 1
119+
if dynamicClients != nil {
120+
clientsCapacity += len(*dynamicClients)
121+
}
122+
clients := make([]*configurationClientWrapper, 0, clientsCapacity)
114123

115124
// Add the static client if it is not in backoff
116125
if currentTime.After(manager.staticClient.backOffEndTime) {
@@ -122,16 +131,18 @@ func (manager *configurationClientManager) getClients(ctx context.Context) ([]*c
122131
}
123132

124133
if currentTime.After(manager.lastFallbackClientAttempt.Add(minimalClientRefreshInterval)) &&
125-
(manager.dynamicClients == nil ||
134+
(dynamicClients == nil ||
126135
currentTime.After(manager.lastFallbackClientRefresh.Add(fallbackClientRefreshExpireInterval))) {
127136
manager.lastFallbackClientAttempt = currentTime
128137
url, _ := url.Parse(manager.endpoint)
129-
manager.discoverFallbackClients(ctx, url.Host)
138+
manager.discoverFallbackClients(url.Host)
130139
}
131140

132-
for _, clientWrapper := range manager.dynamicClients {
133-
if currentTime.After(clientWrapper.backOffEndTime) {
134-
clients = append(clients, clientWrapper)
141+
if dynamicClients != nil {
142+
for _, clientWrapper := range *dynamicClients {
143+
if currentTime.After(clientWrapper.backOffEndTime) {
144+
clients = append(clients, clientWrapper)
145+
}
135146
}
136147
}
137148

@@ -144,21 +155,36 @@ func (manager *configurationClientManager) refreshClients(ctx context.Context) {
144155
currentTime.After(manager.lastFallbackClientAttempt.Add(minimalClientRefreshInterval)) {
145156
manager.lastFallbackClientAttempt = currentTime
146157
url, _ := url.Parse(manager.endpoint)
147-
manager.discoverFallbackClients(ctx, url.Host)
158+
manager.discoverFallbackClients(url.Host)
148159
}
149160
}
150161

151-
func (manager *configurationClientManager) discoverFallbackClients(ctx context.Context, host string) {
152-
newCtx, cancel := context.WithTimeout(ctx, failoverTimeout)
153-
defer cancel()
154-
155-
srvTargetHosts, err := querySrvTargetHost(newCtx, host)
156-
if err != nil {
157-
log.Printf("failed to discover fallback clients for %s: %v", host, err)
158-
return
162+
func (manager *configurationClientManager) discoverFallbackClients(host string) {
163+
if !manager.discoveryInProgress.CompareAndSwap(false, true) {
164+
return // Another discovery is already in progress
159165
}
160166

161-
manager.processSrvTargetHosts(srvTargetHosts)
167+
// Reset the flag when we're done
168+
defer manager.discoveryInProgress.Store(false)
169+
170+
go func() {
171+
defer func() {
172+
if r := recover(); r != nil {
173+
log.Printf("panic in replica discovery: %v", r)
174+
}
175+
}()
176+
177+
discoveryCtx, cancel := context.WithTimeout(context.Background(), failoverTimeout)
178+
defer cancel()
179+
180+
srvTargetHosts, err := querySrvTargetHost(discoveryCtx, host)
181+
if err != nil {
182+
log.Printf("failed to discover fallback clients for %s: %v", host, err)
183+
return
184+
}
185+
186+
manager.processSrvTargetHosts(srvTargetHosts)
187+
}()
162188
}
163189

164190
func (manager *configurationClientManager) processSrvTargetHosts(srvTargetHosts []string) {
@@ -188,7 +214,7 @@ func (manager *configurationClientManager) processSrvTargetHosts(srvTargetHosts
188214
}
189215
}
190216

191-
manager.dynamicClients = newDynamicClients
217+
manager.dynamicClients.Store(&newDynamicClients) // atomic write
192218
manager.lastFallbackClientRefresh = time.Now()
193219
}
194220

0 commit comments

Comments
 (0)