Fix race condition with less than 1 seconds left (#84)
* Special case * return * test * readme
This commit is contained in:
Родитель
f0ae628599
Коммит
edf7ff1259
|
@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
|
||||
## Unreleased
|
||||
|
||||
## <a name="1.6.2"/> 1.6.2 - 2024-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#84](https://github.com/Azure/Microsoft.Extensions.Caching.Cosmos/pull/84) Fix race condition with less than 1 seconds left on sliding expiration
|
||||
|
||||
## <a name="1.6.1"/> 1.6.1 - 2024-03-27
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -129,6 +129,13 @@ namespace Microsoft.Extensions.Caching.Cosmos
|
|||
else
|
||||
{
|
||||
double pendingSeconds = (absoluteExpiration - DateTimeOffset.UtcNow).TotalSeconds;
|
||||
if (pendingSeconds == 0)
|
||||
{
|
||||
// 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;
|
||||
|
@ -224,6 +231,13 @@ namespace Microsoft.Extensions.Caching.Cosmos
|
|||
else
|
||||
{
|
||||
double pendingSeconds = (absoluteExpiration - DateTimeOffset.UtcNow).TotalSeconds;
|
||||
if (pendingSeconds == 0)
|
||||
{
|
||||
// Cosmos DB TTL works on seconds granularity and this item has less than a second to live.
|
||||
// Treat it as a cache-miss.
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingSeconds < ttl)
|
||||
{
|
||||
cosmosCacheSessionResponse.Resource.TimeToLive = (long)pendingSeconds;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
|
||||
<CurrentDate>$([System.DateTime]::Now.ToString(yyyyMMdd))</CurrentDate>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<ClientVersion>1.6.1</ClientVersion>
|
||||
<ClientVersion>1.6.2</ClientVersion>
|
||||
<VersionSuffix Condition=" '$(IsPreview)' == 'true' ">preview</VersionSuffix>
|
||||
<Version Condition=" '$(VersionSuffix)' == '' ">$(ClientVersion)</Version>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(ClientVersion)-$(VersionSuffix)</Version>
|
||||
|
|
|
@ -715,6 +715,46 @@ namespace Microsoft.Extensions.Caching.Cosmos.Tests
|
|||
mockedContainer.Verify(c => c.ReplaceItemAsync<CosmosCacheSession>(It.Is<CosmosCacheSession>(item => item.TimeToLive == ttlSliding), It.Is<string>(id => id == "key"), It.IsAny<PartitionKey?>(), It.IsAny<ItemRequestOptions>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SlidingExpirationWithAbsoluteExpirationOnAlmostExpiredRead()
|
||||
{
|
||||
const int ttlSliding = 20;
|
||||
const int ttlAbsolute = 500;
|
||||
string etag = "etag";
|
||||
CosmosCacheSession existingSession = new CosmosCacheSession();
|
||||
existingSession.SessionKey = "key";
|
||||
existingSession.Content = new byte[0];
|
||||
existingSession.IsSlidingExpiration = true;
|
||||
existingSession.TimeToLive = ttlSliding;
|
||||
existingSession.AbsoluteSlidingExpiration = DateTimeOffset.UtcNow.AddMilliseconds(ttlAbsolute).ToUnixTimeSeconds();
|
||||
Mock<ItemResponse<CosmosCacheSession>> mockedItemResponse = new Mock<ItemResponse<CosmosCacheSession>>();
|
||||
Mock<CosmosClient> mockedClient = new Mock<CosmosClient>();
|
||||
Mock<Container> mockedContainer = new Mock<Container>();
|
||||
Mock<Database> mockedDatabase = new Mock<Database>();
|
||||
Mock<ContainerResponse> mockedResponse = new Mock<ContainerResponse>();
|
||||
mockedItemResponse.Setup(c => c.Resource).Returns(existingSession);
|
||||
mockedItemResponse.Setup(c => c.ETag).Returns(etag);
|
||||
mockedResponse.Setup(c => c.StatusCode).Returns(HttpStatusCode.OK);
|
||||
mockedContainer.Setup(c => c.ReadContainerAsync(It.IsAny<ContainerRequestOptions>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockedResponse.Object);
|
||||
mockedContainer.Setup(c => c.ReadItemAsync<CosmosCacheSession>(It.Is<string>(id => id == "key"), It.IsAny<PartitionKey>(), It.IsAny<ItemRequestOptions>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockedItemResponse.Object);
|
||||
mockedClient.Setup(c => c.GetContainer(It.IsAny<string>(), It.IsAny<string>())).Returns(mockedContainer.Object);
|
||||
mockedClient.Setup(c => c.GetDatabase(It.IsAny<string>())).Returns(mockedDatabase.Object);
|
||||
mockedClient.Setup(x => x.Endpoint).Returns(new Uri("http://localhost"));
|
||||
CosmosCache cache = new CosmosCache(Options.Create(new CosmosCacheOptions()
|
||||
{
|
||||
DatabaseName = "something",
|
||||
ContainerName = "something",
|
||||
CreateIfNotExists = true,
|
||||
CosmosClient = mockedClient.Object
|
||||
}));
|
||||
|
||||
Assert.NotNull(await cache.GetAsync("key"));
|
||||
// Checks for Db existence due to CreateIfNotExists
|
||||
mockedClient.Verify(c => c.CreateDatabaseIfNotExistsAsync(It.IsAny<string>(), It.IsAny<int?>(), It.IsAny<RequestOptions>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockedContainer.Verify(c => c.ReadItemAsync<CosmosCacheSession>(It.Is<string>(id => id == "key"), It.IsAny<PartitionKey>(), It.IsAny<ItemRequestOptions>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockedContainer.Verify(c => c.ReplaceItemAsync<CosmosCacheSession>(It.Is<CosmosCacheSession>(item => item.TimeToLive == ttlSliding), It.Is<string>(id => id == "key"), It.IsAny<PartitionKey?>(), It.IsAny<ItemRequestOptions>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
private class DiagnosticsSink
|
||||
{
|
||||
private List<CosmosDiagnostics> capturedDiagnostics = new List<CosmosDiagnostics>();
|
||||
|
|
Загрузка…
Ссылка в новой задаче