add debugPtr.h to troubleshoot shared_ptr problems

A wrapper around shared_ptr which tracks backwards references
to help untangle complicated ownership situations (aka. ref loop
waiting to happen).
This commit is contained in:
Michael Davidsaver
2017-06-26 15:59:45 +02:00
parent 66633a7728
commit 568ee1fa85
6 changed files with 843 additions and 8 deletions
+2 -1
View File
@@ -20,6 +20,7 @@ INC += pv/messageQueue.h
INC += pv/destroyable.h
INC += pv/status.h
INC += pv/sharedPtr.h
INC += pv/debugPtr.h
INC += pv/localStaticLock.h
INC += pv/typeCast.h
INC += pv/sharedVector.h
@@ -40,4 +41,4 @@ LIBSRCS += messageQueue.cpp
LIBSRCS += localStaticLock.cpp
LIBSRCS += typeCast.cpp
LIBSRCS += parseToPOD.cpp
LIBSRCS += debugPtr.cpp
+145
View File
@@ -0,0 +1,145 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <pv/debugPtr.h>
namespace epics {
namespace debug {
struct tracker {
std::mutex mutex;
ptr_base::ref_set_t refs;
};
void shared_ptr_base::track_new()
{
if(track) {
std::lock_guard<std::mutex> G(track->mutex);
track->refs.insert(this);
}
snap_stack();
}
// create new tracker if ptr!=nullptr, otherwise clear
void shared_ptr_base::track_new(void* ptr)
{
track_clear();
if(ptr){
track.reset(new tracker);
std::lock_guard<std::mutex> G(track->mutex);
track->refs.insert(this);
}
snap_stack();
}
void shared_ptr_base::track_assign(const shared_ptr_base &o)
{
if(track!=o.track) {
track_clear();
track = o.track;
if(track) {
std::lock_guard<std::mutex> G(track->mutex);
track->refs.insert(this);
}
snap_stack();
}
}
void shared_ptr_base::track_clear()
{
if(track) {
std::lock_guard<std::mutex> G(track->mutex);
track->refs.erase(this);
}
track.reset();
m_depth = 0;
}
void shared_ptr_base::swap(shared_ptr_base &o)
{
// we cheat a bit here to avoid lock order, and to lock only twice
if(track) {
std::lock_guard<std::mutex> G(track->mutex);
track->refs.insert(&o);
track->refs.erase(this);
}
track.swap(o.track);
if(track) {
std::lock_guard<std::mutex> G(track->mutex);
track->refs.insert(this);
track->refs.erase(&o);
}
//TODO: keep original somehow???
snap_stack();
o.snap_stack();
}
void shared_ptr_base::snap_stack()
{
if(!track) {
m_depth = 0;
return;
}
#if defined(EXCEPT_USE_BACKTRACE)
{
m_depth=backtrace(m_stack,EXCEPT_DEPTH);
}
#else
{}
#endif
}
void shared_ptr_base::show_stack(std::ostream& strm) const
{
strm<<"ptr "<<this;
if(m_depth<=0) return;
#if 0 && defined(EXCEPT_USE_BACKTRACE)
{
char **symbols=backtrace_symbols(m_stack, m_depth);
strm<<": ";
for(int i=0; i<m_depth; i++) {
strm<<symbols[i]<<", ";
}
std::free(symbols);
}
#else
{
strm<<": ";
for(int i=0; i<m_depth; i++) {
strm<<std::hex<<m_stack[i]<<" ";
}
}
#endif
}
void ptr_base::show_refs(std::ostream& strm, bool self, bool weak) const
{
if(!track) {
strm<<"# No refs\n";
} else {
std::lock_guard<std::mutex> G(track->mutex);
for(auto ref : track->refs) {
if(!self && ref==this) continue;
strm<<'#';
ref->show_stack(strm);
strm<<'\n';
}
}
}
void ptr_base::spy_refs(ref_set_t &refs) const
{
if(track) {
std::lock_guard<std::mutex> G(track->mutex);
refs.insert(track->refs.begin(), track->refs.end());
}
}
}} // namespace epics::debug
+322
View File
@@ -0,0 +1,322 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
/* Author: Michael Davidsaver */
/* wrapper around shared_ptr which tracks backwards references.
* Can help to find ref. leaks, loops, and other exciting bugs.
* See comments in sharedPtr.h
*/
#ifndef DEBUGPTR_H
#define DEBUGPTR_H
#if __cplusplus<201103L
# error c++11 required
#endif
#include <ostream>
#include <memory>
#include <thread>
#include <mutex>
#include <set>
#include <pv/epicsException.h>
//! User code should test this macro
//! before calling epics::debug::shared_ptr::show_refs()
#define HAVE_SHOW_REFS
namespace epics {
namespace debug {
struct tracker;
class shared_ptr_base;
class ptr_base {
friend class shared_ptr_base;
template<typename A>
friend class shared_ptr;
template<typename A>
friend class weak_ptr;
protected:
typedef std::shared_ptr<tracker> track_t;
track_t track;
ptr_base() noexcept : track() {}
ptr_base(const track_t& track) :track(track) {}
ptr_base(const ptr_base&) = delete;
ptr_base(ptr_base&&) = delete;
ptr_base& operator=(const ptr_base&) = delete;
public:
typedef std::set<const shared_ptr_base *> ref_set_t;
void show_refs(std::ostream&, bool self=true, bool weak=false) const;
void spy_refs(ref_set_t&) const;
};
class weak_ptr_base : public ptr_base {
protected:
weak_ptr_base() {}
weak_ptr_base(const track_t& track) :ptr_base(track) {}
};
class shared_ptr_base : public ptr_base {
protected:
shared_ptr_base() noexcept
#ifndef EXCEPT_USE_NONE
:m_stack(), m_depth(0)
#endif
{}
shared_ptr_base(const track_t& track) :ptr_base(track)
#ifndef EXCEPT_USE_NONE
,m_stack(), m_depth(0)
#endif
{}
~shared_ptr_base() {track_clear();}
// add ourselves to tracker
void track_new();
// create new tracker if ptr!=nullptr, otherwise clear
void track_new(void* ptr);
// copy tracker and add ourself
void track_assign(const shared_ptr_base& o);
void track_clear();
void swap(shared_ptr_base& o);
void snap_stack();
#ifndef EXCEPT_USE_NONE
void *m_stack[EXCEPT_DEPTH];
int m_depth; // always <= EXCEPT_DEPTH
#endif
public:
void show_stack(std::ostream&) const;
};
template<typename T>
class shared_ptr;
template<typename T>
class weak_ptr;
template<class Base>
class enable_shared_from_this;
template<typename Store, typename Actual>
void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
);
template<typename T>
inline void
do_enable_shared_from_this(const shared_ptr<T>&, ...) {}
template<typename T>
class shared_ptr : public shared_ptr_base {
typedef ::std::shared_ptr<T> real_type;
real_type real;
template<typename A>
friend class shared_ptr;
template<typename A>
friend class weak_ptr;
// ctor for casts
shared_ptr(const real_type& r, const ptr_base::track_t& t)
:shared_ptr_base(t), real(r)
{track_new();}
public:
typedef typename real_type::element_type element_type;
typedef weak_ptr<T> weak_type;
// new NULL
shared_ptr() noexcept {}
// copy existing same type
shared_ptr(const shared_ptr& o) :shared_ptr_base(o.track), real(o.real) {track_new();}
// copy existing of implicitly castable type
template<typename A>
shared_ptr(const shared_ptr<A>& o) :shared_ptr_base(o.track), real(o.real) {track_new();}
// construct around new pointer
template<typename A, class ... Args>
explicit shared_ptr(A* a, Args ... args) : shared_ptr_base(), real(a, args...) {
track_new(a);
do_enable_shared_from_this(*this, a);
}
// make strong ref from weak
template<typename A>
shared_ptr(const weak_ptr<A>& o) :shared_ptr_base(o.track), real(o.real) {track_new();}
~shared_ptr() {}
shared_ptr& operator=(const shared_ptr& o) {
if(this!=&o) {
real = o.real;
track_assign(o);
}
return *this;
}
template<typename A>
shared_ptr& operator=(const shared_ptr<A>& o) {
if(get()!=o.get()) {
real = o.real;
track_assign(o);
}
return *this;
}
void reset() noexcept { real.reset(); track_clear(); }
template<typename A, class ... Args>
void reset(A* a, Args ... args)
{
real.reset(a, args...);
track_new(a);
do_enable_shared_from_this(*this, a);
}
void swap(shared_ptr &o) noexcept
{
if(this!=&o) {
real.swap(o.real);
shared_ptr_base::swap(o);
}
}
// proxy remaining to underlying shared_ptr
T* get() const noexcept { return real.get(); }
typename std::add_lvalue_reference<T>::type operator*() const noexcept { return *real; }
T* operator->() const noexcept { return real.get(); }
long use_count() const noexcept { return real.use_count(); }
bool unique() const noexcept { return real.unique(); }
explicit operator bool() const noexcept { return bool(real); }
bool operator==(const shared_ptr<T>& o) const { return real==o.real; }
bool operator!=(const shared_ptr<T>& o) const { return real!=o.real; }
bool operator<(const shared_ptr<T>& o) const { return real<o.real; }
template<typename A>
bool owner_before(const shared_ptr<A>& o) { return real.owner_before(o); }
template<typename A>
bool owner_before(const weak_ptr<A>& o) { return real.owner_before(o); }
template<typename TO, typename FROM>
friend
shared_ptr<TO> static_pointer_cast(const shared_ptr<FROM>& src);
template<typename TO, typename FROM>
friend
shared_ptr<TO> const_pointer_cast(const shared_ptr<FROM>& src);
template<typename TO, typename FROM>
friend
shared_ptr<TO> dynamic_pointer_cast(const shared_ptr<FROM>& src);
template<typename Store, typename Actual>
friend void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
);
};
template<typename TO, typename FROM>
shared_ptr<TO> static_pointer_cast(const shared_ptr<FROM>& src) {
return shared_ptr<TO>(std::static_pointer_cast<TO>(src.real), src.track);
}
template<typename TO, typename FROM>
shared_ptr<TO> const_pointer_cast(const shared_ptr<FROM>& src) {
return shared_ptr<TO>(std::const_pointer_cast<TO>(src.real), src.track);
}
template<typename TO, typename FROM>
shared_ptr<TO> dynamic_pointer_cast(const shared_ptr<FROM>& src) {
return shared_ptr<TO>(std::dynamic_pointer_cast<TO>(src.real), src.track);
}
template<typename T>
class weak_ptr : public weak_ptr_base {
typedef ::std::weak_ptr<T> real_type;
real_type real;
template<typename A>
friend class shared_ptr;
template<typename A>
friend class weak_ptr;
public:
typedef typename real_type::element_type element_type;
typedef weak_ptr<T> weak_type;
// new NULL
weak_ptr() noexcept {}
// copy existing same type
weak_ptr(const weak_ptr& o) :weak_ptr_base(o.track), real(o.real) {}
// copy existing of similar type
template<typename A>
weak_ptr(const weak_ptr<A>& o) :weak_ptr_base(o.track), real(o.real) {}
// create week ref from strong ref
template<typename A>
weak_ptr(const shared_ptr<A>& o) :weak_ptr_base(o.track), real(o.real) {}
~weak_ptr() {}
weak_ptr& operator=(const weak_ptr& o) {
if(this!=&o) {
real = o.real;
track = o.track;
}
return *this;
}
template<typename A>
weak_ptr& operator=(const shared_ptr<A>& o) {
real = o.real;
track = o.track;
return *this;
}
shared_ptr<T> lock() const noexcept { return shared_ptr<T>(real.lock(), track); }
void reset() noexcept { track.reset(); real.reset(); }
long use_count() const noexcept { return real.use_count(); }
bool unique() const noexcept { return real.unique(); }
};
template<class Base>
class enable_shared_from_this {
mutable weak_ptr<Base> xxInternalSelf;
template<typename Store, typename Actual>
friend
void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
);
public:
shared_ptr<Base> shared_from_this() const {
return shared_ptr<Base>(xxInternalSelf);
}
};
template<typename Store, typename Actual>
void
do_enable_shared_from_this(const shared_ptr<Store>& dest,
enable_shared_from_this<Actual>* self
)
{
shared_ptr<Actual> actual(dynamic_pointer_cast<Actual>(dest));
if(!actual)
throw std::logic_error("epics::debug::enabled_shared_from_this fails");
self->xxInternalSelf = actual;
}
}} // namespace epics::debug
template<typename T>
std::ostream& operator<<(std::ostream& strm, const epics::debug::shared_ptr<T>& ptr)
{
strm<<ptr.get();
return strm;
}
#endif // DEBUGPTR_H
+90 -7
View File
@@ -31,6 +31,32 @@
* # boost version of tr1/memory
*/
/* Debugging shared_ptr with debugPtr.h requires >= c++11
*
* Define DEBUG_SHARED_PTR globally to cause epics::debug::shared_ptr
* to be injected as std::tr1::shared_ptr and the macro
* HAVE_SHOW_REFS will be defined.
*
* epics::debug::shared_ptr wraps std::shared_ptr with additional
* tracking of backwards references.
* std::shared_ptr::use_count() gives the number of shared_ptr
* (strong refs) to the referenced object.
*
* If use_count()==5 then epics::debug::shared_ptr::show_refs() will print
* 5 lines of the format
*
* # <addr>: <IP0> <IP1> ...
*
* Given the numberic address of each shared_ptr as well as the call stack
* at the point where it was initialized.
* Use the 'addr2line' utility to interpret the stack addresses.
*
* On linux w/ ASLR it is necessary to turn on static linking to meaningfully
* interpret call stack addresses.
* Append "STATIC_BUILD=YES" to configure/CONFIG_SITE
*/
//#define DEBUG_SHARED_PTR
#if defined(SHARED_FROM_MANUAL)
// define SHARED_FROM_MANUAL if from some reason it is desirable to manually select
// which shared_ptr implementation to use
@@ -59,18 +85,39 @@
#include <memory>
#ifndef DEBUG_SHARED_PTR
namespace std {
namespace tr1 {
using std::shared_ptr;
using std::weak_ptr;
using std::static_pointer_cast;
using std::dynamic_pointer_cast;
using std::const_pointer_cast;
using std::enable_shared_from_this;
using std::bad_weak_ptr;
using ::std::shared_ptr;
using ::std::weak_ptr;
using ::std::static_pointer_cast;
using ::std::dynamic_pointer_cast;
using ::std::const_pointer_cast;
using ::std::enable_shared_from_this;
using ::std::bad_weak_ptr;
}
}
#else // DEBUG_SHARED_PTR
#include "debugPtr.h"
namespace std {
namespace tr1 {
using ::epics::debug::shared_ptr;
using ::epics::debug::weak_ptr;
using ::epics::debug::static_pointer_cast;
using ::epics::debug::dynamic_pointer_cast;
using ::epics::debug::const_pointer_cast;
using ::epics::debug::enable_shared_from_this;
using ::std::bad_weak_ptr;
}
}
#endif // DEBUG_SHARED_PTR
#elif defined(SHARED_FROM_TR1)
# include <tr1/memory>
@@ -101,6 +148,42 @@ namespace std {
# undef SHARED_FROM_BOOST
#endif
namespace detail {
template<typename T>
struct ref_shower {
const std::tr1::shared_ptr<T>& ptr;
bool self, weak;
ref_shower(const std::tr1::shared_ptr<T>& ptr, bool self, bool weak) :ptr(ptr),self(self),weak(weak) {}
};
}
/** Print a list (one per line) of shared_ptr which refer to the same object
*
* @param ptr Use the object pointed to by this shared_ptr
* @param self include or omit a line for this shared_ptr
* @param weak include a line for each weak_ptr (not implemented)
@code
shared_ptr<int> x;
std::cout << show_referrers(x);
@endcode
*/
template<typename T>
inline detail::ref_shower<T> show_referrers(const std::tr1::shared_ptr<T>& ptr, bool self=true, bool weak=false)
{
return detail::ref_shower<T>(ptr, self, weak);
}
namespace std{
template<typename T>
inline std::ostream& operator<<(std::ostream& strm, const ::detail::ref_shower<T>& refs)
{
#ifdef HAVE_SHOW_REFS
refs.ptr.show_refs(strm, refs.self, refs.weak);
#endif // HAVE_SHOW_REFS
return strm;
}
}//namespace std
#define POINTER_DEFINITIONS(clazz) \
typedef std::tr1::shared_ptr<clazz> shared_pointer; \
typedef std::tr1::shared_ptr<const clazz> const_shared_pointer; \