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:
+2
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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; \
|
||||
|
||||
Reference in New Issue
Block a user