Skip to content
Merged
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
39 changes: 39 additions & 0 deletions api/include/opentelemetry/context/runtime_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,26 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
{
friend class ThreadLocalContextStorage;

/**
* Limit the max stack depth.
* The stack will still work beyond this limit,
* counting push() and pop() properly,
* but will not record contexts.
*
* In practice, this should not affect instrumented applications,
* the limit is set to 1 million nested scopes.
*
* The whole reason for this limit to exist is to prevent
* compiler warnings like:
* error: argument 1 value ‘18446744073709551615’
* exceeds maximum object size 9223372036854775807
* [-Werror=alloc-size-larger-than=]
* on this line of code:
* Context *temp = new Context[new_capacity];
* when compiling with gcc and optimizations (-flto).
*/
static constexpr size_t max_capacity_ = 1000000;

Stack() noexcept : size_(0), capacity_(0), base_(nullptr) {}

// Pops the top Context off the stack.
Expand All @@ -250,6 +270,11 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
{
return;
}
if (size_ > max_capacity_)
{
size_ -= 1;
return;
}
// Store empty Context before decrementing `size`, to ensure
// the shared_ptr object (if stored in prev context object ) are released.
// The stack is not resized, and the unused memory would be reutilised
Expand Down Expand Up @@ -278,6 +303,10 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
{
return Context();
}
if (size_ > max_capacity_)
{
return Context();
Copy link
Member

Choose a reason for hiding this comment

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

Just curious - should it return the last valid context, instead of empty one ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question.

In my understanding, Scope will attach and detach contexts on creation and destruction, adding/removing from the stack.

If the code returns the last valid context, then we will have:

  • every scope nested at depth max_capacity_, max_capacity_ + 1, ..., max_capacity_ + N share the same context.
  • Scope at max_capacity_ + N will destroy the last valid scope on destruction, causing side effects.

So, I think Top() and Pop() should return an empty context instead, once the stack depth goes beyond the limit.

This is very theoretical, because any process with 1 million nested scopes in the call stack will never reach this point, being long dead, already killed for other reasons (stack overflow, out of memory).

}
return base_[size_ - 1];
}

Expand All @@ -286,6 +315,10 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
void Push(const Context &context) noexcept
{
size_++;
if (size_ > max_capacity_)
{
return;
}
if (size_ > capacity_)
{
Resize(size_ * 2);
Copy link
Contributor

@Reneg973 Reneg973 Oct 25, 2025

Choose a reason for hiding this comment

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

@marcalff
Did you see this line?
Resize(size_ * 2);

Expand All @@ -299,8 +332,14 @@ class ThreadLocalContextStorage : public RuntimeContextStorage
size_t old_size = size_ - 1;
if (new_capacity == 0)
{
// First increase
new_capacity = 2;
}
if (new_capacity > max_capacity_)
{
// Last increase
new_capacity = max_capacity_;
}
Context *temp = new Context[new_capacity];
if (base_ != nullptr)
{
Expand Down
Loading