in src/CosmosCache.cs [86:178]
public async Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException(nameof(key));
}
await this.ConnectAsync(token).ConfigureAwait(false);
ItemResponse<CosmosCacheSession> cosmosCacheSessionResponse;
try
{
cosmosCacheSessionResponse = await this.cosmosContainer.ReadItemAsync<CosmosCacheSession>(
partitionKey: new PartitionKey(key),
id: key,
requestOptions: null,
cancellationToken: token).ConfigureAwait(false);
this.options.DiagnosticsHandler?.Invoke(cosmosCacheSessionResponse.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);
return null;
}
// If using sliding expiration then replace item with itself in order to reset the ttl in Cosmos
if (cosmosCacheSessionResponse.Resource.IsSlidingExpiration.GetValueOrDefault())
{
try
{
if (cosmosCacheSessionResponse.Resource.AbsoluteSlidingExpiration.GetValueOrDefault() > 0)
{
long ttl = cosmosCacheSessionResponse.Resource.TimeToLive.Value;
DateTimeOffset absoluteExpiration = DateTimeOffset.FromUnixTimeSeconds(cosmosCacheSessionResponse.Resource.AbsoluteSlidingExpiration.GetValueOrDefault());
if (absoluteExpiration < DateTimeOffset.UtcNow)
{
// At this point the cache item we just read expired, in which case, we should treat it as not found.
// The TTL will clean it up on the container.
return null;
}
else
{
double pendingSeconds = (absoluteExpiration - DateTimeOffset.UtcNow).TotalSeconds;
if (pendingSeconds < 1)
{
// Cosmos DB TTL works on seconds granularity and this item has less than a second to live.
// Return the content because it does exist, but it will be cleaned up by the TTL shortly after.
return cosmosCacheSessionResponse.Resource.Content;
}
if (pendingSeconds < ttl)
{
cosmosCacheSessionResponse.Resource.TimeToLive = (long)pendingSeconds;
}
}
}
cosmosCacheSessionResponse.Resource.PartitionKeyAttribute = this.options.ContainerPartitionKeyAttribute;
ItemResponse<CosmosCacheSession> replaceCacheSessionResponse = await this.cosmosContainer.ReplaceItemAsync(
partitionKey: new PartitionKey(key),
id: key,
item: cosmosCacheSessionResponse.Resource,
requestOptions: new ItemRequestOptions()
{
IfMatchEtag = cosmosCacheSessionResponse.ETag,
EnableContentResponseOnWrite = false,
},
cancellationToken: token).ConfigureAwait(false);
this.options.DiagnosticsHandler?.Invoke(replaceCacheSessionResponse.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// The cache item has expired in-between the read and replace operations.
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);
return null;
}
catch (CosmosException cosmosException) when (cosmosException.StatusCode == HttpStatusCode.PreconditionFailed)
{
this.options.DiagnosticsHandler?.Invoke(cosmosException.Diagnostics);
if (this.options.RetrySlidingExpirationUpdates)
{
// Race condition on replace, we need to get the latest version of the item
return await this.GetAsync(key, token).ConfigureAwait(false);
}
}
}
return cosmosCacheSessionResponse.Resource.Content;
}