Skip to content

Commit 69efc51

Browse files
authored
[SDK] Allow context construction failures to propagate (#4703)
1 parent fc137c6 commit 69efc51

10 files changed

Lines changed: 245 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ Increment the:
117117

118118
Breaking changes:
119119

120+
* [SDK] Remove `noexcept` from the public `TracerContext`, `LoggerContext`,
121+
and `MeterContext` constructors so allocation failures propagate to provider
122+
initialization callers instead of terminating. This changes their exception
123+
contract and may affect code relying on nothrow construction.
124+
[#4695](https://github.com/open-telemetry/opentelemetry-cpp/issues/4695)
125+
120126
* [SDK] Handle instrumentation object construction failures safely
121127
[#4469](https://github.com/open-telemetry/opentelemetry-cpp/pull/4469)
122128
* Removed `noexcept` from the public SDK `TracerProvider`,

‎sdk/include/opentelemetry/sdk/logs/logger_context.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class LoggerContext
4545
instrumentationscope::ScopeConfigurator<LoggerConfig>::Builder(
4646
LoggerConfig::Default())
4747
.Build()),
48-
LogRecordLimits log_record_limits = LogRecordLimits()) noexcept;
48+
LogRecordLimits log_record_limits = LogRecordLimits());
4949

5050
/**
5151
* Attaches a log processor to list of configured processors to this logger context.

‎sdk/include/opentelemetry/sdk/metrics/meter_context.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class MeterContext : public std::enable_shared_from_this<MeterContext>
8686
,
8787
ExemplarFilterType exemplar_filter_type = exemplar_filter_env::GetExemplarFilterFromEnv()
8888
#endif
89-
) noexcept;
89+
);
9090

9191
/**
9292
* Obtain the resource associated with this meter context.

‎sdk/include/opentelemetry/sdk/trace/tracer_context.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class TracerContext
5252
instrumentationscope::ScopeConfigurator<TracerConfig>::Builder(
5353
TracerConfig::Default())
5454
.Build()),
55-
SpanLimits span_limits = SpanLimits::NoLimits()) noexcept;
55+
SpanLimits span_limits = SpanLimits::NoLimits());
5656

5757
TracerContext(const TracerContext &) = delete;
5858
TracerContext(TracerContext &&) = delete;

‎sdk/src/logs/logger_context.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ LoggerContext::LoggerContext(
2727
std::vector<std::unique_ptr<LogRecordProcessor>> &&processors,
2828
const opentelemetry::sdk::resource::Resource &resource,
2929
std::unique_ptr<instrumentationscope::ScopeConfigurator<LoggerConfig>> logger_configurator,
30-
LogRecordLimits log_record_limits) noexcept
30+
LogRecordLimits log_record_limits)
3131
: resource_(resource),
3232
processor_(
3333
std::unique_ptr<LogRecordProcessor>(new MultiLogRecordProcessor(std::move(processors)))),

‎sdk/src/metrics/meter_context.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ MeterContext::MeterContext(
9494
,
9595
ExemplarFilterType exemplar_filter_type
9696
#endif
97-
) noexcept
97+
)
9898
: resource_{resource},
9999
views_(std::move(views)),
100100
sdk_start_ts_{std::chrono::system_clock::now()},

‎sdk/src/trace/tracer_context.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ TracerContext::TracerContext(
3131
std::unique_ptr<Sampler> sampler,
3232
std::unique_ptr<IdGenerator> id_generator,
3333
std::unique_ptr<instrumentationscope::ScopeConfigurator<TracerConfig>> tracer_configurator,
34-
SpanLimits span_limits) noexcept
34+
SpanLimits span_limits)
3535
: resource_(resource),
3636
sampler_(std::move(sampler)),
3737
id_generator_(std::move(id_generator)),

‎sdk/test/common/BUILD‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,18 @@ cc_test(
239239
"@com_google_googletest//:gtest_main",
240240
],
241241
)
242+
243+
cc_test(
244+
name = "provider_initialization_test",
245+
srcs = ["provider_initialization_test.cc"],
246+
# Preserve the test's new/delete overrides; libc malloc/free are still checked.
247+
env = {"VALGRIND_OPTS": "--soname-synonyms=somalloc=nouserintercepts"},
248+
tags = ["test"],
249+
deps = [
250+
"//sdk/src/logs",
251+
"//sdk/src/metrics",
252+
"//sdk/src/resource",
253+
"//sdk/src/trace",
254+
"@com_google_googletest//:gtest_main",
255+
],
256+
)

‎sdk/test/common/CMakeLists.txt‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,12 @@ if(OTELCPP_WITH_BENCHMARK)
5555
target_link_libraries(attributemap_hash_benchmark benchmark::benchmark
5656
${CMAKE_THREAD_LIBS_INIT} opentelemetry_common)
5757
endif()
58+
59+
# Executable allocation overrides cannot intercept Windows DLL allocations.
60+
if(NOT (WIN32 AND BUILD_SHARED_LIBS))
61+
add_executable(provider_initialization_test provider_initialization_test.cc)
62+
target_link_libraries(
63+
provider_initialization_test ${GTEST_BOTH_LIBRARIES} opentelemetry_trace
64+
opentelemetry_logs opentelemetry_metrics)
65+
gtest_add_tests(TARGET provider_initialization_test TEST_PREFIX common.)
66+
endif()
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#include <gtest/gtest.h>
5+
#include <cstddef> // IWYU pragma: keep
6+
#include <cstdlib>
7+
#include <memory>
8+
#include <new>
9+
#include <string>
10+
#include <utility>
11+
#include <vector>
12+
13+
#include "opentelemetry/common/macros.h"
14+
#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h"
15+
#include "opentelemetry/sdk/logs/logger_config.h"
16+
#include "opentelemetry/sdk/logs/logger_context.h"
17+
#include "opentelemetry/sdk/logs/logger_provider.h"
18+
#include "opentelemetry/sdk/logs/multi_log_record_processor.h"
19+
#include "opentelemetry/sdk/logs/processor.h"
20+
#include "opentelemetry/sdk/metrics/meter_context.h"
21+
#include "opentelemetry/sdk/metrics/meter_provider.h"
22+
#include "opentelemetry/sdk/metrics/view/view_registry.h"
23+
#include "opentelemetry/sdk/resource/resource.h"
24+
#include "opentelemetry/sdk/trace/id_generator.h"
25+
#include "opentelemetry/sdk/trace/multi_span_processor.h"
26+
#include "opentelemetry/sdk/trace/processor.h"
27+
#include "opentelemetry/sdk/trace/random_id_generator.h"
28+
#include "opentelemetry/sdk/trace/sampler.h"
29+
#include "opentelemetry/sdk/trace/samplers/always_on.h"
30+
#include "opentelemetry/sdk/trace/tracer_config.h"
31+
#include "opentelemetry/sdk/trace/tracer_context.h"
32+
#include "opentelemetry/sdk/trace/tracer_provider.h"
33+
34+
#if OPENTELEMETRY_HAVE_EXCEPTIONS
35+
namespace
36+
{
37+
thread_local std::size_t failure_size = 0;
38+
39+
class FailAllocation
40+
{
41+
public:
42+
explicit FailAllocation(std::size_t size) : previous_(std::exchange(failure_size, size)) {}
43+
~FailAllocation() { failure_size = previous_; }
44+
FailAllocation(const FailAllocation &) = delete;
45+
FailAllocation &operator=(const FailAllocation &) = delete;
46+
FailAllocation(FailAllocation &&) = delete;
47+
FailAllocation &operator=(FailAllocation &&) = delete;
48+
49+
private:
50+
std::size_t previous_;
51+
};
52+
53+
// Target the resource's schema string, not variant-copy cleanup or constructor arguments.
54+
// Track the separate attribute-copy cleanup failure in:
55+
// https://github.com/open-telemetry/opentelemetry-cpp/issues/4707
56+
constexpr std::size_t kResourceValueSize = 4096;
57+
} // namespace
58+
59+
// Keep allocation injection in this dedicated test executable.
60+
// Keep the malloc/free implementations out of line to avoid GCC mismatch warnings.
61+
# if defined(__GNUC__) && !defined(__clang__)
62+
[[gnu::noinline]]
63+
# endif
64+
void *operator new(std::size_t size)
65+
{
66+
// A threshold also covers standard libraries that round string capacities up.
67+
if (failure_size != 0 && size >= failure_size)
68+
{
69+
failure_size = 0;
70+
throw std::bad_alloc();
71+
}
72+
if (void *result = std::malloc(size == 0 ? 1 : size)) // NOLINT(cppcoreguidelines-no-malloc)
73+
{
74+
return result;
75+
}
76+
throw std::bad_alloc();
77+
}
78+
79+
void *operator new[](std::size_t size)
80+
{
81+
return ::operator new(size);
82+
}
83+
# if defined(__GNUC__) && !defined(__clang__)
84+
[[gnu::noinline]]
85+
# endif
86+
void operator delete(void *ptr) noexcept
87+
{
88+
std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc)
89+
}
90+
void operator delete[](void *ptr) noexcept
91+
{
92+
::operator delete(ptr);
93+
}
94+
void operator delete(void *ptr, std::size_t) noexcept
95+
{
96+
::operator delete(ptr);
97+
}
98+
void operator delete[](void *ptr, std::size_t) noexcept
99+
{
100+
::operator delete(ptr);
101+
}
102+
void *operator new(std::size_t size, const std::nothrow_t &) noexcept
103+
{
104+
try
105+
{
106+
return ::operator new(size);
107+
}
108+
catch (const std::bad_alloc &)
109+
{
110+
return nullptr;
111+
}
112+
}
113+
void *operator new[](std::size_t size, const std::nothrow_t &tag) noexcept
114+
{
115+
return ::operator new(size, tag);
116+
}
117+
void operator delete(void *ptr, const std::nothrow_t &) noexcept
118+
{
119+
::operator delete(ptr);
120+
}
121+
void operator delete[](void *ptr, const std::nothrow_t &) noexcept
122+
{
123+
::operator delete(ptr);
124+
}
125+
126+
namespace trace_sdk = opentelemetry::sdk::trace;
127+
namespace logs_sdk = opentelemetry::sdk::logs;
128+
namespace metrics_sdk = opentelemetry::sdk::metrics;
129+
using opentelemetry::sdk::resource::Resource;
130+
131+
TEST(ProviderInitialization, TracerContextResourceCopyThrows)
132+
{
133+
Resource resource({{"key", true}}, std::string(kResourceValueSize, 'x'));
134+
auto sampler = std::make_unique<trace_sdk::AlwaysOnSampler>();
135+
auto generator = std::make_unique<trace_sdk::RandomIdGenerator>();
136+
FailAllocation failure(kResourceValueSize + 1);
137+
EXPECT_THROW(trace_sdk::TracerContext({}, resource, std::move(sampler), std::move(generator)),
138+
std::bad_alloc);
139+
}
140+
141+
TEST(ProviderInitialization, LoggerContextResourceCopyThrows)
142+
{
143+
Resource resource({{"key", true}}, std::string(kResourceValueSize, 'x'));
144+
FailAllocation failure(kResourceValueSize + 1);
145+
EXPECT_THROW(logs_sdk::LoggerContext({}, resource), std::bad_alloc);
146+
}
147+
148+
TEST(ProviderInitialization, MeterContextResourceCopyThrows)
149+
{
150+
Resource resource({{"key", true}}, std::string(kResourceValueSize, 'x'));
151+
auto views = std::make_unique<metrics_sdk::ViewRegistry>();
152+
FailAllocation failure(kResourceValueSize + 1);
153+
EXPECT_THROW(metrics_sdk::MeterContext(std::move(views), resource), std::bad_alloc);
154+
}
155+
156+
TEST(ProviderInitialization, TracerProviderResourceCopyReachesCaller)
157+
{
158+
Resource resource({{"key", true}}, std::string(kResourceValueSize, 'x'));
159+
auto sampler = std::make_unique<trace_sdk::AlwaysOnSampler>();
160+
auto generator = std::make_unique<trace_sdk::RandomIdGenerator>();
161+
FailAllocation failure(kResourceValueSize + 1);
162+
EXPECT_THROW(trace_sdk::TracerProvider(std::vector<std::unique_ptr<trace_sdk::SpanProcessor>>{},
163+
resource, std::move(sampler), std::move(generator)),
164+
std::bad_alloc);
165+
}
166+
167+
TEST(ProviderInitialization, LoggerProviderResourceCopyReachesCaller)
168+
{
169+
Resource resource({{"key", true}}, std::string(kResourceValueSize, 'x'));
170+
FailAllocation failure(kResourceValueSize + 1);
171+
EXPECT_THROW(logs_sdk::LoggerProvider(
172+
std::vector<std::unique_ptr<logs_sdk::LogRecordProcessor>>{}, resource),
173+
std::bad_alloc);
174+
}
175+
176+
TEST(ProviderInitialization, MeterProviderResourceCopyReachesCaller)
177+
{
178+
Resource resource({{"key", true}}, std::string(kResourceValueSize, 'x'));
179+
auto views = std::make_unique<metrics_sdk::ViewRegistry>();
180+
FailAllocation failure(kResourceValueSize + 1);
181+
EXPECT_THROW(metrics_sdk::MeterProvider(std::move(views), resource), std::bad_alloc);
182+
}
183+
184+
TEST(ProviderInitialization, TracerContextProcessorAllocationThrows)
185+
{
186+
Resource resource;
187+
auto sampler = std::make_unique<trace_sdk::AlwaysOnSampler>();
188+
auto generator = std::make_unique<trace_sdk::RandomIdGenerator>();
189+
using Configurator =
190+
opentelemetry::sdk::instrumentationscope::ScopeConfigurator<trace_sdk::TracerConfig>;
191+
auto configurator = std::make_unique<Configurator>(
192+
Configurator::Builder(trace_sdk::TracerConfig::Default()).Build());
193+
FailAllocation failure(sizeof(trace_sdk::MultiSpanProcessor));
194+
EXPECT_THROW(trace_sdk::TracerContext({}, resource, std::move(sampler), std::move(generator),
195+
std::move(configurator)),
196+
std::bad_alloc);
197+
}
198+
199+
TEST(ProviderInitialization, LoggerContextProcessorAllocationThrows)
200+
{
201+
Resource resource;
202+
using Configurator =
203+
opentelemetry::sdk::instrumentationscope::ScopeConfigurator<logs_sdk::LoggerConfig>;
204+
auto configurator = std::make_unique<Configurator>(
205+
Configurator::Builder(logs_sdk::LoggerConfig::Default()).Build());
206+
FailAllocation failure(sizeof(logs_sdk::MultiLogRecordProcessor));
207+
EXPECT_THROW(logs_sdk::LoggerContext({}, resource, std::move(configurator)), std::bad_alloc);
208+
}
209+
#endif // OPENTELEMETRY_HAVE_EXCEPTIONS

0 commit comments

Comments
 (0)