SQL Server Compact Garbage Collection – Whys and Hows
Some background about SQL Server Compact:
SQL Server Compact is an embedded database implemented in native and it can be accessed in application by either OLEDB or ADO.NET provider model.
ADO.NET provider for SQL Server Compact is a managed assembly which depends on native SQL Server Compact DLLs to provide the service. In general, the managed classes under ADO.NET provider model for SQL Server Compact are just wrappers around the native classes.
SQL Server Compact native objects are dependent. For instance, Cursors and Transactions are dependent on Connection object and are expected to be released in the right order.
What’s the problem then? In native application, the developer can dispose the cursors, commands, etc. before closing the connection but in managed application with .Net Garbage Collector there is no particular order in which these objects will be disposed.
So what do we do? Our SqlCeConnection object maintains weak references to all objects tied with it. Or to be specific, it maintains short weak references.
How does it help me in disposing the objects in order? When the connection object is getting disposed, the weak reference cache is iterated and all the objects there are disposed before the connection object getting disposed. Please note that when we say Dispose, we also mean Finalization.
How does .Net Garbage Collector comes into picture of SQL Server Compact
Simply employing short weak references to keep track of all related objects and dispose them in sequence does not solve all the problems.
What is the problem then? Sometimes the database files can remain locked in stress scenarios because not all the dependent objects are disposed upon dispose of SqlCeConnection. This happens because we use short weak refs to track object lifetime and we could end up in situations in which an object is in the finalization queue (hence the short reference is not longer alive) and isn’t in the freachable queue (hence isn’t guaranteed to be picked up by the GC immediately). More information about short weak references can be found here.
All this leads to a situation, where the database file is still locked even though customer application closed the connection. Hence, it is not able to get deleted the file using file explorer …etc means.
How did we solve the problem? When we are cleaning weak reference cache in dispose of SqlCeConnection object, we need to call GC.WaitForPendingFinalizers() before we return to the caller; in this way, we are guranteed that the GC will pick up all finalizable objects and hence all the native interfaces will be properly released even if we no longer have short references to them.
More information about this API can be found here.
1. Why are we employing short weak references and not long weak references?
Ans.: SQL Server Compact ships for both Desktops as well as Devices. Long weak references are not supported in .Net CF. More information here.
2. Does call to GC.WaitForPendingFinalizers() has negative performance impact?
Ans.: No. Most of the time, the freachable queue is empty (the same is not true for finalization queue though). See here for more information about Garbage Collector.
3. Can this call to GC.WaitForPendingFinalizers() lead to deadlocks?
Ans.: Yes. If Dispose method of SQL CE objects are called in the finalization context than the explicit dispose context. The reason being when an object is getting finalized, it is not supposed to touch any other managed objects as the currently getting disposed object does not know the life-status of the object it is trying to refer.
4. Why did we choose this design?
Ans.: SQL CE was primarily designed for Devices where the memory, processing comes at very high cost. So, it has been designed to free up the resources as early as we can and hence it is calling GC.WaitForPendingFinalizers.