📜 ⬆️ ⬇️

About singletons and static constructors

Initially, the author wanted to call this article as follows: “About singletons, static constructors and initializers of static fields, about the beforeFieldInit flag and its effect on deadlock static constructors when starting release services in .Net Framework 3.5,” however, due to that the multi-line names, for reasons unknown to the author, did not take root in the modern computer community, he (the author) decided to shorten this name, horribly distorting its original meaning.

-------------------------

Any implementation of the Singleton pattern in general has two goals: first, the implementation must be thread-safe to prevent the creation of more than one instance in the multi-threaded .Net world; and secondly, this implementation must be “deferred” (lazy) in order not to create an instance of (potentially) expensive object ahead of time or in cases when it may not be needed at all. But since the main focus when reading any article about the implementation of Singleton is multithreading, then “laziness” often lacks no time for desire.
')


Let's look at one of the most simple and popular implementations of the Singleton pattern (*), based on the static field initializer:

public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();

// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{}

private Singleton()
{}

public static Singleton Instance { get { return instance; } }
}

* This source code was highlighted with Source Code Highlighter .


The thread safety of this implementation is based on the fact that a static constructor (or, in other words, a type initializer) in one domain is guaranteed to be called no more than once. And if so, then on the part of the developer, you do not need to do any feints with your ears in order to make thread safety even safer. Most developers (and until recently, including me) are calming down on this, since we have already solved the main problem that can occur with any singleton, so few people pay attention to the comment of an empty static constructor. And since this comment is generally not understandable, the empty static constructor simply does not reach many implementations in real-world applications.

Static constructor and field initializers



A static constructor is a thing designed to initialize a type, which must be called before accessing any static or non-static member, as well as before creating an instance of a class. However, if a class in C # does not explicitly declare a static constructor, the compiler marks it with a beforeFieldInit attribute, which tells the runtime environment that the type can be initialized in a deferred (“relaxed”) manner. However, as practice shows, in the .Net Framework up to version 4, this behavior can be called anything but “deferred”.

So let's consider the following code:

class Singleton
{
//static Singleton()
//{
// Console.WriteLine(".cctor");
//}

public static string S = Echo( "Field initializer" );

public static string Echo( string s)
{
Console .WriteLine(s);
return s;
}
}

class Program
{

static void Main( string [] args)
{
Console .WriteLine( "Starting Main..." );
if (args.Length == 1)
{
Console .WriteLine(Singleton.S);
}
Console .ReadLine();
}
}

* This source code was highlighted with Source Code Highlighter .


Since in this case the explicit static constructor of the class Singleton is missing, the compiler adds the beforeFieldInit attribute to this type. According to the specification, in this the initialization of the static field will occur before the first reference to this field, and it can happen long before this appeal. In practice, when using .Net Framework 3.5 and lower, this causes the initialization of a static field to occur before calling the Main method, even if the condition is args. Legnth == 1 will not be executed. All this leads to the fact that when you run the above code, we get the following:

Field initializer

Starting Main ...

As you can see, the static field will be initialized, although the type itself is not used in the application. Practice shows that in most cases, in the absence of an explicit constructor, the JIT compiler calls the initializer of static variables immediately before calling a method that uses this variable . If you uncomment the static constructor of the class Singleton , then the behavior will be exactly what most developers expect - the field initializer will not be invoked and when you start the application there will be only one line on the screen: Starting Main ...” .

NOTE

The developer cannot and should not be tied up while the static constructor is being called. If you follow the "letter of the law", then it is quite possible that in the example above (without an explicit type constructor), the variable is Singleton. S will not be initialized when creating an instance of the Singleton class and when calling a static method that does not use the S field , but will be initialized when it calls a static function that uses the S field. And although it is this behavior that is originally embedded in the definition of the beforeFieldInit flag, the C # language specification specifically states that the exact time of the call is determined by the implementation. So, for example, when launching the above source fragment (without an explicit static constructor) under the .Net Framework 4, we will get more expected behavior: the S field will not be initialized ! More details about this can be found in the additional links at the end of the article.

Static constructors and deadlock



