Difference between revisions of "HOWTO resolve circular references in ns-3 memory disposal"
(→HOWTO resolve smart pointer circular references in ns-3 memory disposal) |
(add TOC) |
||
(5 intermediate revisions by 2 users not shown) | |||
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. | ||
+ | |||
+ | For example, the following code will cause valgrind to report a memory leak due to a circular reference. | ||
class A : public Object | class A : public Object | ||
Line 22: | Line 26: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | 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== LEAK SUMMARY: | ||
==15749== definitely lost: 40 bytes in 1 blocks | ==15749== definitely lost: 40 bytes in 1 blocks | ||
Line 27: | Line 34: | ||
==15749== possibly lost: 0 bytes in 0 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: | + | 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 | class A : public Object | ||
Line 36: | Line 43: | ||
virtual void DoDispose (void) | virtual void DoDispose (void) | ||
{ | { | ||
+ | //remove A's reference to B | ||
m_callback = MakeNullCallback<void>(); | m_callback = MakeNullCallback<void>(); | ||
} | } | ||
Line 60: | Line 68: | ||
==16877== possibly 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 | + | 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: | + | For example, an explicit call to Dispose() is not necessary here: |
<pre> | <pre> | ||
int main(int argc, char* argv[]) | int main(int argc, char* argv[]) | ||
Line 74: | Line 82: | ||
} | } | ||
</pre> | </pre> | ||
− | |||
− | |||
==17541== LEAK SUMMARY: | ==17541== LEAK SUMMARY: |
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