Here is a simple example for using std::condition_variable. When using clang+tsan for building the following code,
#include <cassert>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
class WaitSignalLock {
private:
std::condition_variable conditionVariable;
std::mutex mtx;
bool condition = false;
public:
bool wait(const std::chrono::milliseconds& timeout);
void signal();
private:
bool conditionMet() const;
bool waitForConditionVariableSignal(std::unique_lock<std::mutex>& lck, const std::chrono::milliseconds& timeout);
};
bool WaitSignalLock::wait(const std::chrono::milliseconds& timeout) {
std::unique_lock<std::mutex> lck(this->mtx);
const auto result = waitForConditionVariableSignal(lck, timeout);
condition = false;
return result;
}
void WaitSignalLock::signal() {
// the variable used for checking the condition has to be modified under a lock (even if the variable is atomic),
// and the condition variable should not be notified under a lock
{
std::lock_guard<std::mutex> lck(this->mtx);
condition = true;
}
conditionVariable.notify_one();
}
bool WaitSignalLock::conditionMet() const {
// do not lock, method will be called under the lock already
return condition;
}
bool WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>& lck, const std::chrono::milliseconds& timeout) {
assert(lck.owns_lock());
if(this->conditionVariable.wait_for(lck, timeout, [this]() {
return this->conditionMet();
})) {
return true;
}
else {
return false;
}
}
int main(int, char**) {
WaitSignalLock waitSignalLock;
std::thread sendSignal([&waitSignalLock]() {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
waitSignalLock.signal();
});
const auto result = waitSignalLock.wait(std::chrono::seconds(1));
sendSignal.join();
return !result;
}
tsan reports the following two issues:
WARNING: ThreadSanitizer: double lock of a mutex (pid=16421)
#0 pthread_mutex_lock <null> (a.out+0x7e358)
#1 __gthread_mutex_lock(pthread_mutex_t*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/x86_64-pc-linux-gnu/bits/gthr-default.h:749:12 (a.out+0xd39a6)
#2 std::mutex::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:100:17 (a.out+0xd45e9)
#3 std::lock_guard<std::mutex>::lock_guard(std::mutex&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:159:19 (a.out+0xd4148)
#4 WaitSignalLock::signal() /home/jae/projects/condition_variable/main.cpp:38:37 (a.out+0xd367e)
#5 main::$_1::operator()() const /home/jae/projects/condition_variable/main.cpp:67:24 (a.out+0xd3f2f)
#6 void std::__invoke_impl<void, main::$_1>(std::__invoke_other, main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:60:14 (a.out+0xd3eb1)
#7 std::__invoke_result<main::$_1>::type std::__invoke<main::$_1>(main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:95:14 (a.out+0xd3e01)
#8 void std::thread::_Invoker<std::tuple<main::$_1> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:264:13 (a.out+0xd3db9)
#9 std::thread::_Invoker<std::tuple<main::$_1> >::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:271:11 (a.out+0xd3d69)
#10 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_1> > >::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:215:13 (a.out+0xd3c9d)
#11 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80:18 (libstdc++.so.6+0xcfb73)
Location is stack of main thread.
Location is global '??' at 0x7ffd19818000 ([stack]+0x00000001ec48)
Mutex M12 (0x7ffd19836c48) created at:
#0 pthread_mutex_lock <null> (a.out+0x7e358)
#1 __gthread_mutex_lock(pthread_mutex_t*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/x86_64-pc-linux-gnu/bits/gthr-default.h:749:12 (a.out+0xd39a6)
#2 std::mutex::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:100:17 (a.out+0xd45e9)
#3 std::unique_lock<std::mutex>::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:138:17 (a.out+0xd46df)
#4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:68:2 (a.out+0xd40b3)
#5 WaitSignalLock::wait(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:28:34 (a.out+0xd3563)
#6 main /home/jae/projects/condition_variable/main.cpp:70:40 (a.out+0xd3860)
SUMMARY: ThreadSanitizer: double lock of a mutex (/home/jae/projects/condition_variable/a.out+0x7e358) in pthread_mutex_lock
==================
==================
WARNING: ThreadSanitizer: data race (pid=16421)
Write of size 1 at 0x7ffd19836c70 by thread T1 (mutexes: write M12):
#0 WaitSignalLock::signal() /home/jae/projects/condition_variable/main.cpp:39:19 (a.out+0xd3687)
#1 main::$_1::operator()() const /home/jae/projects/condition_variable/main.cpp:67:24 (a.out+0xd3f2f)
#2 void std::__invoke_impl<void, main::$_1>(std::__invoke_other, main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:60:14 (a.out+0xd3eb1)
#3 std::__invoke_result<main::$_1>::type std::__invoke<main::$_1>(main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:95:14 (a.out+0xd3e01)
#4 void std::thread::_Invoker<std::tuple<main::$_1> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:264:13 (a.out+0xd3db9)
#5 std::thread::_Invoker<std::tuple<main::$_1> >::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:271:11 (a.out+0xd3d69)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_1> > >::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:215:13 (a.out+0xd3c9d)
#7 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80:18 (libstdc++.so.6+0xcfb73)
Previous read of size 1 at 0x7ffd19836c70 by main thread (mutexes: write M12):
#0 WaitSignalLock::conditionMet() const /home/jae/projects/condition_variable/main.cpp:46:12 (a.out+0xd36ea)
#1 WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0::operator()() const /home/jae/projects/condition_variable/main.cpp:52:18 (a.out+0xd3af1)
#2 bool std::condition_variable::wait_until<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> >, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0>(std::unique_lock<std::mutex>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > const&, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/condition_variable:157:10 (a.out+0xd3a37)
#3 bool std::condition_variable::wait_for<long, std::ratio<1l, 1000l>, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0>(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/condition_variable:185:9 (a.out+0xd378e)
#4 WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:51:32 (a.out+0xd3608)
#5 WaitSignalLock::wait(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:29:25 (a.out+0xd3572)
#6 main /home/jae/projects/condition_variable/main.cpp:70:40 (a.out+0xd3860)
As if synchronized via sleep:
#0 nanosleep <null> (a.out+0x6aadc)
#1 void std::this_thread::sleep_for<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:405:9 (a.out+0xd556a)
#2 main::$_1::operator()() const /home/jae/projects/condition_variable/main.cpp:66:9 (a.out+0xd3f1f)
#3 void std::__invoke_impl<void, main::$_1>(std::__invoke_other, main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:60:14 (a.out+0xd3eb1)
#4 std::__invoke_result<main::$_1>::type std::__invoke<main::$_1>(main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:95:14 (a.out+0xd3e01)
#5 void std::thread::_Invoker<std::tuple<main::$_1> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:264:13 (a.out+0xd3db9)
#6 std::thread::_Invoker<std::tuple<main::$_1> >::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:271:11 (a.out+0xd3d69)
#7 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_1> > >::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:215:13 (a.out+0xd3c9d)
#8 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80:18 (libstdc++.so.6+0xcfb73)
Location is stack of main thread.
Location is global '??' at 0x7ffd19818000 ([stack]+0x00000001ec70)
Mutex M12 (0x7ffd19836c48) created at:
#0 pthread_mutex_lock <null> (a.out+0x7e358)
#1 __gthread_mutex_lock(pthread_mutex_t*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/x86_64-pc-linux-gnu/bits/gthr-default.h:749:12 (a.out+0xd39a6)
#2 std::mutex::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:100:17 (a.out+0xd45e9)
#3 std::unique_lock<std::mutex>::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:138:17 (a.out+0xd46df)
#4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:68:2 (a.out+0xd40b3)
#5 WaitSignalLock::wait(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:28:34 (a.out+0xd3563)
#6 main /home/jae/projects/condition_variable/main.cpp:70:40 (a.out+0xd3860)
Thread T1 (tid=16423, running) created by main thread at:
#0 pthread_create <null> (a.out+0x8dbce)
#1 __gthread_create /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:663:35 (libstdc++.so.6+0xcfe49)
#2 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:135:37 (libstdc++.so.6+0xcfe49)
#3 main /home/jae/projects/condition_variable/main.cpp:65:17 (a.out+0xd3816)
SUMMARY: ThreadSanitizer: data race /home/jae/projects/condition_variable/main.cpp:39:19 in WaitSignalLock::signal()
==================
ThreadSanitizer: reported 2 warnings
I neither understand why the first nor the second warning is reported.
The first warning claims that the mutex is locked twice, but how is this even possible? The mutex is not locked by the same thread while holding it, which is, as far as I understand, what tsan is claiming.
The second warning indicates that the variable 'condition' is not read/written in a thread-safe manner. However, it is only read and written when the mutex is locked. Or is the lock not locked after condition_variable::wait_for returns? According to https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for, the lock should be locked.
Is it possible that this is a bug in clang/tsan? I couldn't find a bug report for this issue, and have a hard time believing that this is the case. But I simply don't see what's wrong with the code above.
I used the following command for compiling the source:
clang++ -O1 -g -fsanitize=thread -fno-omit-frame-pointer -lpthread main.cpp
The clang version is
$ clang --version
clang version 10.0.0
Related
I have a strange segmentation fault I am unable to properly understand. I am creating a function object with std::bind, then assign this to a std::function object, this apparently results in a segfault.
std::function<ara::com::Future<SetShapeOutput>(const
messages::test::SetShapeParams&)> SetShape_callback_;
void set_SetShape_callback(const std::function<ara::com::Future<SetShapeOutput>(
const messages::test::SetShapeParams&)>& callback) {
SetShape_callback_ = callback;
}
[somewhere else]
algo_service_.GetshapeServiceSkeleton()->set_SetShape_callback(
std::bind(&ShapeServerAraBinding::on_call_serviceshapeService_methodSetShape,
this, std::placeholders::_1));
// definition
ara::com::Future<adaptiveautosarapplication::shapeServiceSkeleton::SetShapeOutput>
on_call_serviceshapeService_methodSetShape(
const messages::test::SetShapeParams& araSetShapeParams);
And the stacktrace from gdb showing the assignment causes a segfault:
#0 0x000055c45c268839 in std::swap<std::_Any_data> (__a=..., __b=...) at /usr/include/c++/6/bits/move.h:191
#1 0x000055c45c267781 in std::function<ara::com::Future<serviceInterfaces::test::shapeService::SetShapeOutput> (messages::test::SetShapeParams const&)>::swap(s
td::function<ara::com::Future<serviceInterfaces::test::shapeService::SetShapeOutput> (messages::test::SetShapeParams const&)>&) (this=0x7fffea5d6be0, __x=...)
at /usr/include/c++/6/functional:2016
#2 0x000055c45c263934 in std::function<ara::com::Future<serviceInterfaces::test::shapeService::SetShapeOutput> (messages::test::SetShapeParams const&)>::operat
or=(std::function<ara::com::Future<serviceInterfaces::test::shapeService::SetShapeOutput> (messages::test::SetShapeParams const&)> const&) (this=0x58, __x=...)
at /usr/include/c++/6/functional:1931
#3 0x000055c45c26009f in shapeServer::adaptiveautosarapplication::shapeServiceSkeleton::set_SetShape_callback(std::function<ara::com::Future<serviceInterfaces:
:test::shapeService::SetShapeOutput> (messages::test::SetShapeParams const&)> const&) (this=0x0, callback=...)
at /app/tests/eclipseProject/projects/shapeRPC/build/autogen/algos/shape/server/ara/include/shapeServer_service.h:40
#4 0x000055c45c260508 in shapeServer::ShapeServerAraBinding::Initialize (this=0x7fffea5d6dd0)
at /app/tests/eclipseProject/projects/shapeRPC/build/autogen/algos/shape/server/ara/include/shapeServerAraBinding.h:69
#5 0x000055c45c25854c in main (argc=1, argv=0x7fffea5d6fd8)
at /app/tests/eclipseProject/projects/shapeRPC/build/autogen/algos/shape/server/ara/src/shapeServerAraMain.cpp:108
The problem cause is shown in the following line:
#3 0x000055c45c26009f in shapeServer::adaptiveautosarapplication::shapeServiceSkeleton::set_SetShape_callback(std::function<ara::com::Future<serviceInterfaces:
:test::shapeService::SetShapeOutput> (messages::test::SetShapeParams const&)> const&) (this=0x0, callback=...)
Note shapeServiceSkeleton this=0x0.
I'm making a NodeJS addon using the Nan library, and I'm running into an issue where calling a callback (created on the javascript side and passed to the addon to be executed asyncronously) will cause a segfault - but only about once every 10 thousand or so runs.
There's quite a bit of complexity involved in how everything operates, but I'm hoping that someone will see something I missed or be able to figure out what's going on.
The C++ callback function is created from the javascript callback like this:
auto nodeFunc = val.As<v8::Function>();
auto nodeCb = std::make_shared<Nan::Callback>(nodeFunc);
auto callback = [nodeCb] (std::string err, std::string val) -> void {
Nan::HandleScope scope;
v8::Local<v8::Value> argv[2];
if (err.length() == 0) {
auto isolate = v8::Isolate::GetCurrent();
auto json = v8::JSON::Parse(isolate, Nan::New(val).ToLocalChecked());
auto object = json.ToLocalChecked();
argv[0] = Nan::Null();
argv[1] = object;
} else {
argv[0] = Nan::Error(err.c_str());
argv[1] = Nan::Null();
}
try {
nodeCb->Call(2, argv);
} catch (std::exception& ex) {
std::cout << ex.what() << std::endl;
Nan::ThrowReferenceError(ex.what());
}
};
After creation, it is passed to a separate thread which eventually sends the callback to the main libuv thread using uv_async_send() and executes it. This works fine the vast majority of the time, but will very rarely segfault on the nodeCb->Call(2, argv) line.
If anyone has any insight into what's happening here, I'd really appreciate it.
Also, here's the call stack from gdb in case that's any help:
#0 0x00000000009870e0 in v8::Function::Call(v8::Local<v8::Value>, int, v8::Local<v8::Value>*) ()
#1 0x00000000010a562c in node::MakeCallback(node::Environment*, v8::Local<v8::Value>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*) ()
#2 0x00000000010a5a98 in node::MakeCallback(v8::Isolate*, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*) ()
#3 0x00007ffff47b4b30 in Nan::Callback::Call_ (this=0x20c3500, isolate=0x1ded750, target=..., argc=2,
argv=0x7fffffffa430) at ../node_modules/nan/nan.h:1477
#4 0x00007ffff47b4a93 in Nan::Callback::Call (this=0x20c3500, argc=2, argv=0x7fffffffa430)
at ../node_modules/nan/nan.h:1443
#5 0x00007ffff47b194b in detail::info::__lambda1::operator() (__closure=0x1e40710, err="",
val="{\"index\":1,\"status\":1}") at ../node/utils.hpp:125
#6 0x00007ffff47b37f2 in std::_Function_handler<void(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >), detail::info::setElementValue(T&, v8::Local<v8::Value>, size_t) [with T = std::function<void(std::basic_string<char>, std::basic_string<char>)>; size_t = long unsigned int]::__lambda1>::_M_invoke(const std::_Any_data &, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >) (__functor=..., __args#0="", __args#1="")
at /usr/include/c++/4.8.2/functional:2071
#7 0x00007ffff44cd339 in std::function<void (std::string, std::string)>::operator()(std::string, std::string) const (this=0x1e29c80, __args#0="", __args#1="")
at /opt/rh/devtoolset-4/root/usr/include/c++/5.2.1/functional:2271
#8 0x00007ffff44e172c in std::_Bind<std::function<void (std::string, std::string)> (char const*, std::string)>::__call<void, , 0ul, 1ul>(std::tuple<>&&, std::_Index_tuple<0ul, 1ul>) (this=0x1e29c80,
__args=<unknown type in /usr/local/lib/libSCPlay.so, CU 0x0, DIE 0x83e21>)
at /opt/rh/devtoolset-4/root/usr/include/c++/5.2.1/functional:1074
#9 0x00007ffff44daec8 in std::_Bind<std::function<void (std::string, std::string)> (char const*, std::string)>::operator()<, void>() (this=0x1e29c80)
at /opt/rh/devtoolset-4/root/usr/include/c++/5.2.1/functional:1133
#10 0x00007ffff44d3b58 in std::_Function_handler<void (), std::_Bind<std::function<void (std::string, std::string)> (char const*, std::string)> >::_M_invoke(std::_Any_data const&) (__functor=...)
at /opt/rh/devtoolset-4/root/usr/include/c++/5.2.1/functional:1871
#11 0x00007ffff44fab0a in std::function<void ()>::operator()() const (this=0x7fffffffa650)
at /opt/rh/devtoolset-4/root/usr/include/c++/5.2.1/functional:2271
#12 0x00007ffff44f890c in DeviceThread::asyncListener (handle=0x1efb9f0)
at /home/scl37510/Projects/SCPlay2/lib/device_thread.cpp:124
#13 0x0000000001316b0b in uv__async_event (loop=0x1de7fe0 <default_loop_struct>, w=<optimized out>,
nevents=<optimized out>) at ../deps/uv/src/unix/async.c:98
#14 0x0000000001316be3 in uv__async_io (loop=0x1de7fe0 <default_loop_struct>,
w=0x1de81a8 <default_loop_struct+456>, events=<optimized out>) at ../deps/uv/src/unix/async.c:138
#15 0x00000000013271b0 in uv__io_poll (loop=loop#entry=0x1de7fe0 <default_loop_struct>, timeout=0)
at ../deps/uv/src/unix/linux-core.c:380
#16 0x00000000013176c6 in uv_run (loop=0x1de7fe0 <default_loop_struct>, mode=UV_RUN_ONCE)
at ../deps/uv/src/unix/core.c:354
#17 0x00000000010aabe0 in node::Start(int, char**) ()
#18 0x00007ffff6bf5b35 in __libc_start_main () from /lib64/libc.so.6
#19 0x00000000007b1f1d in _start ()
Edit: I've created a much smaller test program to see if I could pinpoint the source of the bug, and I've discovered that I can prevent it by changing the Callback shared_ptr to a regular pointer, deleting it immediately after the nodeCb->Call(2,argv) line.
Is there some semantic difference between the two that could cause this?
The usage of shared_ptr to wrap a Callback is suspicious:
std::make_shared<Nan::Callback>(nodeFunc);
I don't think V8 would like references being handled that way.
Nan::Callback itself contains a Persistent that it uses to store the function's value, so you need not worry about persistence.
...I just noticed your edit after I already wrote out this answer. The shared_ptr is potentially dangerous to mix with V8 internal handles and references.
If your intention is to store the callback and delete it immediately, maybe you could benefit by refactoring your code to use Nan::AsyncWorker instead.
There is a class with one signal and slot:
#ifndef SOME_CLASS_H
#define SOME_CLASS_H
#include <QObject>
class SomeClass : public QObject
{
Q_OBJECT
public:
explicit SomeClass(QObject *parent = 0) : QObject(parent) {
connect(this, &SomeClass::valueChanged, this, &SomeClass::setValue);
}
void emitSignal() {
emit valueChanged();
}
signals:
void valueChanged();
public slots:
void setValue() {}
};
#endif // SOME_CLASS_H
Here is main file of program defining a singleton which creates instance of above defined class and puts it into separate thread:
#include <QCoreApplication>
#include <QThread>
#include <someclass.h>
class Singleton {
public:
QThread t;
SomeClass someClassObject;
Singleton() {
someClassObject.moveToThread(&t);
t.start();
}
static SomeClass& getInstance() {
static Singleton st;
return st.someClassObject;
}
~Singleton() {
t.quit();
t.wait();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
SomeClass& b = Singleton::getInstance();
b.emitSignal();
QThread::msleep(100);
return a.exec();
}
The problem is that when I evaluate this program using some thread checking tools like ThreadSanitizer or Helgrind then several warnings are reported about data race conditions. Example output from ThreadSanitizer:
==================
WARNING: ThreadSanitizer: data race (pid=3827)
Read of size 8 at 0x7d040000f6a0 by thread T1:
#0 void QtPrivate::FunctionPointer<void (SomeClass::*)()>::call<void, void>(void (SomeClass::*)(), SomeClass*, void**) /usr/include/qt5/QtCore/qobjectdefs_impl.h:142 (exe+0x0000000a2d1c)
#1 QtPrivate::QSlotObject<void (SomeClass::*)(), void, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) /usr/include/qt5/QtCore/qobject_impl.h:149 (exe+0x0000000a29b4)
#2 QObject::event(QEvent*) ??:0 (libQt5Core.so.5+0x00000029022d)
Previous write of size 8 at 0x7d040000f6a0 by main thread:
#0 malloc ??:0 (exe+0x0000000426e9)
#1 QMetaObject::activate(QObject*, int, int, void**) ??:0 (libQt5Core.so.5+0x00000028ef1c)
#2 main /tmp/project/main.cpp:31 (exe+0x0000000a19f2)
Location is heap block of size 8 at 0x7d040000f6a0 allocated by main thread:
#0 malloc ??:0 (exe+0x0000000426e9)
#1 QMetaObject::activate(QObject*, int, int, void**) ??:0 (libQt5Core.so.5+0x00000028ef1c)
#2 main /tmp/project/main.cpp:31 (exe+0x0000000a19f2)
Thread T1 'QThread' (tid=3829, running) created by main thread at:
#0 pthread_create ??:0 (exe+0x000000046f6b)
#1 QThread::start(QThread::Priority) ??:0 (libQt5Core.so.5+0x0000000928b7)
#2 Singleton::getInstance() /tmp/project/main.cpp:16 (exe+0x0000000a1b3c)
#3 main /tmp/project/main.cpp:30 (exe+0x0000000a19bf)
SUMMARY: ThreadSanitizer: data race /usr/include/qt5/QtCore/qobjectdefs_impl.h:142 void QtPrivate::FunctionPointer<void (SomeClass::*)()>::call<void, void>(void (SomeClass::*)(), SomeClass*, void**)
==================
==================
WARNING: ThreadSanitizer: data race (pid=3827)
Write of size 1 at 0x7d040000f6b0 by thread T1:
#0 free ??:0 (exe+0x000000042dfb)
#1 QMetaCallEvent::~QMetaCallEvent() ??:0 (libQt5Core.so.5+0x00000028d3a4)
Previous write of size 8 at 0x7d040000f6b0 by main thread:
#0 malloc ??:0 (exe+0x0000000426e9)
#1 QMetaObject::activate(QObject*, int, int, void**) ??:0 (libQt5Core.so.5+0x00000028eefe)
#2 main /tmp/project/main.cpp:31 (exe+0x0000000a19f2)
Location is heap block of size 4 at 0x7d040000f6b0 allocated by main thread:
#0 malloc ??:0 (exe+0x0000000426e9)
#1 QMetaObject::activate(QObject*, int, int, void**) ??:0 (libQt5Core.so.5+0x00000028eefe)
#2 main /tmp/project/main.cpp:31 (exe+0x0000000a19f2)
Thread T1 'QThread' (tid=3829, running) created by main thread at:
#0 pthread_create ??:0 (exe+0x000000046f6b)
#1 QThread::start(QThread::Priority) ??:0 (libQt5Core.so.5+0x0000000928b7)
#2 Singleton::getInstance() /tmp/project/main.cpp:16 (exe+0x0000000a1b3c)
#3 main /tmp/project/main.cpp:30 (exe+0x0000000a19bf)
SUMMARY: ThreadSanitizer: data race ??:0 free
==================
==================
WARNING: ThreadSanitizer: data race (pid=3827)
Write of size 8 at 0x7d180000edc0 by thread T1:
#0 operator delete(void*) ??:0 (exe+0x00000004392b)
#1 QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) ??:0 (libQt5Core.so.5+0x000000269e0f)
Previous write of size 8 at 0x7d180000edc0 by main thread:
#0 operator new(unsigned long) ??:0 (exe+0x0000000432b9)
#1 QMetaObject::activate(QObject*, int, int, void**) ??:0 (libQt5Core.so.5+0x00000028ef94)
#2 main /tmp/project/main.cpp:31 (exe+0x0000000a19f2)
Location is heap block of size 88 at 0x7d180000edc0 allocated by main thread:
#0 operator new(unsigned long) ??:0 (exe+0x0000000432b9)
#1 QMetaObject::activate(QObject*, int, int, void**) ??:0 (libQt5Core.so.5+0x00000028ef94)
#2 main /tmp/project/main.cpp:31 (exe+0x0000000a19f2)
Thread T1 'QThread' (tid=3829, running) created by main thread at:
#0 pthread_create ??:0 (exe+0x000000046f6b)
#1 QThread::start(QThread::Priority) ??:0 (libQt5Core.so.5+0x0000000928b7)
#2 Singleton::getInstance() /tmp/project/main.cpp:16 (exe+0x0000000a1b3c)
#3 main /tmp/project/main.cpp:30 (exe+0x0000000a19bf)
SUMMARY: ThreadSanitizer: data race ??:0 operator delete(void*)
==================
What does cause these race conditions?
Since the warnings are all originating in Qt internals related to signals, slots, and event loops, I think that the race conditions detected are false positives. Does Helgrind detect the same race conditions? Helgrind bugs with Qt are reported by some, (http://www.kdab.com/~dfaure/helgrind.html) and seem to be addressed recently.
Clang has the -fsanitize-blacklist compile switch to suppress warnings from the ThreadSanitizer. Unfortunately, I cannot get it to work.
Here is an example that I want to suppress:
WARNING: ThreadSanitizer: data race (pid=21502)
Read of size 8 at 0x7f0dcf5b31a8 by thread T6:
#0 tbb::interface6::internal::auto_partition_type_base<tbb::interface6::internal::auto_partition_type>::check_being_stolen(tbb::task&) /usr/include/tbb/partitioner.h:305 (exe+0x000000388b38)
#1 <null> <null>:0 (libtbb.so.2+0x0000000224d9)
Previous write of size 8 at 0x7f0dcf5b31a8 by thread T1:
#0 auto_partition_type_base /usr/include/tbb/partitioner.h:299 (exe+0x000000388d9a)
#1 <null> <null>:0 (libtbb.so.2+0x0000000224d9)
#2 GhostSearch::Ghost3Search::SearchTask::execute_impl() /home/phil/ghost/search/ghost3/ghost3_search_alg.cpp:1456 (exe+0x000000387a8a)
#3 <null> <null>:0 (libtbb.so.2+0x0000000224d9)
#4 GhostSearch::Ghost3Search::Ghost3SearchAlg::NullWindowSearch(int, MOVE, int, std::vector<MOVE, std::allocator<MOVE> >&) /home/phil/ghost/search/ghost3/ghost3_search_alg.cpp:1640 (exe+0x000000388310)
#5 GhostSearch::PureMTDSearchAlg::FullWindowSearch(GhostSearch::SearchWindow, GhostSearch::SearchWindow, MOVE, int, std::vector<MOVE, std::allocator<MOVE> >&) /home/phil/ghost/search/pure_mtd_search_alg.cpp:41 (exe+0x000000370e3f)
#6 GhostSearch::PureSearchAlgWrapper::RequestHandlerThread::EnterHandlerMainLoop() /home/phil/ghost/search/pure_search_alg_wrapper.cpp:124 (exe+0x000000372d1b)
#7 operator() /home/phil/ghost/search/pure_search_alg_wrapper.cpp:94 (exe+0x000000374683)
#8 execute_native_thread_routine /home/phil/tmp/gcc/src/gcc-4.8-20130725/libstdc++-v3/src/c++11/thread.cc:84 (libstdc++.so.6+0x0000000b26cf)
Thread T6 (tid=21518, running) created by thread T3 at:
#0 pthread_create ??:0 (exe+0x0000002378e1)
#1 <null> <null>:0 (libtbb.so.2+0x0000000198c0)
Thread T1 (tid=21513, running) created by main thread at:
#0 pthread_create ??:0 (exe+0x0000002378e1)
#1 __gthread_create /home/phil/tmp/gcc/src/gcc-build/x86_64-unknown-linux-gnu/libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:662 (libstdc++.so.6+0x0000000b291e)
#2 GhostSearch::PureSearchAlgWrapper::StartRequestHandlerThread() /home/phil/ghost/search/pure_search_alg_wrapper.cpp:77 (exe+0x0000003715c3)
#3 GhostSearch::Search::ExecuteSearch(GhostSearch::SEARCH_SETTINGS const&) /home/phil/ghost/search.cpp:243 (exe+0x00000033063f)
#4 GhostSearch::Search::StartSearch(GhostSearch::SEARCH_SETTINGS const&, UserBoard const&, GhostInterfaces::UserInterface*) /home/phil/ghost/search.cpp:176 (exe+0x00000033037a)
#5 GhostInterfaces::UserInterface::StartSearch(GhostSearch::SEARCH_SETTINGS const&, UserBoard const&) /home/phil/ghost/interface.cpp:1072 (exe+0x0000002ea220)
#6 GhostInterfaces::UserInterface::MainLoop() /home/phil/ghost/interface.cpp:576 (exe+0x0000002e9464)
#7 GhostInterfaces::Command_Analyze::Execute(GhostInterfaces::UserInterfaceData&) /home/phil/ghost/commands.cpp:1005 (exe+0x00000028756c)
#8 GhostInterfaces::UserInterface::FinishNextCommand() /home/phil/ghost/interface.cpp:1161 (exe+0x0000002e9ed0)
#9 GhostInterfaces::UserInterface::MainLoop() /home/phil/ghost/interface.cpp:571 (exe+0x0000002e9447)
#10 main /home/phil/ghost/ghost.cpp:54 (exe+0x000000274efd)
SUMMARY: ThreadSanitizer: data race /usr/include/tbb/partitioner.h:305 tbb::interface6::internal::auto_partition_type_base<tbb::interface6::internal::auto_partition_type>::check_being_stolen(tbb::task&)
My tries for the suppression file so far (but it does not work):
# TBB
fun:tbb::*
src:/usr/include/tbb/partitioner.h
Do you know why it does not work?
(By the way, I would be happy to suppress all warnings from the TBB library.)
Finally, I got it working.
According to the documentation, each line must start with a valid "suppression_type" (race, thread, mutex, signal, deadlock, or called_from_lib).
In my example, the correct suppression_type is race.
Here is an example file called "sanitizer-thread-suppressions.txt", which suppresses two functions, which are known to contain data races:
race:Function1
race:MyNamespace::Function2
To test the suppress file, set the TSAN_OPTIONS environment variable and call the application (compiled with -fsanitize=thread):
$ TSAN_OPTIONS="suppressions=sanitizer-thread-suppressions.txt" ./myapp
If that works, you can apply the settings at compile time:
-fsanitize=thread -fsanitize-blacklist=sanitizer-thread-suppressions.txt
Using iOS 5.X
Cpp 11
Xcode
I am trying to identify what is causing this error.
It may be caused from destructor being called prior to a thread completing.
// default_delete
template <class _Tp>
struct _LIBCPP_VISIBLE default_delete
{
_LIBCPP_INLINE_VISIBILITY default_delete() _NOEXCEPT {}
template <class _Up>
_LIBCPP_INLINE_VISIBILITY default_delete(const default_delete<_Up>&,
typename enable_if<is_convertible<_Up*, _Tp*>::value>::type* = 0) _NOEXCEPT {}
_LIBCPP_INLINE_VISIBILITY void operator() (_Tp* __ptr) const _NOEXCEPT
{
static_assert(sizeof(_Tp) > 0, "default_delete can not delete incomplete type");
delete __ptr;
}
};
The stack trace is as follows:
stack trace
2013-01-28 13:50:33.704 CPP11AudioRecoClient[4633:5107] A task has completed. Map now contains 0 entries.
terminate called without an active exception(gdb) bt
#0 0x316e932c in __pthread_kill
#1 0x33de620e in pthread_kill
#2 0x33ddf29e in abort
#3 0x36c6cf6a in abort_message
#4 0x36c6a35a in default_terminate
#5 0x35237324 in _objc_terminate
#6 0x31997c96 in std::terminate
#7 0x319d3d18 in std::__1::thread::~thread ()
#8 0x00076cce in std::__1::default_delete<std::__1::thread>::operator() () at /Volumes/Xcode/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/c++/v1/memory:2512
#9 0x319d3d18 in std::__1::thread::~thread ()
#10 0x319d3d18 in std::__1::thread::~thread ()
#11 0x319c94cc in std::__1::__shared_weak_count::__release_shared ()
#12 0x00077606 in std::__1::shared_ptr<std::__1::thread>::~shared_ptr (this=0x2d64c4) at memory:4441
#13 0x0007676c in std::__1::shared_ptr<std::__1::thread>::~shared_ptr (this=0x2d64c4) at memory:4439
#14 0x00076038 in Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread::~Cpp11Thread (this=0x2d64c0) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/Cpp11ThreadFactory.cpp:125
#15 0x00075ffc in Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread::~Cpp11Thread (this=0x2d64c0) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/Cpp11ThreadFactory.cpp:123
#16 0x00075fdc in Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread::~Cpp11Thread (this=0x2d64c0) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/Cpp11ThreadFactory.cpp:123
#17 0x00077f68 in std::__1::default_delete<Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread>::operator() () at /Volumes/Xcode/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/c++/v1/memory:2512
#18 0x00077f68 in std::__1::__shared_ptr_pointer<Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread*, std::__1::default_delete<Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread>, std::__1::allocator<Microsoft::BingMobile::MusicReco::ClientSDK::Cpp11Thread> >::__on_zero_shared (this=0x2d5280) at memory:3668
#19 0x319c94cc in std::__1::__shared_weak_count::__release_shared ()
#20 0x0006a86e in std::__1::shared_ptr<Microsoft::BingMobile::MusicReco::ClientSDK::IThread>::~shared_ptr (this=0x2d6478) at memory:4441
#21 0x00069d94 in std::__1::shared_ptr<Microsoft::BingMobile::MusicReco::ClientSDK::IThread>::~shared_ptr (this=0x2d6478) at memory:4439
#22 0x00068d56 in Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask::~AsyncTask (this=0x2d6460) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/AsyncTask.cpp:34
#23 0x00068ce8 in Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask::~AsyncTask (this=0x2d6460) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/AsyncTask.cpp:33
#24 0x00068cc8 in Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask::~AsyncTask (this=0x2d6460) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/AsyncTask.cpp:33
#25 0x0006ad08 in std::__1::default_delete<Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask>::operator() () at /Volumes/Xcode/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/c++/v1/memory:2512
#26 0x0006ad08 in std::__1::__shared_ptr_pointer<Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask*, std::__1::default_delete<Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask>, std::__1::allocator<Microsoft::BingMobile::MusicReco::ClientSDK::AsyncTask> >::__on_zero_shared (this=0x2d6090) at memory:3668
#27 0x319c94cc in std::__1::__shared_weak_count::__release_shared ()
#28 0x00069fca in std::__1::shared_ptr<Microsoft::BingMobile::MusicReco::ClientSDK::ITaskObserver>::~shared_ptr (this=0x44fb4fc) at memory:4441
#29 0x00069e6c in std::__1::shared_ptr<Microsoft::BingMobile::MusicReco::ClientSDK::ITaskObserver>::~shared_ptr (this=0x44fb4fc) at memory:4439
#30 0x00099c22 in Microsoft::BingMobile::MusicReco::ClientSDK::RunnableTaskBase::notifyObserver (this=0x2d6350, observerWeak=<value temporarily unavailable, due to optimizations>, sharedReferenceToSelf=#0x44fb638) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/RunnableTaskBase.cpp:100
#31 0x00099930 in Microsoft::BingMobile::MusicReco::ClientSDK::RunnableTaskBase::notifyObservers (this=0x2d6350, sharedReferenceToSelf=<value temporarily unavailable, due to optimizations>) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/RunnableTaskBase.cpp:79
#32 0x000996e0 in Microsoft::BingMobile::MusicReco::ClientSDK::RunnableTaskBase::setStatus (this=0x2d6350, targetStatus=Microsoft::BingMobile::MusicReco::ClientSDK::Completed, sharedReferenceToSelf=#0x44fbd20) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/RunnableTaskBase.cpp:107
#33 0x00087168 in Microsoft::BingMobile::MusicReco::ClientSDK::PingWebServiceTask::runTaskChecked (this=0x2d6350, sharedReferenceToSelf=<value temporarily unavailable, due to optimizations>) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/PingWebServiceTask.cpp:120
#34 0x00099356 in Microsoft::BingMobile::MusicReco::ClientSDK::RunnableTaskBase::runTask (this=0x2d6350) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/RunnableTaskBase.cpp:60
#35 0x000991d2 in Microsoft::BingMobile::MusicReco::ClientSDK::RunnableTaskBase::run (this=0x2d6350, sharedReferenceToSelf=<value temporarily unavailable, due to optimizations>) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/RunnableTaskBase.cpp:48
#36 0x00099250 in non-virtual thunk to Microsoft::BingMobile::MusicReco::ClientSDK::RunnableTaskBase::run(std::__1::shared_ptr<Microsoft::BingMobile::MusicReco::ClientSDK::IRunnable>) () at memory:4023
#37 0x000759ca in Microsoft::BingMobile::MusicReco::ClientSDK::RunHookedRunnable::run (this=0x2d64a0, sharedReferenceToSelf=#0x44fcea8) at /Users/v-mattkn/Desktop/CPP11AudioRecoClient/Cpp11ThreadFactory.cpp:75
#38 0x0007736e in _ZNKSt3__110unique_ptrINS_5tupleIJMN9Microsoft10BingMobile9MusicReco9ClientSDK9IRunnableEFvNS_10shared_ptrIS6_EEES8_S8_EEENS_14default_deleteISB_EEEdeEv [inlined] () at /Volumes/Xcode/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/c++/v1/memory:309
#39 0x0007736e in _ZNSt3__114__thread_proxyINS_5tupleIJMN9Microsoft10BingMobile9MusicReco9ClientSDK9IRunnableEFvNS_10shared_ptrIS6_EEES8_S8_EEEEEPvSC_ (__vp=0x2d6230) at __functional_base:340
40 0x33da7734 in _pthread_start ()
That thread::~thread() in the backtrace suggests that the thread is still running when it's object gets destroyed. That's an error; you have to call join() or detach() on the thread object before destroying it.