Can DateTime tear in a 64 bit environment?

C#.NetDatetimeConcurrencyTearing

C# Problem Overview


In C# setting a value to a variable is atomic as long as its size is at most native int (i.e. 4 bytes in a 32-bit runtime environment and 8 bytes on a 64-bit one). In a 64-bit environment that includes all references types and most built-in value types (byte, short, int, long, etc.).

Setting a bigger value isn't atomic and can cause tearing where only part of the memory is updated.

DateTime is a struct that includes only a single ulong field containing all its data (Ticks and the DateTimeKind) and ulong by itself is atomic in a 64-bit environment.

Does that mean that DateTime is atomic as well? Or Can the following code lead to tearing at some point?

static DateTime _value;
static void Main()
{
    for (int i = 0; i < 10; i++)
    {
        new Thread(_ =>
        {
            var random = new Random();
            while (true)
            {
                _value = new DateTime((long)random.Next() << 30 | (long)random.Next());
            }
        }).Start();
    }

    Console.ReadLine();
}

C# Solutions


Solution 1 - C#

From the ECMA specification section "I.12.6.6 Atomic reads and writes"

> A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size (the size of type native int) is atomic (see §I.12.6.2) when all the write accesses to a location are the same size. Atomic writes shall alter no bits other than those written. Unless explicit layout control (see Partition II (Controlling Instance Layout)) is used to alter the default behavior, data elements no larger than the natural word size (the size of a native int) shall be properly aligned. Object references shall be treated as though they are stored in the native word size.

A native int is a IntPtr in C#.

So long as sizeof(IntPtr) >= sizeof(DateTime) is true for the runtime environment (aka: running as 64 bit), and they don't alter the internal structure to be explicit layout with misaligned bytes instead of the [StructLayout(LayoutKind.Auto)] it currently has, then reads and writes of a DateTime struct (or any other struct that follows those rules) are guaranteed to be atomic by the ECMA specification.

You can verify that by running the following code in a 64-bit environment:

public unsafe static void Main()
{
    Console.WriteLine(sizeof(DateTime)); // Outputs 8
    Console.WriteLine(sizeof(IntPtr)); // Outputs 8
    Console.WriteLine(sizeof(ulong)); // Outputs 8
}

Solution 2 - C#

Running some tests and based on the above answer it is pretty safe to say it is atomic today.

I wrote a test to verify how many tears could be found during X iterations over N threads for Int64, DateTime and 3 custom structs of 128, 192 and 256 sizes - none with their StructLayout messed up.

The test consists of:

  1. Adding a set of values to a array so they are known.
  2. Setting up one thread for each array position, this thread will assign the value from the array to a shared variable.
  3. Setting up the same number of threads (array.length) to read from this shared variable to a local.
  4. Check if this local is contained in the original array.

The results are as follows in my machine (Core i7-4500U, Windows 10 x64, .NET 4.6, Release without debug, Platform target: x64 with code optimization):

-------------- Trying to Tear --------------
Running: 64bits
Max Threads: 30
Max Reruns: 10
Iterations per Thread: 20000
--------------------------------------------
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         23             Struct128 (128bits)
         87             Struct192 (192bits)
         43             Struct256 (256bits)
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         44             Struct128 (128bits)
         59             Struct192 (192bits)
         52             Struct256 (256bits)
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         26             Struct128 (128bits)
         53             Struct192 (192bits)
         45             Struct256 (256bits)
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         46             Struct128 (128bits)
         57             Struct192 (192bits)
         56             Struct256 (256bits)
------------------- End --------------------

The code for the test can be found here: https://gist.github.com/Flash3001/da5bd3ca800f674082dd8030ef70cf4e

Solution 3 - C#

From C# language specification.

> 5.5 Atomicity of variable references Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, > int, float, and reference types. In addition, reads and writes of enum > types with an underlying type in the previous list are also atomic. > Reads and writes of other types, including long, ulong, double, and > decimal, as well as user-defined types, are not guaranteed to be > atomic. Aside from the library functions designed for that purpose, > there is no guarantee of atomic read-modify-write, such as in the case > of increment or decrement.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questioni3arnonView Question on Stackoverflow
Solution 1 - C#Scott ChamberlainView Answer on Stackoverflow
Solution 2 - C#Lucas TeixeiraView Answer on Stackoverflow
Solution 3 - C#OmariOView Answer on Stackoverflow