Wrong Answer #2: Your own ThreadLocal
Originally posted at 12/15/2010
Well, last time we tried to introduce a finalizer, but we forgot that we were using our own instance as the key to the slots dictionary, which prevented us from being collected, hence we never run the finalizer.
All problems can be solved by adding an additional level of indirection… instead of using our own instance, let us use a separate instance:
public class CloseableThreadLocal { [ThreadStatic] public static Dictionary<object, object> slots; object holder = new object(); public static Dictionary<object, object> Slots { get { return slots ?? (slots = new Dictionary<object, object>()); } } public /*protected internal*/ virtual Object InitialValue() { return null; } public virtual Object Get() { object val; if (Slots.TryGetValue(holder, out val)) { return val; } val = InitialValue(); Set(val); return val; } public virtual void Set(object val) { Slots[holder] = val; } public virtual void Close() { GC.SuppressFinalize(this); if (slots != null)// intentionally using the field here, to avoid creating the instance slots.Remove(holder); } ~CloseableThreadLocal() { Close(); } }
Now it should work, right?
Expect that it doesn’t… We still see that the data isn’t cleaned up properly, even though the finalizer is run.
Why?
Comments
We seem to be setting it with the key as "holder" but when removing, we're doing slots.Remove(this). Could that be it?
beautiful technique.
Ashic,
Nope, that is a bug in the wrong example, fixed now
The finalizer runs on another thread, so the slots field is null when the finalizer runs. The slots field on the original thread is never touched in the finalizer.
You could use WeakReference class instead of own "holder": msdn.microsoft.com/.../system.weakreference.aspx
but why a threadstatic static backing field? seems that the dictionary was suppose to id instances by key but the threadstatic attribute allocated a new backing field per thread..
Ori,
Take a look at the class name
ooops :)
The idea is nice but he forgot to pass the context to make the finalizer work. This one would do the trick:
public class CloseableThreadLocal
<closer,> slots = new Dictionary <closer,> ();
<closer,> Slots
Yes it does not scale that well because it uses a global dictionary but until you show me the requirement that thread thrashing is a problem it is out of scope.
How about this guys? Any comments?
<object,> _slots;
<object,> slots;
<object,> Slots {
<object,> ()); }
The finalizer runs in the GC thread.
You could change the dictionary to a concurrent dictionary and then keep a local reference to the one you are using in CloseableThreadLocal.
Something like this:
<object,> _mySlots;
<object,> MySlots
In Get and Set use MySlots in Close() use _mySlots
The CloseableThreadLocal will get collected, but the key (and the value) will still be referenced by the slots, and will therefore stay uncollected.
Why not use WeakReference? Isn't this the exact scenario WeakReference is intended for?
How about this?
public class CloseableThreadLocal {
<int,> _slots;
<int,> Slots {
<int,> ()); }
<int GenerateThreadKey = () => string.Format("{0}{1}", Thread.CurrentThread.ManagedThreadId, _counter).GetHashCode();
<int,>
<int,>
<int,>
My Closable class needs a flag in the ctor to prevent the finalizer from beeing run when the thing is queried. Apart from this minor issue it should work.
@Hendry, the Close() method removes the key and value, doesn't this mean they will be collected after all.
My first working solution collected all thread slots. That doesn't seem to be necessary, so I modified it. Just keeping a local reference makes the code simpler and removes the need for additional cleanup.
A fun exercise, by the way. Too bad I need so many iterations and hints before I get it right ;)
http://pastebin.com/d9HSepi5
Comment preview