0

I try to hold a singleton C++/CLI object from a native object which is held by another C++/CLI object which is held by the C# App.

TestGcroot::App (C#) -> Class1 (C++/CLI) -> Class2 (native) -> Something (C++/CLI) ClassLibrary1::SharedSomething (C++/CLI) -> Something (C++/CLI)

Since both Class2 and SharedSomething hold the same Something I would expect that Something will be alive for the lifetime of Class2. However the observation is that when the application shuts down Something is finalized (thus destroyed) before Class2 is destroyed. The Class2::gcroot still holds Something but it is like referencing a deleted pointer - it crashes the application.

In C++ terms the situation looks as if I have passed the same raw pointer to two shared_ptr. One will destroy the object while the other still "holds" the object.

Here is the code that demonstrates the issue:

namespace ClassLibrary1 {

    ref class Something {
    public:
        Something() {
        }
        ~Something() {   // Dispose
            this->!Something();
        }
        !Something() {    // Finalize() - this happens before ~Class2() even though Class2 holds a gcroot handle to the same Something object!!!
        }
    };

    ref class SharedSomething {
    public:
        SharedSomething() {
        }
        ~SharedSomething() {   // Dispose()
            this->!SharedSomething();
        }
        !SharedSomething() {   // Finalize()
        }
        static Something^ Get() {
            return _something;
        }
    private:
        static Something^ _something = gcnew Something();
    };

    class Class2 {
    public:
        Class2() {
            _something = SharedSomething::Get();
        }
        ~Class2() {
            // _something is dead even though it has not been reset!!!
        }
    private:
        gcroot<Something^> _something;
    };

    public ref class Class1 {
    public:
        Class1() {
            _class2 = new Class2();
        }
        ~Class1() {   // Dispose()
            this->!Class1();
        }
        !Class1() {   // Finalize()
            delete _class2;
        }
    private:
        Class2* _class2;
    };
}
namespace TestGcroot
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            _class1 = new ClassLibrary1.Class1();
        }
        ClassLibrary1.Class1 _class1;
    }
}

I have the complete code example at https://github.com/georgevs/TestGcroot.git

EDIT: the code is failing even if no singletons or statics are involved:

App (C#) -> Class1 (C++/CLI) -> Class2 (native) -> Something (C++/CLI)

Something finalizer is called before both the Class2 destructor and Class1 finalizer. Even if Class1 creates Something and passes it to Class2, still Something finalizer is called first, and Class1 finalizer is called next while still referencing Something!?!

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
George S.
  • 613
  • 5
  • 11
  • During shutdown, things are.... different. Everything is eligible for finalization, gcroots are not respected. – Ben Voigt Mar 15 '16 at 05:07
  • 1
    Finalizers are tricky. Trickier than most people think. Read Eric Lippert's articles here: [part1](http://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/), [part2](http://ericlippert.com/2015/05/21/when-everything-you-know-is-wrong-part-two/) – Lucas Trzesniewski Mar 15 '16 at 09:08
  • 1
    It is not particularly clear what "failure" looks like, your test project behaves correctly. The only object root that matters is `_class1`, the rest are just circular references. As soon as the object referenced by _class1 gets collected, everything is dead and the finalizers are added to the queue. To be executed in no particular order, seeing Something go first is not wrong, covered in [this Q+A](http://stackoverflow.com/questions/4163603/why-garbage-collector-takes-objects-in-the-wrong-order). – Hans Passant Mar 15 '16 at 10:13
  • @Hans Passant I don't think there is a circular references in the example provided. There is an object which is referenced from more than one other objects but there are no loops in the dependencies tree. Even with simple case of App -> Class1 -> Something the GC finalizer first calls Something and then calls Class1 finalizer. – George S. Mar 15 '16 at 17:30
  • I guess once an object (in this case Class1) is found to be finalizable its dependencies (in this case Something) are also finalizable (unless refered by a live object). My theory is that because of the non-deterministic GC finalization the order of the calls on the finalizers is undefined and not dependent on the dependencies in the objects themselves. – George S. Mar 15 '16 at 17:30
  • This is from the article @Lucas Trzesniewski refered: "Myth: An object being finalized can safely access another object. This myth follows directly from the previous. If you have a tree of objects and you are finalizing the root, then the children are still alive — because the root is alive, because it is on the finalization queue, and so the children have a living reference — but the children may have already been finalized, and are in no particularly good state to have their methods or data accessed." – George S. Mar 15 '16 at 17:36
  • The only solution I have found to work is deterministic GC (Dispose()) all the way down the chain of dependencies. If at any given point an object is left on the non-deterministic GC (Finalize()) all bets are off with regards to its own dependencies lifetime. Bummer! I don't know why they decided to implement it this way... – George S. Mar 15 '16 at 20:48
  • Myth: Finalizers run in a predictable order Suppose we have a tree of objects, all finalizable, and all on the finalizer queue. There is no requirement whatsoever that the tree be finalized from the root to the leaves, from the leaves to the root, or any other order – George S. Mar 16 '16 at 00:00

0 Answers0