Skip to content

Commit 0695dd4

Browse files
committed
Improve retry logic
1 parent edb6877 commit 0695dd4

File tree

6 files changed

+143
-18
lines changed

6 files changed

+143
-18
lines changed

Couchbase.AspNet.UnitTests/Couchbase.AspNet.UnitTests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
<ItemGroup>
6969
<Compile Include="Properties\AssemblyInfo.cs" />
7070
<Compile Include="ProviderHelperTests.cs" />
71-
<Compile Include="SessionStateItemTests.cs" />
71+
<Compile Include="SessionState\SessionStateItemTests.cs" />
72+
<Compile Include="SessionState\CouchbaseSessionStateProviderTests.cs" />
7273
</ItemGroup>
7374
<ItemGroup>
7475
<None Include="app.config">
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Web;
3+
using System.Web.SessionState;
4+
using Couchbase.AspNet.SessionState;
5+
using Couchbase.Core;
6+
using Couchbase.IO;
7+
using Moq;
8+
using NUnit.Framework;
9+
10+
namespace Couchbase.AspNet.UnitTests.SessionState
11+
{
12+
[TestFixture()]
13+
public class CouchbaseSessionStateProviderTests
14+
{
15+
[Test()]
16+
public void GetSessionStoreItem_WhenKeyNotFound_ReturnsNull()
17+
{
18+
//arrange
19+
var result = new Mock<IOperationResult<byte[]>>();
20+
result.Setup(x => x.Status).Returns(ResponseStatus.KeyNotFound);
21+
result.Setup(x => x.Value).Returns(new byte[0]);
22+
23+
var bucket = new Mock<IBucket>();
24+
bucket.Setup(x => x.Get<byte[]>(It.IsAny<string>())).Returns(result.Object);
25+
26+
bool locked;
27+
TimeSpan lockAge;
28+
object lockId;
29+
SessionStateActions actions;
30+
31+
//Act
32+
var sessionStateItem = CouchbaseSessionStateProvider.GetSessionStoreItem(
33+
bucket.Object, null, false, "thekey", out locked, out lockAge, out lockId, out actions);
34+
35+
//Assert
36+
Assert.IsNull(sessionStateItem);
37+
}
38+
39+
[Test()]
40+
public void SetAndReleaseItemExclusive_WhenKeyNotFound_ReturnsNull()
41+
{
42+
//arrange
43+
var result = new Mock<IOperationResult<byte[]>>();
44+
result.Setup(x => x.Status).Returns(ResponseStatus.KeyNotFound);
45+
result.Setup(x => x.Value).Returns(new byte[0]);
46+
47+
var bucket = new Mock<IBucket>();
48+
bucket.Setup(x => x.Get<byte[]>(It.IsAny<string>())).Returns(result.Object);
49+
50+
var provider = new CouchbaseSessionStateProvider(new Mock<ICluster>().Object, bucket.Object);
51+
52+
bool locked;
53+
TimeSpan lockAge;
54+
object lockId = 10ul;
55+
SessionStateActions actions;
56+
57+
//Act
58+
provider.SetAndReleaseItemExclusive(null, "thekey", new SessionStateStoreData(new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 10), lockId, false);
59+
60+
61+
62+
}
63+
}
64+
}

Couchbase.AspNet.UnitTests/SessionStateItemTests.cs renamed to Couchbase.AspNet.UnitTests/SessionState/SessionStateItemTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using Moq;
55
using NUnit.Framework;
66

