blob: 47b16b69287ce8c9c550da859fa504ba603f85c8 [file] [log] [blame]
diff --git a/api/include/opentelemetry/common/threadlocal.h b/api/include/opentelemetry/common/threadlocal.h
new file mode 100644
index 0000000..23a39e1
--- /dev/null
+++ b/api/include/opentelemetry/common/threadlocal.h
@@ -0,0 +1,123 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "opentelemetry/version.h"
+
+//
+// GCC can be told that a certain branch is not likely to be taken (for
+// instance, a CHECK failure), and use that information in static analysis.
+// Giving it this information can help it optimize for the common case in
+// the absence of better information (ie. -fprofile-arcs).
+//
+#define LIKELY(expr) __builtin_expect(!!(expr), 1)
+#define UNLIKELY(expr) __builtin_expect(!!(expr), 0)
+
+// Block-scoped static thread local implementation.
+//
+// Usage is similar to a C++11 thread_local. The BLOCK_STATIC_THREAD_LOCAL_TELEMTRY macro
+// defines a thread-local pointer to the specified type, which is lazily
+// instantiated by any thread entering the block for the first time. The
+// constructor for the type T is invoked at macro execution time, as expected,
+// and its destructor is invoked when the corresponding thread's Runnable
+// returns, or when the thread exits.
+//
+// Inspired by Poco <http://pocoproject.org/docs/Poco.ThreadLocal.html>,
+// Andrew Tomazos <http://stackoverflow.com/questions/12049684/>, and
+// the C++11 thread_local API.
+//
+// Example usage:
+//
+// // Invokes a 3-arg constructor on SomeClass:
+// BLOCK_STATIC_THREAD_LOCAL_TELEMTRY(SomeClass, instance, arg1, arg2, arg3);
+// instance->DoSomething();
+//
+#define BLOCK_STATIC_THREAD_LOCAL_TELEMTRY(T, t, ...) \
+ static __thread T *t; \
+ do \
+ { \
+ if (UNLIKELY(t == nullptr)) \
+ { \
+ t = new T(__VA_ARGS__); \
+ internal_threadlocal::AddDestructor(internal_threadlocal::Destroy<T>, t); \
+ } \
+ } while (false)
+
+// Class-scoped static thread local implementation.
+//
+// Very similar in implementation to the above block-scoped version, but
+// requires a bit more syntax and vigilance to use properly.
+//
+// DECLARE_STATIC_THREAD_LOCAL_TELEMETRY(Type, instance_var_) must be placed in the
+// class header, as usual for variable declarations.
+//
+// Because these variables are static, they must also be defined in the impl
+// file with DEFINE_STATIC_THREAD_LOCAL_TELEMETRY(Type, Classname, instance_var_),
+// which is very much like defining any static member, i.e. int Foo::member_.
+//
+// Finally, each thread must initialize the instance before using it by calling
+// INIT_STATIC_THREAD_LOCAL_TELEMETRY(Type, instance_var_, ...). This is a cheap
+// call, and may be invoked at the top of any method which may reference a
+// thread-local variable.
+//
+// Due to all of these requirements, you should probably declare TLS members
+// as private.
+//
+// Example usage:
+//
+// // foo.h
+// #include "kudu/utils/file.h"
+// class Foo {
+// public:
+// void DoSomething(std::string s);
+// private:
+// DECLARE_STATIC_THREAD_LOCAL_TELEMETRY(utils::File, file_);
+// };
+//
+// // foo.cc
+// #include "kudu/foo.h"
+// DEFINE_STATIC_THREAD_LOCAL_TELEMETRY(utils::File, Foo, file_);
+// void Foo::WriteToFile(std::string s) {
+// // Call constructor if necessary.
+// INIT_STATIC_THREAD_LOCAL_TELEMETRY(utils::File, file_, "/tmp/file_location.txt");
+// file_->Write(s);
+// }
+
+// Goes in the class declaration (usually in a header file).
+// dtor must be destructed _after_ t, so it gets defined first.
+// Uses a mangled variable name for dtor since it must also be a member of the
+// class.
+#define DECLARE_STATIC_THREAD_LOCAL_TELEMETRY(T, t) static __thread T *t
+
+// You must also define the instance in the .cc file.
+#define DEFINE_STATIC_THREAD_LOCAL_TELEMETRY(T, Class, t) __thread T *Class::t
+
+// Must be invoked at least once by each thread that will access t.
+#define INIT_STATIC_THREAD_LOCAL_TELEMETRY(T, t, ...) \
+ do \
+ { \
+ if (UNLIKELY(t == nullptr)) \
+ { \
+ t = new T(__VA_ARGS__); \
+ internal_threadlocal::AddDestructor(internal_threadlocal::Destroy<T>, t); \
+ } \
+ } while (false)
+
+// Internal implementation below.
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace internal_threadlocal
+{
+
+// Add a destructor to the list.
+void AddDestructor(void (*destructor)(void *), void *arg);
+
+// Destroy the passed object of type T.
+template <class T>
+static void Destroy(void *t)
+{
+ // With tcmalloc, this should be pretty cheap (same thread as new).
+ delete reinterpret_cast<T *>(t);
+}
+} // namespace internal_threadlocal
+OPENTELEMETRY_END_NAMESPACE
\ No newline at end of file
diff --git a/api/include/opentelemetry/context/runtime_context.h b/api/include/opentelemetry/context/runtime_context.h
index 167a928..608b6da 100644
--- a/api/include/opentelemetry/context/runtime_context.h
+++ b/api/include/opentelemetry/context/runtime_context.h
@@ -4,6 +4,7 @@
#pragma once
#include "opentelemetry/context/context.h"
+#include "opentelemetry/common/threadlocal.h"
OPENTELEMETRY_BEGIN_NAMESPACE
namespace context
@@ -188,7 +189,7 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
ThreadLocalContextStorage() noexcept = default;
// Return the current context.
- Context GetCurrent() noexcept override { return GetStack().Top(); }
+ Context GetCurrent() noexcept override { return GetStack()->Top(); }
// Resets the context to the value previous to the passed in token. This will
// also detach all child contexts of the passed in token.
@@ -196,23 +197,23 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
bool Detach(Token &token) noexcept override
{
// In most cases, the context to be detached is on the top of the stack.
- if (token == GetStack().Top())
+ if (token == GetStack()->Top())
{
- GetStack().Pop();
+ GetStack()->Pop();
return true;
}
- if (!GetStack().Contains(token))
+ if (!GetStack()->Contains(token))
{
return false;
}
- while (!(token == GetStack().Top()))
+ while (!(token == GetStack()->Top()))
{
- GetStack().Pop();
+ GetStack()->Pop();
}
- GetStack().Pop();
+ GetStack()->Pop();
return true;
}
@@ -221,14 +222,14 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
// that can be used to reset to the previous Context.
nostd::unique_ptr<Token> Attach(const Context &context) noexcept override
{
- GetStack().Push(context);
+ GetStack()->Push(context);
return CreateToken(context);
}
-private:
// A nested class to store the attached contexts in a stack.
class Stack
{
+ public:
friend class ThreadLocalContextStorage;
Stack() noexcept : size_(0), capacity_(0), base_(nullptr){};
@@ -315,9 +316,10 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
Context *base_;
};
- Stack &GetStack()
+ Stack *GetStack()
{
- static thread_local Stack stack_ = Stack();
+ // static thread_local Stack stack_ = Stack();
+ BLOCK_STATIC_THREAD_LOCAL_TELEMTRY(ThreadLocalContextStorage::Stack, stack_);
return stack_;
}
};
diff --git a/sdk/CMakeLists.txt b/sdk/CMakeLists.txt
index 1a824fe..91d4b5c 100644
--- a/sdk/CMakeLists.txt
+++ b/sdk/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_library(opentelemetry_sdk INTERFACE)
+add_library(opentelemetry_sdk INTERFACE ../api/include/opentelemetry/common/threadlocal.h)
target_include_directories(
opentelemetry_sdk
INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
diff --git a/sdk/src/common/CMakeLists.txt b/sdk/src/common/CMakeLists.txt
index debffef..b4fc875 100644
--- a/sdk/src/common/CMakeLists.txt
+++ b/sdk/src/common/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(COMMON_SRCS random.cc core.cc)
+set(COMMON_SRCS random.cc core.cc threadlocal.cc)
if(WIN32)
list(APPEND COMMON_SRCS platform/fork_windows.cc)
else()
diff --git a/sdk/src/common/random.cc b/sdk/src/common/random.cc
index 77b88cf..dc71f9c 100644
--- a/sdk/src/common/random.cc
+++ b/sdk/src/common/random.cc
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
#include "src/common/random.h"
+#include "opentelemetry/common/threadlocal.h"
#include "src/common/platform/fork.h"
#include <cstring>
@@ -29,33 +30,37 @@ class TlsRandomNumberGenerator
platform::AtFork(nullptr, nullptr, OnFork);
}
- static FastRandomNumberGenerator &engine() noexcept { return engine_; }
+ static FastRandomNumberGenerator *engine() noexcept { return engine_; }
private:
- static thread_local FastRandomNumberGenerator engine_;
+ // static thread_local FastRandomNumberGenerator engine_;
+ DECLARE_STATIC_THREAD_LOCAL_TELEMETRY(FastRandomNumberGenerator, engine_);
static void OnFork() noexcept { Seed(); }
static void Seed() noexcept
{
+ INIT_STATIC_THREAD_LOCAL_TELEMETRY(FastRandomNumberGenerator, engine_);
std::random_device random_device;
std::seed_seq seed_seq{random_device(), random_device(), random_device(), random_device()};
- engine_.seed(seed_seq);
+ engine_->seed(seed_seq);
}
};
-thread_local FastRandomNumberGenerator TlsRandomNumberGenerator::engine_{};
+// thread_local FastRandomNumberGenerator TlsRandomNumberGenerator::engine_{};
+DEFINE_STATIC_THREAD_LOCAL_TELEMETRY(FastRandomNumberGenerator, TlsRandomNumberGenerator, engine_);
} // namespace
-FastRandomNumberGenerator &Random::GetRandomNumberGenerator() noexcept
+FastRandomNumberGenerator *Random::GetRandomNumberGenerator() noexcept
{
- static thread_local TlsRandomNumberGenerator random_number_generator{};
+ // static thread_local TlsRandomNumberGenerator random_number_generator{};
+ BLOCK_STATIC_THREAD_LOCAL_TELEMTRY(TlsRandomNumberGenerator, random_number_generator);
return TlsRandomNumberGenerator::engine();
}
uint64_t Random::GenerateRandom64() noexcept
{
- return GetRandomNumberGenerator()();
+ return GetRandomNumberGenerator()->operator()();
}
void Random::GenerateRandomBuffer(opentelemetry::nostd::span<uint8_t> buffer) noexcept
diff --git a/sdk/src/common/random.h b/sdk/src/common/random.h
index ecd6dab..1aaa220 100644
--- a/sdk/src/common/random.h
+++ b/sdk/src/common/random.h
@@ -34,7 +34,7 @@ class Random
/**
* @return a seeded thread-local random number generator.
*/
- static FastRandomNumberGenerator &GetRandomNumberGenerator() noexcept;
+ static FastRandomNumberGenerator *GetRandomNumberGenerator() noexcept;
};
} // namespace common
} // namespace sdk
diff --git a/sdk/src/common/threadlocal.cc b/sdk/src/common/threadlocal.cc
new file mode 100644
index 0000000..ec2038b
--- /dev/null
+++ b/sdk/src/common/threadlocal.cc
@@ -0,0 +1,83 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#include <opentelemetry/common/threadlocal.h>
+#include "opentelemetry/sdk/common/global_log_handler.h"
+
+#include <pthread.h>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <sstream>
+
+OPENTELEMETRY_BEGIN_NAMESPACE
+namespace internal_threadlocal
+{
+
+// One key used by the entire process to attach destructors on thread exit.
+static pthread_key_t destructors_key;
+
+static std::once_flag once_init;
+
+namespace
+{
+// List of destructors for all thread locals instantiated on a given thread.
+struct PerThreadDestructorList
+{
+ void (*destructor)(void *);
+ void *arg;
+ PerThreadDestructorList *next;
+};
+
+} // anonymous namespace
+
+// Call all the destructors associated with all THREAD_LOCAL instances in this thread.
+static void InvokeDestructors(void *t)
+{
+ auto *d = reinterpret_cast<PerThreadDestructorList *>(t);
+ while (d != nullptr)
+ {
+ d->destructor(d->arg);
+ PerThreadDestructorList *next = d->next;
+ delete d;
+ d = next;
+ }
+}
+
+// This key must be initialized only once.
+static void CreateKey()
+{
+ int ret = pthread_key_create(&destructors_key, &InvokeDestructors);
+ // Linux supports up to 1024 keys, we will use only one for all thread locals.
+ if (ret != 0)
+ {
+ std::stringstream ss;
+ ss << "[thread local] pthread_key_create() failed, cannot add destructor to thread: "
+ << "error " << ret;
+ OTEL_INTERNAL_LOG_ERROR(ss.str());
+ }
+}
+
+// Adds a destructor to the list.
+void AddDestructor(void (*destructor)(void *), void *arg)
+{
+ std::call_once(once_init, &CreateKey);
+
+ // Returns NULL if nothing is set yet.
+ std::unique_ptr<PerThreadDestructorList> p(new PerThreadDestructorList());
+ p->destructor = destructor;
+ p->arg = arg;
+ p->next = reinterpret_cast<PerThreadDestructorList *>(pthread_getspecific(destructors_key));
+ int ret = pthread_setspecific(destructors_key, p.release());
+ // The only time this check should fail is if we are out of memory, or if
+ // somehow key creation failed, which should be caught by the above CHECK.
+ if (ret != 0)
+ {
+ std::stringstream ss;
+ ss << "[thread local] pthread_setspecific() failed, cannot update destructor list: "
+ << "error " << ret;
+ OTEL_INTERNAL_LOG_ERROR(ss.str());
+ }
+}
+} // namespace internal_threadlocal
+OPENTELEMETRY_END_NAMESPACE
\ No newline at end of file