Skip to content

Commit 283114d

Browse files
authored
Runtime and threadlocal context (open-telemetry#209)
1 parent b096d8e commit 283114d

File tree

6 files changed

+271
-2
lines changed

6 files changed

+271
-2
lines changed

api/include/opentelemetry/context/context.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Context
1616
{
1717

1818
public:
19+
Context() = default;
1920
// Creates a context object from a map of keys and identifiers, this will
2021
// hold a shared_ptr to the head of the DataList linked list
2122
template <class T>
@@ -89,9 +90,9 @@ class Context
8990
return false;
9091
}
9192

92-
private:
93-
Context() = default;
93+
bool operator==(const Context &other) { return (head_ == other.head_); }
9494

95+
private:
9596
// A linked list to contain the keys and values of this context node
9697
class DataList
9798
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
#include "opentelemetry/context/context.h"
4+
5+
OPENTELEMETRY_BEGIN_NAMESPACE
6+
namespace context
7+
{
8+
// Provides a wrapper for propagating the context object globally. In order
9+
// to use either the threadlocal_context.h file must be included or another
10+
// implementation which must be derived from the RuntimeContext can be
11+
// provided.
12+
class RuntimeContext
13+
{
14+
public:
15+
class Token
16+
{
17+
public:
18+
bool operator==(const Context &other) noexcept { return context_ == other; }
19+
20+
~Token() noexcept { Detach(*this); }
21+
22+
private:
23+
friend class RuntimeContext;
24+
25+
// A constructor that sets the token's Context object to the
26+
// one that was passed in.
27+
Token(Context context) noexcept : context_(context){};
28+
29+
Token() noexcept = default;
30+
31+
Context context_;
32+
};
33+
34+
// Return the current context.
35+
static Context GetCurrent() noexcept { return context_handler_->InternalGetCurrent(); }
36+
37+
// Sets the current 'Context' object. Returns a token
38+
// that can be used to reset to the previous Context.
39+
static Token Attach(Context context) noexcept
40+
{
41+
return context_handler_->InternalAttach(context);
42+
}
43+
44+
// Resets the context to a previous value stored in the
45+
// passed in token. Returns true if successful, false otherwise
46+
static bool Detach(Token &token) noexcept { return context_handler_->InternalDetach(token); }
47+
48+
static RuntimeContext *context_handler_;
49+
50+
protected:
51+
// Provides a token with the passed in context
52+
Token CreateToken(Context context) noexcept { return Token(context); }
53+
54+
virtual Context InternalGetCurrent() noexcept = 0;
55+
56+
virtual Token InternalAttach(Context context) noexcept = 0;
57+
58+
virtual bool InternalDetach(Token &token) noexcept = 0;
59+
};
60+
} // namespace context
61+
OPENTELEMETRY_END_NAMESPACE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#pragma once
2+
3+
#include "opentelemetry/context/context.h"
4+
#include "opentelemetry/context/runtime_context.h"
5+
6+
OPENTELEMETRY_BEGIN_NAMESPACE
7+
namespace context
8+
{
9+
10+
// The ThreadLocalContext class is a derived class from RuntimeContext and
11+
// provides a wrapper for propogating context through cpp thread locally.
12+
// This file must be included to use the RuntimeContext class if another
13+
// implementation has not been registered.
14+
class ThreadLocalContext : public RuntimeContext
15+
{
16+
public:
17+
ThreadLocalContext() noexcept = default;
18+
19+
// Return the current context.
20+
Context InternalGetCurrent() noexcept override { return stack_.Top(); }
21+
22+
// Resets the context to a previous value stored in the
23+
// passed in token. Returns true if successful, false otherwise
24+
bool InternalDetach(Token &token) noexcept override
25+
{
26+
if (!(token == stack_.Top()))
27+
{
28+
return false;
29+
}
30+
stack_.Pop();
31+
return true;
32+
}
33+
34+
// Sets the current 'Context' object. Returns a token
35+
// that can be used to reset to the previous Context.
36+
Token InternalAttach(Context context) noexcept override
37+
{
38+
stack_.Push(context);
39+
Token old_context = CreateToken(context);
40+
return old_context;
41+
}
42+
43+
private:
44+
// A nested class to store the attached contexts in a stack.
45+
class Stack
46+
{
47+
friend class ThreadLocalContext;
48+
49+
Stack() noexcept : size_(0), capacity_(0), base_(nullptr){};
50+
51+
// Pops the top Context off the stack and returns it.
52+
Context Pop() noexcept
53+
{
54+
if (size_ <= 0)
55+
{
56+
return Context();
57+
}
58+
int index = size_ - 1;
59+
size_--;
60+
return base_[index];
61+
}
62+
63+
// Returns the Context at the top of the stack.
64+
Context Top() const noexcept
65+
{
66+
if (size_ <= 0)
67+
{
68+
return Context();
69+
}
70+
return base_[size_ - 1];
71+
}
72+
73+
// Pushes the passed in context pointer to the top of the stack
74+
// and resizes if necessary.
75+
void Push(Context context) noexcept
76+
{
77+
size_++;
78+
if (size_ > capacity_)
79+
{
80+
Resize(size_ * 2);
81+
}
82+
base_[size_ - 1] = context;
83+
}
84+
85+
// Reallocates the storage array to the pass in new capacity size.
86+
void Resize(int new_capacity) noexcept
87+
{
88+
int old_size = size_ - 1;
89+
if (new_capacity == 0)
90+
{
91+
new_capacity = 2;
92+
}
93+
Context *temp = new Context[new_capacity];
94+
if (base_ != nullptr)
95+
{
96+
std::copy(base_, base_ + old_size, temp);
97+
delete[] base_;
98+
}
99+
base_ = temp;
100+
}
101+
102+
~Stack() noexcept { delete[] base_; }
103+
104+
size_t size_;
105+
size_t capacity_;
106+
Context *base_;
107+
};
108+
109+
static thread_local Stack stack_;
110+
};
111+
thread_local ThreadLocalContext::Stack ThreadLocalContext::stack_ = ThreadLocalContext::Stack();
112+
113+
// Registers the ThreadLocalContext as the context handler for the RuntimeContext
114+
RuntimeContext *RuntimeContext::context_handler_ = new ThreadLocalContext();
115+
} // namespace context
116+
OPENTELEMETRY_END_NAMESPACE

api/test/context/BUILD

+11
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,14 @@ cc_test(
1010
"@com_google_googletest//:gtest_main",
1111
],
1212
)
13+
14+
cc_test(
15+
name = "runtime_context_test",
16+
srcs = [
17+
"runtime_context_test.cc",
18+
],
19+
deps = [
20+
"//api",
21+
"@com_google_googletest//:gtest_main",
22+
],
23+
)

