I'm using VS2012 with default optimization settings (/O2), and this problem exists only in release mode.
I have some code that uses a michael_deque
(with the standard GC) and a pointer to (abstract) type T
.
When I try to push back a pointer to a type that is derived from T
, the application crashes while exiting the push_back()
function of michael_deque
.
The problem seems to depend precisely on this specific type T
, because writing a dummy class foo
, deriving from it in class bar
(and printing something in the constructor to avoid it being optimized away) and then pushing back new bar()
to michael_deque does not cause a crash.
The classT
in question is this:
class Task
{
public:
Task() : started(false), unfinishedTasks(1), taskID(++taskIDCounter) {};
Task(unsigned int parentID_) : started(false), unfinishedTasks(1), taskID(++taskIDCounter), parentID(parentID_)/*, taken(0)*/ {};
virtual ~Task() = 0 {};
virtual void execute() final
{
this->doActualWork();
unfinishedTasks--;
}
virtual void doActualWork() = 0;
public:
unsigned int taskID; //ID of this task
unsigned int parentID; //ID of the parent of this task
bool started;
std::atomic<unsigned int> unfinishedTasks; //Number of child tasks that are still unfinished
std::vector<unsigned int> dependencies; //list of IDs of all tasks that this task depends on
};
The error can be reproduced in a minimal program (if you happen to have an environment that can execute this in the same manner as I do, just put an std::atomic<unsigned int> taskIDCounter
somewhere where the Task class can see it) :
#include <cds/container/michael_deque.h>
#include "task.hpp"
class a : public Task
{
a()
{
std::cout<<"dummy print"<<std::endl;
}
virtual ~a()
{
}
virtual void doActualWork()
{
std::cout<<"whatever"<<std::endl;
}
};
int main()
{
cds::Initialize();
{
cds::gc::HP hpGC;
cds::gc::HP::thread_gc myThreadGC;
cds::container::MichaelDeque<cds::gc::HP,Task*> tasks;
tasks.push_back(new a()); //will crash at the end of push_back
}
cds::Terminate();
}
What could be the cause of this? Do I do something undefined in the class Task which causes problems in the optimization problems?
It was indeed a compiler bug. More specifically, it was a bug associated with Visual Studio 2012's atomic implementation.
Some template specializations of the std::atomic class modify the stack frame pointer (ebp) without backing it up on and popping it form the stack before/after the modification. The libcds library uses one of these specializations, and the resulting incorrect frame pointer sometimes causes illegal memory access (the undefined behavior seems to prevent catastrophic failure in debug mode) when running outside the scope of a function.
The fix in this special case was to make libcds use a different atomics library then the standard one provided by Visual Studio. The library decides which implementation to use in cxx11_atomic.h:
#if defined(CDS_USE_BOOST_ATOMIC)
# error "Boost.atomic is not supported"
//# include <boost/version.hpp>
//# if BOOST_VERSION >= 105300
//# include <boost/atomic.hpp>
//# define CDS_ATOMIC boost
//# define CDS_CXX11_ATOMIC_BEGIN_NAMESPACE namespace boost {
//# define CDS_CXX11_ATOMIC_END_NAMESPACE }
//# else
//# error "Boost version 1.53 or above is needed for boost.atomic"
//# endif
#elif CDS_CXX11_ATOMIC_SUPPORT == 1
// Compiler supports C++11 atomic (conditionally defined in cds/details/defs.h)
# include <cds/compiler/cxx11_atomic_prepatches.h>
# include <atomic>
# define CDS_ATOMIC std
# define CDS_CXX11_ATOMIC_BEGIN_NAMESPACE namespace std {
# define CDS_CXX11_ATOMIC_END_NAMESPACE }
# include <cds/compiler/cxx11_atomic_patches.h>
#else
# include <cds/compiler/cxx11_atomic.h>
# define CDS_ATOMIC cds::cxx11_atomics
# define CDS_CXX11_ATOMIC_BEGIN_NAMESPACE namespace cds { namespace cxx11_atomics {
# define CDS_CXX11_ATOMIC_END_NAMESPACE }}
#endif
The second branch of the if statement can be changed to something like
#elif CDS_CXX11_ATOMIC_SUPPORT == 255
which will cause the library to use its own atomics implementation at all times.