Functional correctness in the current scenario
If multiple code flows of the program are instantiating and using the Logger class, then generated logs can get complex and might become a spaghetti when we start reading them.
- Chronological ordering of log statements: It becomes important in terms of the time stamp of log statements so that the logs statements come out crisp and clean, else it will become a garbled piece of information. An ordering of log statements depicts the flow of control in your code. Multiple instances of Logger class which consequently creates instances of StreamWriter class can mess it up. The content of the file can get overwritten or can get written out of order.
- Simultaneous access can cause errors: When several StreamWriter class instances try to access same log file at one time, then they might get errors like “file is already in use except during write operation”. This mainly happens when earlier handles were not closed after use and now someone is trying to open the file with exclusive write locks. In case of such exceptions, our log statements can get lost.
- Multi-threading and cross-thread synchronization: Simultaneous access can happen more frequently in case of multi-threaded applications. When multiple threads running in different modules of your application are trying to gain access to write the same log file, then it can often result in same access violation issues due to file being already in use errors and things like that. How would you keep a check on this?
Let’s say even if we don’t face the above errors and all this works just fine functionally by luck or due to our application being very less complex even then we can face performance issues also. Let’s explore that aspect.
Application Performance in Current Scenario
The code we saw in the previous sections has got some costly bits involved from your application performance stand-point:
- Instance of Logger class
- Instance of StreamWriterclass (inside Log method’s using block): Instance of StreamWriter class internally involves creating unmanaged file handles at OS level. During disk I/O lot of things happen at operating system level like checking user access permission, etc.
So, each of the above operations might take a fraction of a second or maybe in milliseconds and you might think that why should I care about the cost of creating a file handle (which is eventually an object instance) in this era where I have got 32 GB RAM computers and website server computers (or data centers) are even more powerful. I say we must care. You shouldn’t care about this aspect if you’re going to write few scores of log statements or going to open a few files handles here and there. But the point is that enterprise applications are very big in general. Really big applications can have even dozens or hundreds of log statements in a single flow of the program which ends up creating a hundred instances of StreamWriter class at the same time.
Let’s consider few points below on how the above discussion can contribute to performance issues:
- Redundant instantiation due to bad programming: Notice the logger2 and logger3 instances of Logger class in the code snippet of FetchData. Are they really required in GetStudentDatamethod? Can you somehow prohibit your team-members from making such a mistake? Is our Logger class capable of putting any such restriction?
- Uncontrolled method level local instantiation: A single web call from one user can involve creating multiple handles to log file as you might require logging several pieces of information through various nested methods involved in your flow, e.g., GetStudentData and GetDbConnection as shown above. More file handles objects in memory will increase the memory footprint of your application. If there are several such memory hungry applications running on your system, then your process can also face memory starvation.
- Increasing load on your application: I believe if I created a good enough public-facing website, then users of my website will only grow with time so the number of web calls per second will keep increasing where each web call is creating more handles allocating more resources in parallel, e.g., memory buffers, file handles, etc.
- Memory Leaks: File handles and their memory allocation is outside the purview of CLR’s managed memory space. You must be cautious about resource clean-up associated with such classes. Any miss on this front will cause memory leaks which can be horrible. Using the keyword in C# really comes to our rescue to deal with this scenario using IDisposable pattern. Missing using keyword is a direct invitation to memory leaks.
How Can We Avoid these Functional & Performance Issues?
So, we have some possible solutions that we can think currently:
- Global instance: Anybody would say that I could’ve maintained a global variable at class level which will maintain only one instance of StreamWriter Since there will be only once instance, we can ensure that memory leak doesn’t happen at the central place. But, globals are always considered a bad programming practice. Anybody can set it to null from anywhere in the code and then you start putting not null checks across your entire code base. To ensure that developers aren’t creating local instances of Logger class in any method, you will have to put few checks in place:
- Continuous code review to check for such bad instantiation
- Static code analysis (Linting) tools
And the fact remains that if the class is public, you can’t stop any developer on your team to instantiate it. The compiler treats all developers equally irrespective of designation. 😊
- Add more hardware: Running short of RAM on your computer due to increasing object instantiation or memory leaks? One obvious solution is to add more RAM. Isn’t it? But, you can’t keep upgrading the RAM of your server box forever the moment it is running out of capacity with increasing user load. Ultimately, there is a dollar cost involved and you always must stop your website from upgrading your hardware.
And let’s say even if I don’t face all these issues, even then our honest attempt should always be to reduce the memory footprint of our application. Isn’t it? An enterprise level application isn’t that simple as I’ve mentioned. Your server application requires memory for so many other things, e.g., threads, stack memory, I/O ports, etc. So, our intention should always be to minimize the main memory consumed by the process to make space for other activities and other processes running on the server.
So, all above pointers are giving us a push that we somehow need to restrict the possibilities of several files handles getting opened in parallel or if it happens, then it should happen in a controlled manner. How do we achieve it?
How Can We Reduce Object Creation: Overcoming Resource Contention with Singletons
You must have heard the concept of sharing. At your home in general, we all have one television (TV) set. Why? TVs are costly, so it will not be affordable for every household to purchase a TV for every family member. Also, you’ve got limited space (which is again a resource) in your house. You can’t use the entire space just for installing several TVs. So essentially, you settle with only one TV in your house and share it with all the family members. The same concept in computer science world is known as resource sharing.
So, one question which comes to our mind is that – Can we do some resource sharing here to solve our problem. Yes. That shareable resource is an instance of Logger class which manages the file handles to our log file through StreamWriter class.
It is always the case that your entire application will have centralized log files which contain the logs created by various modules of your application. Logger class will eventually create a file handle to the same file always. Isn’t it?
So why create several instances within a single web request from a single user or across web calls? Essentially why to leave a scope in code that anybody can just instantiate the Logger class as and when they want it. Can we not stop it from happening at code level itself? Why not enforce to have only one (aka single) instance of the object. That should ring some bells now. Singleton (the title of this article) has the word Single in it 😊.
So how can we ensure in code that one and only one (aka singleton) instance of our class is possible?
The static keyword
C# has a keyword called static. You can check Microsoft docs here for some quick details. static can have different usages/meanings while used in different contexts of C# programming but while used in the context of classes, it essentially means a thing which is associated with a type and not an instance of the type. If you apply static keyword to the class itself, then all the properties and methods get associated to that type directly, so you need the type (not the instance) when you need to refer them in code, e.g. <ClassName>.<MethodName>
A class decorated with static keyword can’t be instantiated. Let’s try to understand it with help of our Logger class. Let us say I’ve marked the Logger class with static keyword right after the “public” access specifier as shown below:
Now at the call site in Database access layer, we try to consume “Logger” class using new keyword:
So the moment we try creating an instance of static class it starts throwing below compile-time error:
Cannot create an instance of the static class ‘Logger’
So that’s what we wanted. Isn’t it? No developer can instantiate the Logger class at their will and that is what we’ve got now. But what about using it. We would require at least one instance to log our messages into the log file. Let us see a quick implementation on how we can make it work using <className>.<MethodName> syntax for static classes/methods in C#:
Static Logger full implementation (refer to StaticClassTextLogger.cs):
How things will change at call-site: (refer to FetchDataWithStaticTextLogger.cs)
So, what all did we achieve in the above code snippet:
- We have a single instance of one of the most heavy objects in our case, i.e., “StreamWriter”. Same static thing will get leveraged in every single call.
- We got an extremely simple implementation. Just an extra keyword static and we achieved our goal.
Are you able to notice any problem here so far in the static way of managing Singleton instance of a class? Please scribble few notes on a piece of paper. We will go through them in a later section.
By the way, I just used the phrase “managing Singleton instance”. But where the hell is the instance? You didn’t use “new” keyword even once to create any instance in memory. Isn’t it?
But Where is the Instance in Memory?
It is true that you have now been restricted by the compiler to create instances of the “Logger” class but you know that new-up operation which we apply on normal classes do the crucial work of allocating space to member variables of a class and returning a handle in the end with the help of which we do various operations on the object instance. So where is the instance here? How is memory getting allocated to static class members? You know it already that any code starts to work only after it gets loaded in the main memory of the computer which is RAM.
Yes. So that brings us all to an important concept in .NET called type instance. For every single class (static or non-static) that you have in your project, Common Language Runtime (CLR) creates a special instance for the class called Type instance. It contains all the meta information related to your class, e.g., fully qualified name, all method signatures and its code, etc.
So, all static stuff present in a class is associated directly with this special instance called type instance. If you declare static member variables, then they will lie in line with the memory area where type instance is residing. And that is how the <ClassName>.<MethodName> syntax came into being. By using that <ClassName>, you are referring to the type instance in memory and get your things done.
Please note that CLR guarantees that there will be one and only one type instance for a class per AppDomain in a .NET process. So, you can be rest assured that there will be only occurrences of all the static stuff associated with your class as they are all present with the type instance.
This is a nice concept to know. But is CLR behaving in-line with our expectations while managing static classes. Can there be any pit-falls in the way CLR manages static classes and their instantiation which can be a road block for my application later? Are statics really true Singletons? Let’s explore.
Why static Instance is Enough or NOT Enough for Managing Singletons
Ok. Let me jot down few problems that are there while managing singletons using static keyword. Take out your scribbled paper and start matching them.
The problems which are directly visible from looking at code:
- You don’t have full control over disposing of the static: How would you control the disposing of the object instance which is static instance in this case. You can’t. All the static stuff of your static class is associated with type instance which is controlled and managed by the CLR. In .NET, type instance is disposed of only when AppDomain/process is going to get unloaded. So, at best, the memory of the object referred by static member variable StreamWriter can still be reclaimed but the variable itself will remain alive. If static member variables are structs or value types, then they will also continue to live until the process starts to unload which results in the unloading of type instance.
- Static classes don’t participate in inheritance: Static classes are sealed by default in C#. So, if you have an already existing logger class in your code which inherits from some other class and now you want to restructure it to convert it into a singleton, then you’ve got a bigger work at hand. If you make it static, then you will have to let go of all the inheritance stuff and bring all the functionality from base class to the current class.
- Static classes can’t be passed around into methods as arguments: Method parameters are always referenced to instances in C# and Static classes can’t be instantiated.
- You can’t control the timing of instantiation of static fields: Let us consider a case where you are trying to load a singleton object which is very resource intensive, e.g., if it is loading some very heavy graphics object which must be commonly shared by the entire application. It can always be the case that the heavy singleton graphics object is not required initially at the load time of the application. You want to defer it (lazy loading) to a later time when it is required, otherwise, it will affect the start-up time of your application. Unfortunately, you don’t have a control on when the static fields get initialized as it is totally controlled by CLR. It can happen right when the application is starting up or maybe when AppDomain is getting created or any custom implementation/logic which is subject to change in any new version of CLR. In short, lazy loading or delayed initialization can’t be controlled in case of static classes.
- Static constructors aren’t fault tolerant: Static classes also have constructors where you might want to initialize or setup few things. By any chance, if you encounter an error (e.g., the database might be down currently which you are trying to connect) or an unhandled exception in the static constructor, then you lose the ability to give a retry to call the constructor again. CLR invokes the static constructor of a static class only once in the lifetime of the app domain. There is no built-in mechanism to put a retry mechanism to instantiate the type instance again if you can overcome the fault situation (database came back online) at a later point in time.
So, if you aren’t bothered by the above issues in the application you’re writing, then you are all good. A static class should suffice your needs as all the heavy lifting has already been done by CLR on your behalf to ensure that one and only one instance of the class gets created. Singleton instance was our original intention/requirement. Isn’t it?
If you think that the above issues with static might crib you down the line as your enterprise application evolves, then we might have to think another route. The key concern that we need to solve is – I as a programmer want to have absolute control over the instantiation and lifetime of the singleton instance. I don’t want to rely on CLR or Java Virtual Machine (JVM – CLR’s counterpart in Java world) as when my singleton instance will get created, retired for the creation or destroyed. Can we do something ourselves? Can we have an implementation of Singleton on our own?
Points of Interest
Hmmphhh! That was a lot to chew. Wasn’t it? Thanks for your patience to read this long article which sets the backdrop for the concluding blog. Let’s take some time to ponder and think of possible solutions for designing a singleton class ourselves. Once you’ve done the homework, then we can move to the concluding blog post: Singleton Design Pattern: The Pragmatic Approach – Part III.