api/test/context/context_test.cc

+19
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,22 @@ TEST(ContextTest, ContextHasKey)
118118
EXPECT_TRUE(context_test.HasKey("test_key"));
119119
EXPECT_FALSE(context_test.HasKey("foo_key"));
120120
}
121+
122+
// Tests that a copied context returns true when compared
123+
TEST(ContextTest, ContextCopyCompare)
124+
{
125+
std::map<std::string, context::ContextValue> map_test = {{"test_key", (int64_t)123}};
126+
context::Context context_test = context::Context(map_test);
127+
context::Context copied_test = context_test;
128+
EXPECT_TRUE(context_test == copied_test);
129+
}
130+
131+
// Tests that two differently constructed contexts return false when compared
132+
TEST(ContextTest, ContextDiffCompare)
133+
{
134+
std::map<std::string, context::ContextValue> map_test = {{"test_key", (int64_t)123}};
135+
std::map<std::string, context::ContextValue> map_foo = {{"foo_key", (int64_t)123}};
136+
context::Context context_test = context::Context(map_test);
137+
context::Context foo_test = context::Context(map_foo);
138+
EXPECT_FALSE(context_test == foo_test);
139+
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "opentelemetry/context/context.h"
2+
#include "opentelemetry/context/threadlocal_context.h"
3+
4+
#include <gtest/gtest.h>
5+
6+
using namespace opentelemetry;
7+
8+
// Tests that GetCurrent returns the current context
9+
TEST(RuntimeContextTest, GetCurrent)
10+
{
11+
std::map<std::string, context::ContextValue> map_test = {{"test_key", (int64_t)123}};
12+
context::Context test_context = context::Context(map_test);
13+
context::RuntimeContext::Token old_context = context::RuntimeContext::Attach(test_context);
14+
EXPECT_TRUE(context::RuntimeContext::GetCurrent() == test_context);
15+
context::RuntimeContext::Detach(old_context);
16+
}
17+
18+
// Tests that detach resets the context to the previous context
19+
TEST(RuntimeContextTest, Detach)
20+
{
21+
std::map<std::string, context::ContextValue> map_test = {{"test_key", (int64_t)123}};
22+
context::Context test_context = context::Context(map_test);
23+
context::Context foo_context = context::Context(map_test);
24+
25+
context::RuntimeContext::Token test_context_token = context::RuntimeContext::Attach(test_context);
26+
context::RuntimeContext::Token foo_context_token = context::RuntimeContext::Attach(foo_context);
27+
28+
context::RuntimeContext::Detach(foo_context_token);
29+
EXPECT_TRUE(context::RuntimeContext::GetCurrent() == test_context);
30+
context::RuntimeContext::Detach(test_context_token);
31+
}
32+
33+
// Tests that detach returns false when the wrong context is provided
34+
TEST(RuntimeContextTest, DetachWrongContext)
35+
{
36+
std::map<std::string, context::ContextValue> map_test = {{"test_key", (int64_t)123}};
37+
context::Context test_context = context::Context(map_test);
38+
context::Context foo_context = context::Context(map_test);
39+
context::RuntimeContext::Token test_context_token = context::RuntimeContext::Attach(test_context);
40+
context::RuntimeContext::Token foo_context_token = context::RuntimeContext::Attach(foo_context);
41+
EXPECT_FALSE(context::RuntimeContext::Detach(test_context_token));
42+
context::RuntimeContext::Detach(foo_context_token);
43+
context::RuntimeContext::Detach(test_context_token);
44+
}
45+
46+
// Tests that the ThreadLocalContext can handle three attached contexts
47+
TEST(RuntimeContextTest, ThreeAttachDetach)
48+
{
49+
std::map<std::string, context::ContextValue> map_test = {{"test_key", (int64_t)123}};
50+
context::Context test_context = context::Context(map_test);
51+
context::Context foo_context = context::Context(map_test);
52+
context::Context other_context = context::Context(map_test);
53+
context::RuntimeContext::Token test_context_token = context::RuntimeContext::Attach(test_context);
54+
context::RuntimeContext::Token foo_context_token = context::RuntimeContext::Attach(foo_context);
55+
context::RuntimeContext::Token other_context_token =
56+
context::RuntimeContext::Attach(other_context);
57+
58+
EXPECT_TRUE(context::RuntimeContext::Detach(other_context_token));
59+
EXPECT_TRUE(context::RuntimeContext::Detach(foo_context_token));
60+
EXPECT_TRUE(context::RuntimeContext::Detach(test_context_token));
61+
}

0 commit comments

Comments
 (0)