Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is HashAlgorithm.ComputeHash() thread-safe?

I wasn't able to get a definitive answer on this so this question. There are few SO posts in the past that mentioned that instances of HashAlgorithm are not thread-safe quoting snippet in the MSDN doc.

See

  • Why does SHA1.ComputeHash fail under high load with many threads?
  • HMACSHA1.ComputeHash() thread-safety question
  • Which piece of code is more performant?

But, the current MSDN doc doesn't say so. Surprisingly, the below code doesn't bomb on net3.1, net5.0, but does on net6.0. So, it looks like it was made thread-safe (perhaps), but perhaps net6.0 has a bug.

//<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;net48</TargetFrameworks>
[Explicit]
[Test]
public void Bork_HashAlgorithm()
{
    const int iterations = 1_000_000;
    var bytes = Encoding.UTF8.GetBytes("the overtinkerer");
    using (var md5 = MD5.Create())
    {
        Parallel.For(0, iterations, (i, loop) =>
        {
            md5.ComputeHash(bytes);
        });
    }
}

Exception message:

SafeHandle cannot be null. (Parameter 'pHandle')

like image 902
hIpPy Avatar asked Jul 18 '26 05:07

hIpPy


1 Answers

No, it's not thread-safe.

We're seeing same exceptions under peak load: SafeHandle cannot be null. (Parameter 'pHandle') and similar errors for MD5 provider, for SHA1, and for SHA256, all over the place.

However we found that it's still beneficial to use a "singleton" instance of HashAlgorithm with a lock, it's still 3X faster than creating a separate instance every time.

Here's the benchmark

Method Mean Error StdDev Gen0 Gen1 Gen2 Allocated
MD5Recreate 1,282.5 ns 726.26 ns 39.81 ns 0.0801 0.0286 0.0038 512 B
MD5SingletonWithLock 402.2 ns 39.38 ns 2.16 ns 0.0610 - - 384 B
MD5_HashData 467.7 ns 33.26 ns 1.82 ns 0.0548 - - 344 B

Here's the code:

static MD5 _md5 = MD5.Create(); // <-- one instance for all threads
public static byte[] MD5Hash(byte[] input)
{
    lock (_md5) // <-- use a lock
    {
        return _md5.ComputeHash(input);
    }
}

UPDATE: MD5_HashData is the new static MD5.HashData method introduced in .NET 5 - it's not faster than using lock. However MD5.HashData shows better performance under heavy parallelism, which BDN cannot emulate (see comments below)

UPDATE2: adding benchmarks for SHA256

Method Mean Error StdDev Gen0 Gen1 Allocated
SHARecreate 2.211 us 3.7097 us 0.2033 us 0.1144 0.0534 736 B
SHASingletonWithLock 1.231 us 0.1349 us 0.0074 us 0.0954 - 608 B
SHA_HashData 1.296 us 0.0737 us 0.0040 us 0.0877 - 552 B
like image 89
Alex from Jitbit Avatar answered Jul 19 '26 17:07

Alex from Jitbit