Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,30 @@ target_include_directories(
"$<INSTALL_INTERFACE:include>")

set_target_properties(opentelemetry_api PROPERTIES EXPORT_NAME api)
set(EXPORTED_OTELCPP_TARGETS opentelemetry_api)

if(WIN32)
# * abi_symbol_bridge
add_library(opentelemetry_api_symbol_bridge SHARED
src/symbol_bridge_implementation_windows.cc)
target_link_libraries(opentelemetry_api_symbol_bridge
PRIVATE opentelemetry_api)
set_target_properties(
opentelemetry_api_symbol_bridge
PROPERTIES EXPORT_NAME api_symbol_bridge RUNTIME_OUTPUT_NAME
opentelemetry_api_symbol_bridge)

target_compile_options(
opentelemetry_api_symbol_bridge
PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/wd4190>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wno-return-type-c-linkage>)
target_link_options(
opentelemetry_api_symbol_bridge INTERFACE
$<$<CXX_COMPILER_ID:MSVC>:/INCLUDE:OpenTelemetryAbiBridgeHook>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wl,-u,OpenTelemetryAbiBridgeHook>)

list(APPEND EXPORTED_OTELCPP_TARGETS opentelemetry_api_symbol_bridge)
endif()

unset(TARGET_DEPS)

Expand Down Expand Up @@ -115,7 +139,7 @@ otel_add_component(
COMPONENT
api
TARGETS
opentelemetry_api
${EXPORTED_OTELCPP_TARGETS}
FILES_DIRECTORY
"include/opentelemetry"
FILES_DESTINATION
Expand Down
10 changes: 10 additions & 0 deletions api/include/opentelemetry/baggage/baggage.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/version.h"

#ifdef _WIN32
# include "opentelemetry/common/detail/symbol_bridge_windows.h"
#endif

OPENTELEMETRY_BEGIN_NAMESPACE

namespace baggage
Expand All @@ -36,6 +40,12 @@ class OPENTELEMETRY_EXPORT Baggage

OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<Baggage> GetDefault()
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<Baggage> (*)()>(
"OpenTelemetryBaggageBaggageGetDefault"))
return address();
#endif
static nostd::shared_ptr<Baggage> baggage{new Baggage()};
return baggage;
}
Expand Down
99 changes: 99 additions & 0 deletions api/include/opentelemetry/common/detail/symbol_bridge_windows.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "opentelemetry/version.h"

