Difference between revisions of "HOWTO resolve circular references in ns-3 memory disposal"

From Nsnam
Jump to: navigation, search
(some edits for clarifications)
(add TOC)
 
Line 1: Line 1:
 +
{{TOC}}
 +
 
ns-3's use of reference counting pointers (class ns3::Ptr) and callbacks will sometimes result in reference cycles that make it difficult to dispose of objects cleanly.  The valgrind memory management tool will then report these cycles as memory leaks.  While these are not really harmful leaks (they are just reported as leaks at program termination), they can cloud the reporting of true leaks.   
 
ns-3's use of reference counting pointers (class ns3::Ptr) and callbacks will sometimes result in reference cycles that make it difficult to dispose of objects cleanly.  The valgrind memory management tool will then report these cycles as memory leaks.  While these are not really harmful leaks (they are just reported as leaks at program termination), they can cloud the reporting of true leaks.   
  

Latest revision as of 19:23, 31 August 2013

Main Page - Current Development - Developer FAQ - Tools - Related Projects - Project Ideas - Summer Projects

Installation - Troubleshooting - User FAQ - HOWTOs - Samples - Models - Education - Contributed Code - Papers

ns-3's use of reference counting pointers (class ns3::Ptr) and callbacks will sometimes result in reference cycles that make it difficult to dispose of objects cleanly. The valgrind memory management tool will then report these cycles as memory leaks. While these are not really harmful leaks (they are just reported as leaks at program termination), they can cloud the reporting of true leaks.

For example, the following code will cause valgrind to report a memory leak due to a circular reference.

    class A : public Object
    {
    public:
       static TypeId GetTypeId (void);
       Callback<void> m_callback;
    };
    class B : public Object
    {
    public:
       static TypeId GetTypeId (void);
       void CallbackMethodB (void);
    };
     int main(int argc, char* argv[])
     {
        Ptr<A> a = CreateObject<A>();
        Ptr<B> b = CreateObject<B>();
        a->m_callback = MakeCallback (&B::CallbackMethodB, b);
        b->AggregateObject(a);
     }
     

In the above code, a has a reference to b, via its callback, and b has a reference to a, via its aggregated object list. Running this code with Valgrind will produce:

    ==15749== LEAK SUMMARY:
    ==15749==    definitely lost: 40 bytes in 1 blocks
    ==15749==    indirectly lost: 152 bytes in 5 blocks
    ==15749==    possibly lost: 0 bytes in 0 blocks

The preferred way to break reference cycles like this in ns-3 is to use Object::Dispose(). This method will call the DoDispose method on the object that it is called on as well as all other objects aggregated on to it. In the above example, Class A is aggregated to Class B so we make the following change to Class A:

    class A : public Object
    {
    public:
       static TypeId GetTypeId (void);
       Callback<void> m_callback;
       virtual void DoDispose (void)
       {
          //remove A's reference to B
          m_callback = MakeNullCallback<void>();
       }	
    };

Now the following code:

     int main(int argc, char* argv[])
     {
        Ptr<A> a = CreateObject<A>();
        Ptr<B> b = CreateObject<B>();
        a->m_callback = MakeCallback (&B::CallbackMethodB, b);
        b->AggregateObject(a);
        b->Dispose(); 
        //a->Dispose() will work as well.  Both will end up calling DoDispose 
        //in object 'a' which breaks the reference cycle	
     }
     

will no longer have a memory leak.

    ==16877== LEAK SUMMARY:
    ==16877==    definitely lost: 0 bytes in 0 blocks
    ==16877==    indirectly lost: 0 bytes in 0 blocks
    ==16877==    possibly lost: 0 bytes in 0 blocks

An explicit call to Dispose() is not always necessary. Before the simulator exits, Dispose() is called on all ns-3 nodes. Therefore if your object is aggregated onto a node or aggregated onto something that is aggregated onto a node etc., explicitly calling Dispose may not be necessary.

For example, an explicit call to Dispose() is not necessary here:

     int main(int argc, char* argv[])
     {
        Ptr<A> a = CreateObject<A>();
        Ptr<B> b = CreateObject<B>();
        Ptr<Node> node = CreateObject<Node>();
        a->m_callback = MakeCallback (&B::CallbackMethodB, b);
        b->AggregateObject(a);
        node->AggregateObject(b);
     }
     
    ==17541== LEAK SUMMARY:
    ==17541==    definitely lost: 0 bytes in 0 blocks
    ==17541==    indirectly lost: 0 bytes in 0 blocks
    ==17541==    possibly lost: 0 bytes in 0 blocks