C# Code title image

C# Constructor Gotchas Part 3: Static constructor deadlock

Take a look at the following code sample and see if you can guess what the output will be.

(The clue is in the title of this post.)

If you run that code, then you will find that it enters a deadlock state and prints no output at all.
Why is that?

The ECMA Common Language Infrastructure (CLI) Partition II, Section 10.5.3.3 (originally Partition II, Section 9.5.3.3 of the 1st edition) states that “Type initialization alone shall not create a deadlock unless some code called from a type initializer (directly or indirectly) explicitly invokes blocking operations.”
It then goes on to outline an initialization algorithm for type initialization in a multi-threaded environment:

  1. When loading (before initializing) a class, write zero or null into all static fields.
  2. If no further fields require initialization then exit.
  3. Otherwise, if the type is not yet fully initialized, try to acquire an initialization lock:
    1. If lock could not be acquired, check to see whether this thread (or any thread waiting on this thread to complete) already holds the lock.
    2. If so, return, because blocking would create a deadlock. This thread will now see an incompletely initialized instance, but no deadlock will arise.
    3. Otherwise, block until the type is initialized and then return.
  4. If the lock was acquired, initialize the base class type and then all interfaces implemented by this type.
    Next, execute the type initialization code for this type.
  5. Mark the type as initialized.
    Release the initialization lock.
    Signal any threads waiting for this type to be initialized that they can resume.
    Then return.

What does this actually mean?

It means that static constructors run under a lock.
The runtime environment must ensure that each type is initialized once, and only once.
To ensure that goal is always met, it uses locking to prevent multiple threads from executing the same static constructor.

So, by executing an operation in the static constructor which blocks the current thread, we risk a deadlock situation.

In addition to actions which explicitly perform blocking operations, other actions such as those which wait for the completion of a task can also trigger a deadlock, such as the following example:

Finally, also remember that static field initializers effectively execute as if their code was inlined into the beginning of the static constructor in the sequence in which they are declared.
So, in the same way as the static constructor, they are executed under a lock to ensure that the initialization occurs only once.
This means that the same cautions about blocking and deadlocks apply to code which is called when initializing static fields.
The following example is shows code called from a field initializer which will also result in a deadlock:

This post is part of a series. The introduction, and a list of the other posts in the series can be found here.

One thought on “C# Constructor Gotchas Part 3: Static constructor deadlock”

Leave a Reply