#ifndef OPENTELEMETRY_SYMBOL_BRIDGE_NOINLINE
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <Windows.h>
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace common
{
namespace detail
{

struct LoadedBridgeSymbol
{
void *address;
bool try_again;
};

LoadedBridgeSymbol LoadSymbolBridgeSymbol(const char *symbol_name) noexcept;

template <typename Signature, typename Disambiguate = void>
Signature LoadSymbolBridgeSymbol(const char *symbol_name) noexcept
{
static LoadedBridgeSymbol symbol{nullptr, true};
if (!symbol.address && symbol.try_again)
{
symbol = LoadSymbolBridgeSymbol(symbol_name);
}
return reinterpret_cast<Signature>(symbol.address);
}

#ifndef OPENTELEMETRY_SYMBOL_BRIDGE_NOINLINE
inline
#endif

#if !defined(OPENTELEMETRY_SYMBOL_BRIDGE_NOINLINE) || defined(OPENTELEMETRY_SYMBOL_BRIDGE_IMPL)
LoadedBridgeSymbol
LoadSymbolBridgeSymbol(const char *symbol_name) noexcept
{
struct ModuleLifetime
{
::HMODULE module{nullptr};
bool load_failed{false};

~ModuleLifetime()
{
if (module)
{
::FreeLibrary(module);
}
}
};

static ::ATOM bridge_present_atom{0};
if (bridge_present_atom == 0)
{
bridge_present_atom = ::FindAtomW(L"otelcpp-abi-bridge");
if (bridge_present_atom == 0)
{
return {nullptr, true};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondersing, Should this return {nullptr, false} here?

Two concerns about the lazy-loading path:

  • If the user doesn't load opentelemetry_api_symbol_bridge, FindAtomW runs on every singleton access. I don't know how costly that is, but it's a per-call overhead we may want to avoid.
  • If we support LoadLibrary, the cached address in static LoadedBridgeSymbol symbol{nullptr, true}; becomes invalid after FreeLibrary, which forces us to track the DLL lifetime and reset all the singletons — that adds real complexity.

Given that, I'd lean toward requiring the bridge to be linked rather than dynamically loaded. Then its availability is fixed when this function is called (always present or always absent), and we can drop the reload logic for otelcpp-abi-bridge. WDYT?

@Desp4 Desp4 Oct 10, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I measured the cost of FindAtomW call to be around 30ns on my machine, plus this should lend itself well for branch prediction in a repeated noop case when the atom is not found. I personally don't think that the added cost is critical enough, and I also don't know of a cheaper process-wide method to check for a loaded abi bridge(though this is of course irrelevant if we drop the flexibility of the bridge being loaded after the first singleton access).
  • How so? The FreeLibrary call is tied to the lifetime of ModuleLifetime bridge_module, that is we're only freeing the library at the global destruction phase when the last binary using the bridge is getting unloaded(since FreeLibrary only decrements the refcount).

My thoughts on dropping the dynamic check for the bridge availability. I personally don't find the dynamic check to be a critical feature and wouldn't oppose dropping it, though the benefit I see is only cutting the cost of repeated atom check for a noop case.

I originally wanted to support the dynamic bridge load specifically for two cases:

  1. If some API consumer makes singleton calls at library load time(global inits). In this case the API consumer uses the singleton before the main executable has initialized them, thus going to the "bridge not present" path and caching it, forever being in the noop case.
    I find this case niche enough to not be something the SDK ought to support(given that using the API at global init is very much guaranteed to only give you the noop singletons even on linux, this usage pattern is not really productive to begin with).
  2. The case of the SDK being initialized in a deferred runtime-loaded library, after some API consuming calls have already been made.
    This too I don't find to be that critical since most plugin-loading software loads them near startup before any API consuming code is supposedly ran.

So overall I would not oppose dropping the atom check and just resorting to a hard requirement of a bridge being there on first singleton access, given that the cases I could come up with above are not that common or worth supporting.

}
}

static ModuleLifetime bridge_module{};
if (!bridge_module.module)
{
if (bridge_module.load_failed)
{
return {nullptr, false};
}

::GetModuleHandleExW(0x0, L"opentelemetry_api_symbol_bridge", &bridge_module.module);
bridge_module.load_failed = !bridge_module.module;

if (bridge_module.load_failed)
{
return {nullptr, false};
}
}

const auto address = ::GetProcAddress(bridge_module.module, symbol_name);
return {reinterpret_cast<void *>(address), false};
}
#endif

} // namespace detail
} // namespace common
OPENTELEMETRY_END_NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

#include "opentelemetry/version.h"

#ifdef _WIN32
# include "opentelemetry/common/detail/symbol_bridge_windows.h"
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace context
{
Expand Down Expand Up @@ -60,12 +64,24 @@ class OPENTELEMETRY_EXPORT GlobalTextMapPropagator
private:
OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<TextMapPropagator> &GetPropagator() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<TextMapPropagator> &(*)()>(
"OpenTelemetryContextPropagationGlobalTextMapPropagatorGetPropagator"))
return address();
#endif
static nostd::shared_ptr<TextMapPropagator> propagator(new NoOpPropagator());
return propagator;
}

OPENTELEMETRY_API_SINGLETON static common::SpinLockMutex &GetLock() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<common::SpinLockMutex &(*)(), TextMapPropagator>(
"OpenTelemetryContextPropagationGlobalTextMapPropagatorGetLock"))
return address();
#endif
static common::SpinLockMutex lock;
return lock;
}
Expand Down
23 changes: 23 additions & 0 deletions api/include/opentelemetry/context/runtime_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,18 @@
#include "opentelemetry/nostd/unique_ptr.h"
#include "opentelemetry/version.h"

#ifdef _WIN32
# include "opentelemetry/common/detail/symbol_bridge_windows.h"
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace context
{
namespace detail
{
struct AbiBridgePrivacyIntruder;
} // namespace detail

// The Token object provides is returned when attaching objects to the
// RuntimeContext object and is associated with a context object, and
// can be provided to the RuntimeContext Detach method to remove the
Expand Down Expand Up @@ -192,6 +201,12 @@ class OPENTELEMETRY_EXPORT RuntimeContext

OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<RuntimeContextStorage> &GetStorage() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<RuntimeContextStorage> &(*)()>(
"OpenTelemetryContextRuntimeContextGetStorage"))
return address();
#endif
static nostd::shared_ptr<RuntimeContextStorage> context(GetDefaultStorage());
return context;
}
Expand Down Expand Up @@ -250,10 +265,13 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
}

