I have code that is similar to the following (this code does not compile, is just for illustration purposes):
class A {
std::mutex m_;
std::vector<B*> bv_;
struct B {
B() {
std::lock_guard _(m);
bv_.push_back(this);
}
template<typename Lambda>
void push(Lambda&& lambda) {
// saves lambda in a queue
}
void work() {
// executes the lambdas from the queue
}
};
static thread_local B local_;
public:
void push() {
local_.push([] () {
// lambda that does things
}
}
void poll() {
std::lock_guard _(m);
for (auto * b : bv_) {
b->work();
}
}
};
We have a static_thread local member local_ of type B, which internally has a queue of lambdas, which are pushed when A::push is called. When B is created it adds itself to a queue in A. A::poll goes through this queue, and calls B::work, which runs the lambdas that were previously pushed. We can call A::push and A::poll from different threads.
What I am seeing is that this code deadlocks when calling A::poll from a different thread than the thread that called A::push originally. The reason is that local_, for the thread that is calling A::poll, is being initialized when the lambda that was push into the queue is executed. For context, A::push was never called from the thread that is calling A::poll. What the lambda does is not relevant in this case, as the lambda does nothing with local_.
I found something in the cpp specs that might explain what is happening:
"A variable with thread storage duration shall be initialized before its first odr-use (6.2) and, if constructed, shall be destroyed on thread exit."
My question is: why is local_ being initialized when running the lambda? Is local_ being initialized when executing the lambda because that is the first odr-use of local_ (even though local_ is really not being used there, I guess the definition of "use" in odr-use might not be what one would intuitively think)?
Adding this as the first line in A::poll:
(void)local_;
fixes the issue.
Thanks.
EDIT: trying to make the question clearer: in this code:
void push() {
local_.push([] () {
// lambda that does things, none of them have anything to do with local_
}
}
the compiler is adding a call to __tls_init
for local_
inside the lambda, and the question is why is the compiler doing that if local_ is not used (or even visible) inside the lambda.