Since the static constructor of the specified type must be called no more than once in the application domain, the CLR calls it inside some lock. Then, if the thread executing the static constructor waits for the completion of another thread, which in turn tries to capture the same CLR lock, we get a classic deadlock.

To reproduce a similar situation in ideal conditions is quite simple: to do this, it is enough to create a new stream in a static constructor and try to wait for its execution:

class Program
{
static Program()
{
var thread = new Thread(o => { });
thread.Start();
thread.Join();
}

static void Main()
{
// ,
//
// Program
}
}

* This source code was highlighted with Source Code Highlighter .


Moreover, if we refer to the CLI specification, then it says that deadlock is possible with explicit or implicit calling of a blocking operation inside the static constructor. In practice, this means that an attempt to block a stream inside a static constructor, for example, due to the use of a named mutex can in some cases lead to a deadlock.

Bug in real application



All this nonsense, associated with the time for calling field initializers and deadlocks in static constructors, may seem sucked from the finger and unlikely in real-world applications. To be honest, I had the same opinion a week ago, before I spent the whole day debugging one very unpleasant bug.

So, here are the symptoms of the real problem I faced. We have a service that works great in the console mode, and it also works just fine as a service if we build it in Debug. However, if you collect it in a release, it starts once: once it starts successfully, and the second time the start drops due to a timeout (by default, SCM nails the process if the service does not start in 30 seconds).

As a result of debugging, the following was found. (1) We have a service class, in the constructor of which the performance counters are created; (2) the service class is implemented as a singleton using initialization of a static field without an explicit static constructor, and (3) this singleton was used directly in the Main method to start the service in console mode:

//
partial class Service : ServiceBase
{
// "" .
public static readonly Service instance = new Service();
public static Service Instance { get { return instance; } }

public Service()
{
InitializeComponent();

//
var counters = new CounterCreationDataCollection();

if (PerformanceCounterCategory.Exists(category))
PerformanceCounterCategory.Delete(category);

PerformanceCounterCategory.Create(category, description,
PerformanceCounterCategoryType.SingleInstance, counters);
}

//
public void Start()
{}

const string category = "Category" ;
const string description = "Category description" ;
}
// Program
static void Main( string [] args)
{
if (args[0] == "--console" )
Service.Instance.Start();
else
ServiceBase.Run( new Service());

}

* This source code was highlighted with Source Code Highlighter .


Since the Server class does not contain an explicit static constructor and the C # compiler adds the beforeFieldInit flag, the Service class constructor is called before the Main method is called. At the same time, a named mutex is used to create a category of performance counters, which, under certain conditions, leads to the application deadlock: at the time of the first launch, the specified category is not yet in the system, therefore the Exists method returns false and the Create method succeeds; during the next run, the Exists method returns true , the Delete method succeeds, but the Create method hangs forever. It is clear that after the problem was found, the solution took exactly 13 seconds: add a static constructor to the Service class.

Conclusion



An example with a bug in a real application says that articles about the pitfalls of the C # language and about the correct application of known patterns and idioms are not delusions and fiction of theorists (**), many of these articles are based on bumps stuffed in the real world. Today you may have problems with the implementation of the singleton curve, tomorrow - with the incomprehensible behavior of changeable significant types, the day after tomorrow you nail the thread with Thread.Abort and get the system state (***) that is not consistent. All these problems are very real and an understanding of the principles underlying them can save another day when searching for some particularly evil bug.

Additional links





-------------------------

(*) Two other very popular implementations of the Singleton pattern are: (1) double checked locking, and (2) using the Lazy < T> type , which appeared in .Net Framework 4.

(**) One of my colleagues firmly believes that reading books and articles does not make sense, since they are written by those who do not understand anything in real software development. Therefore, he believes that there is only one “real” singleton implementation, and that you need to add an empty finalizer to all classes that implement the IDisposable interface.

(***) If you are wondering what such problems are hidden in mutable significant types, then the previous article “On the dangers of mutable significant types” is fine, but if you're wondering what is so bad about calling Thread.Abort, then there are even two Notes: “On the Dangers of Calling Thread.Abort” , as well as the translation of the interesting article by Chris Sell “Studying ThreadAbortExcpetion with the help of Rotor”

Source: https://habr.com/ru/post/125421/


All Articles