I recently ran into a bug with NHibernate and the MemCache cache provider in a web app. It seems that once in a while it was throwing CryptographicException and ObjectDisposedException seemingly at random. I tracked it down to it's use of the md5 and sha1 hashing algorithms. Apparently ComputeHash is not thread safe in either.
I added a simple lock around the call to hashAlgorithm.ComputeHash and it seems to have solved my problem.
I could have sworn I saw a post or bug about this somewhere before, but I have not been able to find it again. I wasn't sure where to put this, so I'm just making a new forum post here.
Here is a patch (complete with test) for the current nhcontrib svn:
Code:
Index: src/NHibernate.Caches.MemCache/src/NHibernate.Caches.MemCache.Tests/MemCacheFixture.cs
===================================================================
--- src/NHibernate.Caches.MemCache/src/NHibernate.Caches.MemCache.Tests/MemCacheFixture.cs (revision 473)
+++ src/NHibernate.Caches.MemCache/src/NHibernate.Caches.MemCache.Tests/MemCacheFixture.cs (working copy)
@@ -197,5 +197,44 @@
object get2 = cache2.Get(key);
Assert.IsFalse(get1 == get2);
}
+
+ private delegate void AsyncDelegate();
+
+ [Test]
+ public void TestThreadSafe()
+ {
+ string key = "A really long key that is long enough to make the memcache client hash the key instead of using it as is. The max length for a memcache key is 250 characters so if this string is longer than that, we should fall into the hashing code that is not thread safe.";
+ string value = "value";
+ int numberOfThreads = 5;
+ int getsPerThread = 100;
+
+ ICache cache = provider.BuildCache("nunit", props);
+ cache.Put(key, value);
+
+ // code to run in each thread
+ AsyncDelegate asyncOperation = delegate
+ {
+ for (int i = 0; i < getsPerThread; i++)
+ {
+ object item = cache.Get(key);
+ Assert.IsNotNull(item);
+ Assert.AreEqual(value, item, "didn't return the item we added");
+ }
+ };
+
+ // start threads
+ List<IAsyncResult> results = new List<IAsyncResult>();
+ for (int i = 0; i < numberOfThreads; i++)
+ {
+ IAsyncResult result = asyncOperation.BeginInvoke(null, null);
+ results.Add(result);
+ }
+
+ // wait to finish
+ foreach (IAsyncResult result in results)
+ {
+ asyncOperation.EndInvoke(result);
+ }
+ }
}
}
\ No newline at end of file
Index: src/NHibernate.Caches.MemCache/src/NHibernate.Caches.MemCache/MemCacheClient.cs
===================================================================
--- src/NHibernate.Caches.MemCache/src/NHibernate.Caches.MemCache/MemCacheClient.cs (revision 473)
+++ src/NHibernate.Caches.MemCache/src/NHibernate.Caches.MemCache/MemCacheClient.cs (working copy)
@@ -47,6 +47,7 @@
private string _regionPrefix = "";
private int _expiry;
private MemcachedClient _client;
+ private object _lockObj = new object();
static MemCacheClient()
{
@@ -149,7 +150,11 @@
private string ComputeHash(string fullKeyString, HashAlgorithm hashAlgorithm)
{
byte[] bytes = Encoding.ASCII.GetBytes(fullKeyString);
- byte[] computedHash = hashAlgorithm.ComputeHash(bytes);
+ byte[] computedHash;
+ lock (_lockObj) // ComputeHash is not thread safe, so I'm adding a lock here.
+ {
+ computedHash = hashAlgorithm.ComputeHash(bytes);
+ }
return Convert.ToBase64String(computedHash);
}