Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm writing a class which is roughly analogous to a CancellationToken, except it has a third state for "never going to be cancelled". At the moment I'm trying to decide what to do if the 'source' of the token is garbage collected without ever being set.

It seems that, intuitively, the source should transition the associated token to the 'never cancelled' state when it is about to be collected. However, this could trigger callbacks who were only kept alive by their linkage from the token. That means what those callbacks reference might now in the process of finalization. Calling them would be bad.

In order to "fix" this, I wrote this class:

public sealed class GCRoot {
    private static readonly GCRoot MainRoot = new GCRoot();
    private GCRoot _next;
    private GCRoot _prev;
    private object _value;
    private GCRoot() {
        this._next = this._prev = this;
    }
    private GCRoot(GCRoot prev, object value) {
        this._value = value;
        this._prev = prev;
        this._next = prev._next;
        _prev._next = this;
        _next._prev = this;
    }
    public static GCRoot Root(object value) {
        return new GCRoot(MainRoot, value);
    }
    public void Unroot() {
        lock (MainRoot) {
            _next._prev = _prev;
            _prev._next = _next;
            this._next = this._prev = this;
        }
    }
}

intending to use it like this:

Source() {
    ...
    _root = GCRoot.Root(callbacks);
}
void TransitionToNeverCancelled() {
    _root.Unlink();
    ...
}
~Source() {
    TransitionToNeverCancelled();
}

but now I'm troubled. This seems to open the possibility for memory leaks, without actually fixing all cases of sources in limbo. Like, if a source is closed over in one of its own callbacks, then it is rooted by the callback root and so can never be collected.

Presumably I should just let my sources be collected without a peep. Or maybe not? Is it ever appropriate to try to control the order of finalization, or is it a giant warning sign?

share|improve this question
2  
Finalization is non-deterministic. Period. This is on purpose and by design. Now if you want to control the order of objects being disposed, you can call dispose in an appropriate sequence, and if need be hang that sequence off your finalizer, but trying to ply the finalizer by directly defining the GC Root structure is a sign you're probably thinking about your problem wrong, or maybe you really need to write it in C++ where destructors are deterministic. –  Jimmy Hoffa Dec 4 '12 at 6:34
add comment

1 Answer

up vote 4 down vote accepted

Is it appropriate to try to control the order of finalization?

No, it's not. That you're thinking about finalizers at all is a giant warning sign. If you need to dispose of unmanaged resources, use Dispose. If you want scoped release of some resource, reconsider; then use Dispose if you really benefit from it.

I also would be skeptical about some of your concerns regarding callbacks being in some finalizer state. If you can trigger them, the classes they call can't by definition be collected.

share|improve this answer
2  
Not collected, finalized. For example, I might have an action that writes '1' then flushes a stream. But, before it is triggered by my class being finalized, the stream is finalized (flushed and closed). –  Strilanc Dec 4 '12 at 4:37
    
If you need to dispose of unmanaged resources, use Dispose That's not quite right. Unmanaged resources require a finalizer. Note that in Implementing Finalize and Dispose to Clean Up Unmanaged Resources the base finalizer calls Dispose(false) to clean up only the unmanaged resources. Implementing Dispose alone is used to clean up managed resources that implement IDisposable, like a stream which is a managed resource. –  Conrad Frix Dec 4 '12 at 5:49
2  
To be fair, for safeties sake one should think about and implement a finalizer if they have unmanaged resources to dispose, though they should be aware it is by definition non-deterministic and therefore you need to check nulls on any extraneous objects you try touching to avoid dereferencing as they may have been collected already. Also be certain to SuppressFinalize to avoid attempting to dispose twice. Though having finalizers sucks for GC and memory, you can't guarantee someone won't forget to dispose your object two months or two years from now, and you don't want the leak. –  Jimmy Hoffa Dec 4 '12 at 6:39
    
@ConradFrix iirc, finalizers are not guaranteed to be called in all cases. I don't have the spec in front of me to run it down. –  Telastyn Dec 4 '12 at 12:50
1  
See blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx . His final point "A correctly-written program cannot assume that finalizers will ever run." See also blogs.msdn.com/b/cbrumme/archive/2004/02/20/77460.aspx ("Finalization during process termination will eventually timeout.") –  Brian Dec 4 '12 at 15:42
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.