Skip to content

Commit 7e3c35a

Browse files
authored
[SDK] Release provider locks before construction-failure diagnostics (#4702)
1 parent adebdf0 commit 7e3c35a

13 files changed

Lines changed: 285 additions & 69 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ Increment the:
1515

1616
## [Unreleased]
1717

18+
* [SDK] Release provider mutexes before construction-failure diagnostics so
19+
custom log handlers can retrieve cached or noop tracers, loggers, and meters
20+
from the same provider without deadlocking.
21+
[#4693](https://github.com/open-telemetry/opentelemetry-cpp/issues/4693)
22+
1823
* [SDK] Preserve the default unlimited timeout when batch span and log record
1924
processors shut down their exporters.
2025
[#4686](https://github.com/open-telemetry/opentelemetry-cpp/pull/4686)

‎sdk/src/logs/logger_provider.cc‎

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -116,34 +116,35 @@ opentelemetry::nostd::shared_ptr<opentelemetry::logs::Logger> LoggerProvider::Ge
116116
name = logger_name;
117117
}
118118

119-
// Ensure only one thread can read/write from the map of loggers
120-
std::lock_guard<std::mutex> lock_guard{lock_};
121-
122-
// If a logger with a name "logger_name" already exists, return it
123-
for (auto &logger : loggers_)
119+
#if OPENTELEMETRY_HAVE_EXCEPTIONS
120+
try
124121
{
125-
auto &logger_lib = logger->GetInstrumentationScope();
126-
if (logger->GetName() == logger_name &&
127-
logger_lib.equal(name, version, schema_url, &attributes))
122+
#endif
123+
// Release the lock before construction-failure diagnostics can re-enter the provider.
124+
// Ensure only one thread can read/write from the map of loggers
125+
std::lock_guard<std::mutex> lock_guard{lock_};
126+
127+
// If a logger with a name "logger_name" already exists, return it
128+
for (auto &logger : loggers_)
128129
{
129-
return opentelemetry::nostd::shared_ptr<opentelemetry::logs::Logger>{logger};
130+
auto &logger_lib = logger->GetInstrumentationScope();
131+
if (logger->GetName() == logger_name &&
132+
logger_lib.equal(name, version, schema_url, &attributes))
133+
{
134+
return opentelemetry::nostd::shared_ptr<opentelemetry::logs::Logger>{logger};
135+
}
130136
}
131-
}
132137

133-
// GetLogger is noexcept and does not surface an error, so callers cannot
134-
// recover after a construction failure. Retrying would re-throw, catch, and
135-
// log on later GetLogger calls. After the first failure, treat the provider
136-
// as non-functional for new loggers.
137-
if (!initialized_)
138-
{
139-
return noop_logger_;
140-
}
138+
// GetLogger is noexcept and does not surface an error, so callers cannot
139+
// recover after a construction failure. Retrying would re-throw, catch, and
140+
// log on later GetLogger calls. After the first failure, treat the provider
141+
// as non-functional for new loggers.
142+
if (!initialized_)
143+
{
144+
return noop_logger_;
145+
}
141146

142-
initialized_ = false;
143-
#if OPENTELEMETRY_HAVE_EXCEPTIONS
144-
try
145-
{
146-
#endif
147+
initialized_ = false;
147148
std::unique_ptr<instrumentationscope::InstrumentationScope> lib =
148149
instrumentationscope::InstrumentationScope::Create(name, version, schema_url, attributes);
149150

‎sdk/src/metrics/meter_provider.cc‎

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -122,31 +122,32 @@ nostd::shared_ptr<metrics_api::Meter> MeterProvider::GetMeter(
122122
name = "";
123123
}
124124

125-
const std::lock_guard<std::mutex> guard(lock_);
126-
127-
for (auto &meter : context_->GetMeters())
125+
#if OPENTELEMETRY_HAVE_EXCEPTIONS
126+
try
128127
{
129-
auto meter_lib = meter->GetInstrumentationScope();
130-
if (meter_lib->equal(name, version, schema_url, attributes))
128+
#endif
129+
// Release the lock before construction-failure diagnostics can re-enter the provider.
130+
const std::lock_guard<std::mutex> guard(lock_);
131+
132+
for (auto &meter : context_->GetMeters())
131133
{
132-
return nostd::shared_ptr<metrics_api::Meter>{meter};
134+
auto meter_lib = meter->GetInstrumentationScope();
135+
if (meter_lib->equal(name, version, schema_url, attributes))
136+
{
137+
return nostd::shared_ptr<metrics_api::Meter>{meter};
138+
}
133139
}
134-
}
135140

136-
// GetMeter is noexcept and does not surface an error, so callers cannot
137-
// recover after a construction failure. Retrying would re-throw, catch, and
138-
// log on later GetMeter calls. After the first failure, treat the provider
139-
// as non-functional for new meters.
140-
if (!initialized_)
141-
{
142-
return noop_meter_;
143-
}
141+
// GetMeter is noexcept and does not surface an error, so callers cannot
142+
// recover after a construction failure. Retrying would re-throw, catch, and
143+
// log on later GetMeter calls. After the first failure, treat the provider
144+
// as non-functional for new meters.
145+
if (!initialized_)
146+
{
147+
return noop_meter_;
148+
}
144149

145-
initialized_ = false;
146-
#if OPENTELEMETRY_HAVE_EXCEPTIONS
147-
try
148-
{
149-
#endif
150+
initialized_ = false;
150151
instrumentationscope::InstrumentationScopeAttributes attrs_map(attributes);
151152
auto scope =
152153
instrumentationscope::InstrumentationScope::Create(name, version, schema_url, attrs_map);

‎sdk/src/trace/tracer_provider.cc‎

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,31 +146,32 @@ nostd::shared_ptr<trace_api::Tracer> TracerProvider::GetTracer(
146146
OTEL_INTERNAL_LOG_ERROR("[TracerProvider::GetTracer] Library name is empty.");
147147
}
148148

149-
const std::lock_guard<std::mutex> guard(lock_);
150-
151-
for (auto &tracer : tracers_)
149+
#if OPENTELEMETRY_HAVE_EXCEPTIONS
150+
try
152151
{
153-
auto &tracer_scope = tracer->GetInstrumentationScope();
154-
if (tracer_scope.equal(name, version, schema_url, attributes))
152+
#endif
153+
// Release the lock before construction-failure diagnostics can re-enter the provider.
154+
const std::lock_guard<std::mutex> guard(lock_);
155+
156+
for (auto &tracer : tracers_)
155157
{
156-
return nostd::shared_ptr<trace_api::Tracer>{tracer};
158+
auto &tracer_scope = tracer->GetInstrumentationScope();
159+
if (tracer_scope.equal(name, version, schema_url, attributes))
160+
{
161+
return nostd::shared_ptr<trace_api::Tracer>{tracer};
162+
}
157163
}
158-
}
159164

160-
// GetTracer is noexcept and does not surface an error, so callers cannot
161-
// recover after a construction failure. Retrying would re-throw, catch, and
162-
// log on hot paths such as GetTracer(...)->StartSpan(...). After the first
163-
// failure, treat the provider as non-functional for new tracers.
164-
if (!initialized_)
165-
{
166-
return noop_tracer_;
167-
}
165+
// GetTracer is noexcept and does not surface an error, so callers cannot
166+
// recover after a construction failure. Retrying would re-throw, catch, and
167+
// log on hot paths such as GetTracer(...)->StartSpan(...). After the first
168+
// failure, treat the provider as non-functional for new tracers.
169+
if (!initialized_)
170+
{
171+
return noop_tracer_;
172+
}
168173

169-
initialized_ = false;
170-
#if OPENTELEMETRY_HAVE_EXCEPTIONS
171-
try
172-
{
173-
#endif
174+
initialized_ = false;
174175
instrumentationscope::InstrumentationScopeAttributes attrs_map(attributes);
175176
auto scope =
176177
instrumentationscope::InstrumentationScope::Create(name, version, schema_url, attrs_map);

‎sdk/test/logs/BUILD‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ cc_test(
1515
deps = [
1616
"//api",
1717
"//sdk/src/logs",
18+
"//test_common:headers",
1819
"@com_google_googletest//:gtest_main",
1920
],
2021
)

‎sdk/test/logs/CMakeLists.txt‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ foreach(
1717
target_link_libraries(
1818
${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
1919
opentelemetry_logs opentelemetry_trace)
20+
if(testname STREQUAL "logger_provider_sdk_test")
21+
target_link_libraries(${testname} opentelemetry_test_common)
22+
endif()
2023
gtest_add_tests(
2124
TARGET ${testname}
2225
TEST_PREFIX logs.
2326
TEST_LIST ${testname})
2427
endforeach()
28+
29+
set_tests_properties(
30+
logs.LoggerProviderSDK.ConstructionFailureDiagnosticsAllowProviderReentry
31+
PROPERTIES TIMEOUT 10)

‎sdk/test/logs/logger_provider_sdk_test.cc‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <cstdint>
99
#include <functional>
1010
#include <future>
11+
#include <memory> // IWYU pragma: keep
1112
#include <mutex>
1213
#include <stdexcept>
1314
#include <string>
@@ -29,6 +30,7 @@
2930
#include "opentelemetry/nostd/utility.h"
3031
#include "opentelemetry/nostd/variant.h"
3132
#include "opentelemetry/sdk/common/exporter_utils.h"
33+
#include "opentelemetry/sdk/common/global_log_handler.h"
3234
#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
3335
#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h"
3436
#include "opentelemetry/sdk/logs/event_logger_provider.h" // IWYU pragma: keep
@@ -44,6 +46,7 @@
4446
#include "opentelemetry/sdk/logs/recordable.h"
4547
#include "opentelemetry/sdk/logs/simple_log_record_processor.h"
4648
#include "opentelemetry/sdk/resource/resource.h"
49+
#include "opentelemetry/test_common/sdk/common/scoped_test_log_handler.h"
4750

4851
using namespace opentelemetry::sdk::logs;
4952
namespace logs_api = opentelemetry::logs;
@@ -679,6 +682,61 @@ TEST(LoggerProviderSDK, ConstructorsAreNotNoexcept)
679682
}
680683

681684
#if OPENTELEMETRY_HAVE_EXCEPTIONS
685+
TEST(LoggerProviderSDK, ConstructionFailureDiagnosticsAllowProviderReentry)
686+
{
687+
using opentelemetry::sdk::common::internal_log::LogLevel;
688+
using opentelemetry::test_common::ScopedTestLogHandler;
689+
for (int exception_type = 0; exception_type < 2; ++exception_type)
690+
{
691+
SCOPED_TRACE(exception_type);
692+
int construct_attempts = 0;
693+
auto configurator = std::make_unique<scope_sdk::ScopeConfigurator<LoggerConfig>>(
694+
scope_sdk::ScopeConfigurator<LoggerConfig>::Builder(LoggerConfig::Default())
695+
.AddCondition(
696+
[&](const scope_sdk::InstrumentationScope &scope) {
697+
++construct_attempts;
698+
if (scope.GetName() == "throwing-scope")
699+
{
700+
if (exception_type == 0)
701+
{
702+
throw std::runtime_error("injected construction failure");
703+
}
704+
throw 1;
705+
}
706+
return false;
707+
},
708+
LoggerConfig::Default())
709+
.Build());
710+
LoggerProvider provider(std::vector<std::unique_ptr<LogRecordProcessor>>{},
711+
opentelemetry::sdk::resource::Resource::Create({}),
712+
std::move(configurator));
713+
auto cached = provider.GetLogger("cached-logger", "cached-scope");
714+
nostd::shared_ptr<logs_api::Logger> reentrant_noop;
715+
int diagnostics = 0;
716+
ScopedTestLogHandler handler(LogLevel::Error, [&](const ScopedTestLogHandler::Entry &entry) {
717+
++diagnostics;
718+
EXPECT_EQ(entry.level, LogLevel::Error);
719+
EXPECT_EQ(provider.GetLogger("cached-logger", "cached-scope"), cached);
720+
reentrant_noop = provider.GetLogger("uncached-logger", "uncached-scope");
721+
});
722+
723+
auto failed = provider.GetLogger("throwing-logger", "throwing-scope");
724+
ASSERT_NE(failed, nullptr);
725+
EXPECT_EQ(diagnostics, 1);
726+
EXPECT_EQ(reentrant_noop, failed);
727+
EXPECT_EQ(failed->GetName(), "noop logger");
728+
EXPECT_EQ(provider.GetLogger("cached-logger", "cached-scope"), cached);
729+
EXPECT_EQ(provider.GetLogger("another-logger", "another-scope"), failed);
730+
EXPECT_EQ(construct_attempts, 2);
731+
EXPECT_EQ(diagnostics, 1);
732+
auto entries = handler.Drain();
733+
ASSERT_EQ(entries.size(), 1u);
734+
EXPECT_NE(entries[0].msg.find(exception_type == 0 ? "injected construction failure"
735+
: "unknown exception"),
736+
std::string::npos);
737+
}
738+
}
739+
682740
TEST(LoggerProviderSDK, GetLoggerReturnsNoopOnConstructionFailure)
683741
{
684742
auto should_throw = std::make_shared<bool>(true);

‎sdk/test/metrics/CMakeLists.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ foreach(
5252
TEST_LIST ${testname})
5353
endforeach()
5454

55+
set_tests_properties(
56+
metrics.MeterProvider.ConstructionFailureDiagnosticsAllowProviderReentry
57+
PROPERTIES TIMEOUT 10)
58+
5559
if(OPENTELEMETRY_ABI_VERSION_NO GREATER_EQUAL 2)
5660
add_executable(multi_observer_test multi_observer_test.cc)
5761
target_link_libraries(

‎sdk/test/metrics/meter_provider_sdk_test.cc‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#include <gtest/gtest.h>
55
#include <functional>
6-
#include <memory>
6+
#include <memory> // IWYU pragma: keep
77
#include <stdexcept>
88
#include <string>
99
#include <utility>
@@ -17,10 +17,12 @@
1717
#include "opentelemetry/common/macros.h"
1818
#include "opentelemetry/metrics/meter.h"
1919
#include "opentelemetry/metrics/sync_instruments.h"
20+
#include "opentelemetry/nostd/shared_ptr.h"
2021
#include "opentelemetry/nostd/string_view.h"
2122
#include "opentelemetry/sdk/common/global_log_handler.h"
2223
#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
2324
#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h"
25+
#include "opentelemetry/sdk/metrics/export/metric_producer.h"
2426
#include "opentelemetry/sdk/metrics/instruments.h"
2527
#include "opentelemetry/sdk/metrics/meter.h"
2628
#include "opentelemetry/sdk/metrics/meter_config.h"
@@ -531,6 +533,60 @@ TEST(MeterProvider, ConstructorsAreNotNoexcept)
531533
}
532534

533535
#if OPENTELEMETRY_HAVE_EXCEPTIONS
536+
TEST(MeterProvider, ConstructionFailureDiagnosticsAllowProviderReentry)
537+
{
538+
using opentelemetry::sdk::instrumentationscope::InstrumentationScope;
539+
using opentelemetry::sdk::instrumentationscope::ScopeConfigurator;
540+
for (int exception_type = 0; exception_type < 2; ++exception_type)
541+
{
542+
SCOPED_TRACE(exception_type);
543+
int construct_attempts = 0;
544+
auto configurator = std::make_unique<ScopeConfigurator<MeterConfig>>(
545+
ScopeConfigurator<MeterConfig>::Builder(MeterConfig::Default())
546+
.AddCondition(
547+
[&](const InstrumentationScope &scope) {
548+
++construct_attempts;
549+
if (scope.GetName() == "throwing-scope")
550+
{
551+
if (exception_type == 0)
552+
{
553+
throw std::runtime_error("injected construction failure");
554+
}
555+
throw 1;
556+
}
557+
return false;
558+
},
559+
MeterConfig::Default())
560+
.Build());
561+
MeterProvider provider(std::make_unique<ViewRegistry>(),
562+
opentelemetry::sdk::resource::Resource::Create({}),
563+
std::move(configurator));
564+
auto cached = provider.GetMeter("cached-scope");
565+
opentelemetry::nostd::shared_ptr<opentelemetry::metrics::Meter> reentrant_noop;
566+
int diagnostics = 0;
567+
ScopedTestLogHandler handler(LogLevel::Error, [&](const ScopedTestLogHandler::Entry &entry) {
568+
++diagnostics;
569+
EXPECT_EQ(entry.level, LogLevel::Error);
570+
EXPECT_EQ(provider.GetMeter("cached-scope"), cached);
571+
reentrant_noop = provider.GetMeter("uncached-scope");
572+
});
573+
574+
auto failed = provider.GetMeter("throwing-scope");
575+
ASSERT_NE(failed, nullptr);
576+
EXPECT_EQ(diagnostics, 1);
577+
EXPECT_EQ(reentrant_noop, failed);
578+
EXPECT_EQ(provider.GetMeter("cached-scope"), cached);
579+
EXPECT_EQ(provider.GetMeter("another-scope"), failed);
580+
EXPECT_EQ(construct_attempts, 2);
581+
EXPECT_EQ(diagnostics, 1);
582+
auto entries = handler.Drain();
583+
ASSERT_EQ(entries.size(), 1u);
584+
EXPECT_NE(entries[0].msg.find(exception_type == 0 ? "injected construction failure"
585+
: "unknown exception"),
586+
std::string::npos);
587+
}
588+
}
589+
534590
TEST(MeterProvider, GetMeterReturnsNoopOnConstructionFailure)
535591
{
536592
auto should_throw = std::make_shared<bool>(true);

‎sdk/test/trace/BUILD‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ cc_test(
1717
"//exporters/memory:in_memory_span_exporter",
1818
"//sdk/src/resource",
1919
"//sdk/src/trace",
20+
"//test_common:headers",
2021
"@com_google_googletest//:gtest_main",
2122
],
2223
)

0 commit comments

Comments
 (0)