diff --git a/api/include/opentelemetry/common/threadlocal.h b/api/include/opentelemetry/common/threadlocal.h new file mode 100644 index 00000000..623a6454 --- /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 , +// Andrew Tomazos , 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); \ + } \ + } 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); \ + } \ + } 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 +static void Destroy(void *t) +{ + // With tcmalloc, this should be pretty cheap (same thread as new). + delete reinterpret_cast(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 2cd5b0ff..9e3c5c47 100644 --- a/api/include/opentelemetry/context/runtime_context.h +++ b/api/include/opentelemetry/context/runtime_context.h @@ -5,6 +5,7 @@ #include "opentelemetry/common/macros.h" #include "opentelemetry/context/context.h" +#include "opentelemetry/common/threadlocal.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" @@ -193,7 +194,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. @@ -201,23 +202,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; } @@ -226,14 +227,14 @@ class ThreadLocalContextStorage : public RuntimeContextStorage // that can be used to reset to the previous Context. nostd::unique_ptr 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) {} @@ -320,9 +321,10 @@ class ThreadLocalContextStorage : public RuntimeContextStorage Context *base_; }; - OPENTELEMETRY_API_SINGLETON Stack &GetStack() + OPENTELEMETRY_API_SINGLETON 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 2e8f3bb6..224e6463 100644 --- a/sdk/CMakeLists.txt +++ b/sdk/CMakeLists.txt @@ -1,7 +1,7 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -add_library(opentelemetry_sdk INTERFACE) +add_library(opentelemetry_sdk INTERFACE ../api/include/opentelemetry/common/threadlocal.h) target_include_directories( opentelemetry_sdk INTERFACE "$" diff --git a/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h b/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h index 1608a3a0..6af2bd06 100644 --- a/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h +++ b/sdk/include/opentelemetry/sdk/metrics/data/circular_buffer.h @@ -7,6 +7,7 @@ #include #include +#include OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk diff --git a/sdk/src/common/CMakeLists.txt b/sdk/src/common/CMakeLists.txt index 7bb645f5..041d8ede 100644 --- a/sdk/src/common/CMakeLists.txt +++ b/sdk/src/common/CMakeLists.txt @@ -1,7 +1,7 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -set(COMMON_SRCS random.cc core.cc global_log_handler.cc env_variables.cc) +set(COMMON_SRCS random.cc core.cc global_log_handler.cc env_variables.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 77b88cfa..dc71f9c1 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 @@ -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 buffer) noexcept diff --git a/sdk/src/common/random.h b/sdk/src/common/random.h index ecd6dabc..1aaa2204 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 00000000..ec2038b4 --- /dev/null +++ b/sdk/src/common/threadlocal.cc @@ -0,0 +1,83 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include "opentelemetry/sdk/common/global_log_handler.h" + +#include +#include +#include +#include +#include + +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(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 p(new PerThreadDestructorList()); + p->destructor = destructor; + p->arg = arg; + p->next = reinterpret_cast(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