private:
friend struct detail::AbiBridgePrivacyIntruder;

// A nested class to store the attached contexts in a stack.
class Stack
{
friend class ThreadLocalContextStorage;
friend struct detail::AbiBridgePrivacyIntruder;

/**
* Limit the max stack depth.
Expand Down Expand Up @@ -384,6 +402,11 @@ class ThreadLocalContextStorage : public RuntimeContextStorage

OPENTELEMETRY_API_SINGLETON Stack &GetStack()
{
#ifdef _WIN32
if (const auto address = common::detail::LoadSymbolBridgeSymbol<Stack &(*)()>(
"OpenTelemetryContextThreadLocalContextStorageStackGetStack"))
return address();
#endif
static thread_local Stack stack_{};
return stack_;
}
Expand Down
22 changes: 22 additions & 0 deletions api/include/opentelemetry/logs/provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/version.h"

#ifdef _WIN32
# include "opentelemetry/common/detail/symbol_bridge_windows.h"
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace logs
{
Expand Down Expand Up @@ -97,6 +101,12 @@ class OPENTELEMETRY_EXPORT Provider
private:
OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<LoggerProvider> &GetProvider() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<LoggerProvider> &(*)()>(
"OpenTelemetryLogsProviderGetProvider"))
return address();
#endif
static nostd::shared_ptr<LoggerProvider> provider(new NoopLoggerProvider);
return provider;
}
Expand All @@ -105,13 +115,25 @@ class OPENTELEMETRY_EXPORT Provider
OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<EventLoggerProvider> &
GetEventProvider() noexcept
{
# ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<EventLoggerProvider> &(*)()>(
"OpenTelemetryLogsProviderGetEventProvider"))
return address();
# endif
static nostd::shared_ptr<EventLoggerProvider> provider(new NoopEventLoggerProvider);
return provider;
}
#endif

OPENTELEMETRY_API_SINGLETON static common::SpinLockMutex &GetLock() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<common::SpinLockMutex &(*)(), LoggerProvider>(
"OpenTelemetryLogsProviderGetLock"))
return address();
#endif
static common::SpinLockMutex lock;
return lock;
}
Expand Down
16 changes: 16 additions & 0 deletions api/include/opentelemetry/metrics/provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/version.h"

#ifdef _WIN32
# include "opentelemetry/common/detail/symbol_bridge_windows.h"
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace metrics
{
Expand Down Expand Up @@ -55,12 +59,24 @@ class Provider
private:
OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<MeterProvider> &GetProvider() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<MeterProvider> &(*)()>(
"OpenTelemetryMetricsProviderGetProvider"))
return address();
#endif
static nostd::shared_ptr<MeterProvider> provider(new NoopMeterProvider);
return provider;
}

OPENTELEMETRY_API_SINGLETON static common::SpinLockMutex &GetLock() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<common::SpinLockMutex &(*)(), MeterProvider>(
"OpenTelemetryMetricsProviderGetLock"))
return address();
#endif
static common::SpinLockMutex lock;
return lock;
}
Expand Down
16 changes: 16 additions & 0 deletions api/include/opentelemetry/trace/provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "opentelemetry/trace/tracer_provider.h"
#include "opentelemetry/version.h"

#ifdef _WIN32
# include "opentelemetry/common/detail/symbol_bridge_windows.h"
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace trace
{
Expand Down Expand Up @@ -55,12 +59,24 @@ class OPENTELEMETRY_EXPORT Provider
private:
OPENTELEMETRY_API_SINGLETON static nostd::shared_ptr<TracerProvider> &GetProvider() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<nostd::shared_ptr<TracerProvider> &(*)()>(
"OpenTelemetryTraceProviderGetProvider"))
return address();
#endif
static nostd::shared_ptr<TracerProvider> provider(new NoopTracerProvider);
return provider;
}

OPENTELEMETRY_API_SINGLETON static common::SpinLockMutex &GetLock() noexcept
{
#ifdef _WIN32
if (const auto address =
common::detail::LoadSymbolBridgeSymbol<common::SpinLockMutex &(*)(), TracerProvider>(
"OpenTelemetryTraceProviderGetLock"))
return address();
#endif
static common::SpinLockMutex lock;
return lock;
}
Expand Down
Loading
Loading