Skip to content

Commit 8176a6d

Browse files
committed
Implement double-checked locking on GetPublicKeysAsync. This will reduce lock contention when the method is used by multiple threads concurrently.
Ensure that the clock is read independently for each check, as we don't know how long we'll be waiting for a handle on the semaphore.
1 parent c57713f commit 8176a6d

File tree

1 file changed

+27
-22
lines changed

1 file changed

+27
-22
lines changed

FirebaseAdmin/FirebaseAdmin/Auth/HttpPublicKeySource.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,36 +66,41 @@ public HttpPublicKeySource(string certUrl, IClock clock, HttpClientFactory clien
6666
public async Task<IReadOnlyList<PublicKey>> GetPublicKeysAsync(
6767
CancellationToken cancellationToken = default(CancellationToken))
6868
{
69-
await _lock.WaitAsync().ConfigureAwait(false);
70-
var now = _clock.UtcNow;
71-
try
69+
if (_cachedKeys == null || _clock.UtcNow >= _expirationTime)
7270
{
73-
if (_cachedKeys == null || now >= _expirationTime)
71+
await _lock.WaitAsync().ConfigureAwait(false);
72+
73+
try
7474
{
75-
using (var httpClient = _clientFactory.CreateDefaultHttpClient())
75+
if (_cachedKeys == null || _clock.UtcNow >= _expirationTime)
7676
{
77-
var response = await httpClient.GetAsync(_certUrl, cancellationToken)
78-
.ConfigureAwait(false);
79-
response.EnsureSuccessStatusCode();
80-
_cachedKeys = ParseKeys(await response.Content.ReadAsStringAsync()
81-
.ConfigureAwait(false));
82-
var cacheControl = response.Headers.CacheControl;
83-
if (cacheControl != null && cacheControl.MaxAge.HasValue)
77+
using (var httpClient = _clientFactory.CreateDefaultHttpClient())
8478
{
85-
_expirationTime = now.Add(cacheControl.MaxAge.Value)
86-
.Subtract(ClockSkew);
79+
var now = _clock.UtcNow;
80+
var response = await httpClient.GetAsync(_certUrl, cancellationToken)
81+
.ConfigureAwait(false);
82+
response.EnsureSuccessStatusCode();
83+
_cachedKeys = ParseKeys(await response.Content.ReadAsStringAsync()
84+
.ConfigureAwait(false));
85+
var cacheControl = response.Headers.CacheControl;
86+
if (cacheControl != null && cacheControl.MaxAge.HasValue)
87+
{
88+
_expirationTime = now.Add(cacheControl.MaxAge.Value)
89+
.Subtract(ClockSkew);
90+
}
8791
}
8892
}
8993
}
94+
catch (HttpRequestException e)
95+
{
96+
throw new FirebaseException("Failed to retrieve latest public keys.", e);
97+
}
98+
finally
99+
{
100+
_lock.Release();
101+
}
90102
}
91-
catch (HttpRequestException e)
92-
{
93-
throw new FirebaseException("Failed to retrieve latest public keys.", e);
94-
}
95-
finally
96-
{
97-
_lock.Release();
98-
}
103+
99104
return _cachedKeys;
100105
}
101106

0 commit comments

Comments
 (0)