In my two-week long quest to solve a problem:
- How to get notified before static variables are finalized
- Unload event for the default Application Domain?
- Profiling ASP.net applications over the long term?
the fundamental problem is the garbage collector. My object's Finalizer would have been the ideal time to react when the object is about to be freed (i.e. to have its resources reclaimed). The problem with the .NET garbage collection system is that by the time my finalizer is called, it's entirely likely that other objects i own have already been finalized.
The problem would be trivial to solve if i were interoping with native class objects. The garbage collector is unable to free those objects behind my back (and without my permission). So by the time my managed object's finalizer is called, i know that my internal state is still valid.
What i need is a way to tell the garbage collector to keep your hands off.
Is there a ways to prevent an object from being finalized?
For example
For example, the following code is buggy, because the finalizer mistakenly things that the private _values
object still exists. In reality it is likely already finalized out from under me:
class Sqm
{
private List<Value> _values = new List<Value>();
//finalizer
public ~Sqm()
{
Shutdown();
}
protected void Shutdown()
{
foreach (var value in _values) //<-- crash; _values no longer exists
SaveValueToHardDrive(value); //<-- crash; value no longer exists
}
}
What i need is a way to tell the garbage collector not to finalize that list object, or any of the objects inside it:
class Sqm
{
private List<Value> _values = new List<Value>();
//constructor
public Sqm()
{
GC.LetMeManuallyFinalize(_values);
}
//finalizer
public ~Sqm()
{
Shutdown();
GC.ManuallyFinalize(_values);
}
protected void Shutdown()
{
foreach (var value in _values)
SaveValueToHardDrive(value);
}
}
That has two possible problems:
- there is no
GC.ManuallyFinalize
method it might suppress freeing the
_values
list itself, but the objects it references would likely still be finalized behind my back:protected void Shutdown() { foreach (var value in _values) SaveValueToHardDrive(value); //<---crash, contained object already finalized }
So now i need to ensure that the objects, as they are added to the list, are also excluded from finalization:
public void AddSample(String name, Int64 value)
{
Entry entry = GetEntryByName(name);
if (entry == null)
{
entry = new Entry();
GC.LetMeManuallyFinalize(entry);
}
entry.Count += 1;
entry.Sum += value;
entry.Average = entry.Sum / entry.Count;
}
//finalizer
public ~Sqm()
{
foreach (var value in _values)
GC.ManuallyFinalize(value);
GC.ManuallyFinalize(_values);
}
That probably has the problem that while Entry
has no other internal objects, i don't know about List<T>
. And the garbage collector might perform an unwanted labotomy on _values
even though _values
itself hasn't been finalized.
Use GCAlloc.Alloc()
@MatthewWatson had an excellent idea. i think it would be useful to point out why it's wrong. Use GCAlloc.Alloc
to hold a reference to the object. Then you use can access it during your finalizer:
public Sqm()
{
private List<Value> _values = new List<Value>();
private GCHandle _valuesHandle; //handle to keep _values alive
//constructor
Sqm()
{
//prevent _values from being finalized
_valuesHandle = GCAlloc.Alloc(_values);
}
//finalizer
~Sqm()
{
try
{
Shutdown(_values); //Safe, right? RIGHT? _values couldn't have been finalized
}
finally
{
_valuesHandle.Free();
}
}
private void Shutdown(List<Values> values)
{
foreach (var value in values)
{
//The list itself might not have been finalized
//But objects used internally to Microsoft's List<T> class have been finalized
//and objects in the list itself were already finalized
SaveValueToHardDrive(value); //<--BAD: values inside list were already finalized
}
}
}
Note: It also probably fails because of the pseudo-documented behavior. From The Truth About GCHandles:
When you create a new
GCHandle
, a new entry in the AppDomain's handle table is created. This entry is kept until the handle is freed (viaGCHandle.Free()
) or the AppDomain is unloaded.
Emphasis mine.
So that's right out. i need to tell the garbage collector
Do not finalize this object (and everything inside it)
The tricky part is the internal private members of classes i do not own; even an object still referenced by GCAlloc
will still have objects it depends on finalized behind it's back.
Sample Usage
public static Foo
{
public static Sqm = new Sqm();
}
Foo.Sqm.AddSample("QueryCustomerInfo", stopwatch.TotalMicroseconds);