3

In my two-week long quest to solve a problem:

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 (via GCHandle.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);
Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • You mean something like [`GC.KeepAlive`](http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx)? EDIT: Although, I'm not sure in ASP.NET if this helps when worker processes are recycled for example. – Chris Sinclair Aug 15 '13 at 14:49
  • @ChrisSinclair Something like `GC.KeepAlive` would be good; but that only prevents an object from being collected from the start of the current method up until the call to `GC.KeepAlive`. Once `GC.KeepAlive` returns the object is available to be collected. – Ian Boyd Aug 15 '13 at 15:17
  • Hrrm yeah... Maybe you can keep a static pool for these objects (which would make them never be picked up by the GC), then abstract their access so you're manually counting references to them (maybe by even using `WeakReference`). You can occasionally do a pass through your static listing and if all their counted references (or all their `WeakReference` pointers are lost) then you can then call their `Shutdown`. – Chris Sinclair Aug 15 '13 at 15:20
  • 1
    Can't you use [`GCHandle.Alloc()`](http://msdn.microsoft.com/en-us/library/a95009h1.aspx) to prevent the object from being collected, even if it's not reachable? That's the usual way to say "hands off!". – Matthew Watson Aug 15 '13 at 15:27
  • 1
    @MatthewWatson That sounded promising, until you realize that `GCHandle.Alloc()` may keep an object from being collected (e.g. my instance of `Dictionary` won't be finalized), it doesn't stop the internal objects inside `Dictionary` from being collected. So by the time i go to use it, my dictionary has been lobotomized, (e.g. it's internal `KeyCollection` object was finalized out from underneath **it**) – Ian Boyd Aug 15 '13 at 17:35
  • @IanBoyd That's odd - if you are keeping an object alive via GCHandle.Alloc() it should count as a root object, and should therefore keep alive any object reachable from it.. – Matthew Watson Aug 15 '13 at 20:09
  • @MatthewWatson i the gotcha comes from the fact that the AppDomain is shutting down. i linked to (and quoted some of) a blog post from Eric Lippert, where he notes *"This entry is kept until the handle is freed (via GCHandle.Free()) or the AppDomain is unloaded"*. App domain is unloading; GCHandle won't protect me. – Ian Boyd Aug 15 '13 at 21:19

1 Answers1

0

It's a very bad practice to do any long term operations during finalization, like input-output, anyway. You should consider storing your _values lists of your objects in the static list - this will prevent values from destruction. When your object is being finalizing, you could save the reference to the inner list in another static list, which is checked periodically. When it contains references to lists, it will mean that your object was destructed and values it contained should be saved.

class Sqm
{
  private static List<List<Value>> = _lists = new List<List<Value>>();
  private static List<List<Value>> = _finalizationQueue = new List<List<Value>>();
  private List<Value> _values = new List<Value>();
  Sqm() { _lists.Add(_values); }
  ~Sqm() { _finalizationQueue.Add(_values); }
  public static void CheckAndSave()
  {
    foreach(var list in _finalizationQueue)
      SaveValues(list);
  }
}

UPD.: If domain may be shutdown when you dont want it to, your only way is to store values in another domain.

Aberro
  • 620
  • 6
  • 10