| 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 |