7-
namespace Couchbase.AspNet.UnitTests
7+
namespace Couchbase.AspNet.UnitTests.SessionState
88
{
99
[TestFixture]
1010
public class SessionStateItemTests

Couchbase.AspNet/Couchbase.AspNet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
<Compile Include="SessionState\CouchbaseSessionStateProvider.cs" />
7575
<Compile Include="ICouchbaseBucketFactory.cs" />
7676
<Compile Include="ProviderHelper.cs" />
77-
<Compile Include="SessionState\SessionStateItemcs.cs" />
77+
<Compile Include="SessionState\SessionStateItem.cs" />
7878
<Compile Include="Web\CouchbaseHttpApplication.cs" />
7979
</ItemGroup>
8080
<ItemGroup>

Couchbase.AspNet/SessionState/CouchbaseSessionStateProvider.cs

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,39 @@
33
using System.Web.SessionState;
44
using System.Web;
55
using Couchbase.Core;
6+
using Couchbase.IO;
67

78
namespace Couchbase.AspNet.SessionState
89
{
10+
/// <summary>
11+
/// A <see cref="SessionStateStoreProviderBase"/> implementation which uses Couchbase Server as the backing store.
12+
/// </summary>
913
public class CouchbaseSessionStateProvider : SessionStateStoreProviderBase
1014
{
1115
private ICluster _cluster;
1216
private IBucket _bucket;
1317
private static bool _exclusiveAccess;
18+
private int _maxRetryCount = 5;
19+
20+
/// <summary>
21+
/// Required default ctor for ASP.NET
22+
/// </summary>
23+
public CouchbaseSessionStateProvider()
24+
{
25+
}
26+
27+
/// <summary>
28+
/// For unit testing only.
29+
/// </summary>
30+
/// <param name="cluster"></param>
31+
/// <param name="bucket"></param>
32+
public CouchbaseSessionStateProvider(
33+
ICluster cluster,
34+
IBucket bucket)
35+
{
36+
_cluster = cluster;
37+
_bucket = bucket;
38+
}
1439

1540
/// <summary>
1641
/// Defines the prefix for header data in the Couchbase bucket. Must be unique to ensure it does not conflict with
@@ -28,7 +53,6 @@ public class CouchbaseSessionStateProvider : SessionStateStoreProviderBase
2853
(System.Web.Hosting.HostingEnvironment.SiteName ?? string.Empty).Replace(" ", "-") + "+" +
2954
System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath + "data-";
3055

31-
3256
/// <summary>
3357
/// Function to initialize the provider
3458
/// </summary>
@@ -39,11 +63,16 @@ public override void Initialize(string name, NameValueCollection config)
3963
// Initialize the base class
4064
base.Initialize(name, config);
4165

42-
// Create our Cluster based off the CouchbaseConfigSection
43-
_cluster = ProviderHelper.GetCluster(name, config);
44-
45-
// Create the bucket based off the name provided in the
46-
_bucket = ProviderHelper.GetBucket(name, config, _cluster);
66+
if (_cluster == null)
67+
{
68+
// Create our Cluster based off the CouchbaseConfigSection
69+
_cluster = ProviderHelper.GetCluster(name, config);
70+
}
71+
if (_bucket == null)
72+
{
73+
// Create the bucket based off the name provided in the
74+
_bucket = ProviderHelper.GetBucket(name, config, _cluster);
75+
}
4776

4877
// By default use exclusive session access. But allow it to be overridden in the config file
4978
var exclusive = ProviderHelper.GetAndRemove(config, "exclusiveAccess", false) ?? "true";
@@ -60,6 +89,12 @@ public override void Initialize(string name, NameValueCollection config)
6089
{
6190
DataPrefix = dataPrefix;
6291
}
92+
var maxRetryCount = ProviderHelper.GetAndRemove(config, "maxRetryCount", false);
93+
var temp = 0;
94+
if (int.TryParse(maxRetryCount, out temp))
95+
{
96+
_maxRetryCount = temp;
97+
}
6398

6499
// Make sure no extra attributes are included
65100
ProviderHelper.CheckForUnknownAttributes(config);
@@ -130,7 +165,8 @@ public override void CreateUninitializedItem(HttpContext context, string id, int
130165
Timeout = timeout
131166
};
132167

133-
e.SaveAll(_bucket, id, false);
168+
bool keyNotFound;
169+
e.SaveAll(_bucket, id, false, out keyNotFound);
134170
}
135171

136172
/// <summary>
@@ -227,14 +263,19 @@ public static SessionStateItem GetSessionStoreItem(
227263
e.LockTime = DateTime.UtcNow;
228264
e.Flag = SessionStateActions.None;
229265

266+
ResponseStatus status;
230267
// try to update the item in the store
231-
if (e.SaveHeader(bucket, id, _exclusiveAccess))
268+
if (e.SaveHeader(bucket, id, _exclusiveAccess, out status))
232269
{
233270
locked = true;
234271
lockId = e.LockId;
235272

236273
return e;
237274
}
275+
if (status == ResponseStatus.KeyNotFound)
276+
{
277+
break;
278+
}
238279

239280
// it has been modified between we loaded and tried to save it
240281
e = SessionStateItem.Load(bucket, id, false);
@@ -267,6 +308,8 @@ public override void SetAndReleaseItemExclusive(
267308
object lockId,
268309
bool newItem)
269310
{
311+
bool keyNotFound;
312+
var retries = 0;
270313
SessionStateItem e;
271314
do {
272315
if (!newItem)
@@ -298,7 +341,7 @@ public override void SetAndReleaseItemExclusive(
298341
e.LockTime = DateTime.MinValue;
299342

300343
// Attempt to save with CAS and loop around if it fails
301-
} while (!e.SaveAll(_bucket, id, _exclusiveAccess && !newItem));
344+
} while (!e.SaveAll(_bucket, id, _exclusiveAccess && !newItem, out keyNotFound) && retries++ < _maxRetryCount && !keyNotFound);
302345
}
303346

304347
/// <summary>
@@ -312,6 +355,8 @@ public override void ReleaseItemExclusive(
312355
string id,
313356
object lockId)
314357
{
358+
ResponseStatus status;
359+
var retries = 0;
315360
var tmp = (ulong)lockId;
316361
SessionStateItem e;
317362
do {
@@ -327,7 +372,7 @@ public override void ReleaseItemExclusive(
327372
// Attempt to clear the lock for this item and loop around until we succeed
328373
e.LockId = 0;
329374
e.LockTime = DateTime.MinValue;
330-
} while (!e.SaveHeader(_bucket, id, _exclusiveAccess));
375+
} while (!e.SaveHeader(_bucket, id, _exclusiveAccess, out status) && retries < _maxRetryCount && status != ResponseStatus.KeyNotFound);
331376
}
332377

333378
/// <summary>
@@ -361,6 +406,8 @@ public override void ResetItemTimeout(
361406
HttpContext context,
362407
string id)
363408
{
409+
bool keyNotFound;
410+
var retries = 0;
364411
SessionStateItem e;
365412
do {
366413
// Load the item with CAS
@@ -371,7 +418,7 @@ public override void ResetItemTimeout(
371418
}
372419

373420
// Try to save with CAS, and loop around until we succeed
374-
} while (!e.SaveAll(_bucket, id, _exclusiveAccess));
421+
} while (!e.SaveAll(_bucket, id, _exclusiveAccess, out keyNotFound) && retries < _maxRetryCount && !keyNotFound);
375422
}
376423

377424
/// <summary>

Couchbase.AspNet/SessionState/SessionStateItemcs.cs renamed to Couchbase.AspNet/SessionState/SessionStateItem.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ private void WriteHeader(
5353
public bool SaveHeader(
5454
IBucket bucket,
5555
string id,
56-
bool useCas)
56+
bool useCas,
57+
out ResponseStatus status)
5758
{
5859
using (var ms = new MemoryStream())
5960
{
@@ -64,6 +65,8 @@ public bool SaveHeader(
6465
var retval = useCas
6566
? bucket.Upsert(CouchbaseSessionStateProvider.HeaderPrefix + id, ms.ToArray(), HeadCas, ts)
6667
: bucket.Upsert(CouchbaseSessionStateProvider.HeaderPrefix + id, ms.ToArray(), ts);
68+
69+
status = retval.Status;
6770
return retval.Success;
6871
}
6972
}
@@ -74,11 +77,13 @@ public bool SaveHeader(
7477
/// <param name="bucket">Couchbase bucket to save to</param>
7578
/// <param name="id">Session ID</param>
7679
/// <param name="useCas">True to use a check and set, false to simply store it</param>
80+
/// <param name="status">The <see cref="ResponseStatus"/> from the server.</param>
7781
/// <returns>True if the value was saved, false if not</returns>
7882
public bool SaveData(
7983
IBucket bucket,
8084
string id,
81-
bool useCas)
85+
bool useCas,
86+
out ResponseStatus status)
8287
{
8388
var ts = TimeSpan.FromMinutes(Timeout);
8489
using (var ms = new MemoryStream())
@@ -91,6 +96,7 @@ public bool SaveData(
9196
? bucket.Upsert(CouchbaseSessionStateProvider.DataPrefix + id, ms.ToArray(), DataCas, ts)
9297
: bucket.Upsert(CouchbaseSessionStateProvider.DataPrefix + id, ms.ToArray(), ts);
9398

99+
status = retval.Status;
94100
return retval.Success;
95101
}
96102
}
@@ -101,13 +107,20 @@ public bool SaveData(
101107
/// <param name="bucket">Couchbase bucket to save to</param>
102108
/// <param name="id">Session ID</param>
103109
/// <param name="useCas">True to use a check and set, false to simply store it</param>
110+
/// <param name="keyNotFound">True if <see cref="ResponseStatus.KeyNotFound"/> is returned for the body or the header.</param>
104111
/// <returns>True if the value was saved, false if not</returns>
105112
public bool SaveAll(
106113
IBucket bucket,
107114
string id,
108-
bool useCas)
115+
bool useCas,
116+
out bool keyNotFound)
109117
{
110-
return SaveData(bucket, id, useCas) && SaveHeader(bucket, id, useCas);
118+
var dataStatus = ResponseStatus.None;
119+
var headerStatus = ResponseStatus.None;
120+
121+
var failed = SaveData(bucket, id, useCas, out dataStatus) && SaveHeader(bucket, id, useCas, out headerStatus);
122+
keyNotFound = dataStatus == ResponseStatus.KeyNotFound || headerStatus == ResponseStatus.KeyNotFound;
123+
return failed;
111124
}
112125

113126
/// <summary>

0 commit comments

Comments
 (0)