Skip to content
This repository has been archived by the owner on Jan 26, 2024. It is now read-only.

ScopeManager with default thread_local implementation #124

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ include_directories(SYSTEM 3rd_party/include)
set(SRCS src/propagation.cpp
src/dynamic_load.cpp
src/noop.cpp
src/scope_manager.cpp
src/thread_local_scope_manager.cpp
src/tracer.cpp
src/tracer_factory.cpp
src/ext/tags.cpp)
Expand Down
67 changes: 67 additions & 0 deletions include/opentracing/scope_manager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#ifndef OPENTRACING_SCOPE_MANAGER_H
#define OPENTRACING_SCOPE_MANAGER_H

#include <opentracing/version.h>
#include <functional>
#include <memory>

namespace opentracing {
BEGIN_OPENTRACING_ABI_NAMESPACE

class Span;
class ScopeManager;

// Scope is returned by the ScopeManager when activating a span.
//
// The lifetime of the Scope instance represents the duration of the
// activation. Its lifetime can not exist beyond that of the
// ScopeManager. The specific implementation of ScopeManager may
// enforce additional constraints for the Scope object, please defer
// to the documentation of the ScopeManager for further information.
class Scope {
public:
using Callback = std::function<void()>;

// Create a Scope that will invoke callback on destruction.
Scope(Callback callback) noexcept;

Scope(Scope&& scope) noexcept;
~Scope();

private:
Scope(const Scope& scope) = delete;
Scope& operator=(const Scope&) = delete;
Scope& operator=(Scope&&) = delete;

Callback callback_;
};

// ScopeManager allows a Span to be activated for a specific scope.
//
// Once a Span has been activated, it can then be accessed via the
// ScopeManager. This interface can be implemented to provide
// different characteristics of Span propagation such as passing
// only within the same thread.
class ScopeManager {
public:
virtual ~ScopeManager() = default;

// Activate the given Span, returning a Scope to track its duration.
//
// A Span MUST be upgraded to a shared_ptr as consumers of the span
// via the ScopeManager may take ownership over it beyond the
// duration of the Scope. Implementations are expected to define the
// logic of Scope destrucion.
virtual Scope Activate(std::shared_ptr<Span> span) noexcept = 0;

// Return the current active Span.
//
// A span is always guaranteed to be returned. If there is no span
// active, then a default noop span instance should be returned.
virtual std::shared_ptr<Span> ActiveSpan() noexcept = 0;
};

END_OPENTRACING_ABI_NAMESPACE
} // namespace opentracing

#endif // OPENTRACING_SCOPE_MANAGER_H
43 changes: 43 additions & 0 deletions include/opentracing/thread_local_scope_manager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#ifndef OPENTRACING_THREAD_LOCAL_SCOPE_MANAGER_H
#define OPENTRACING_THREAD_LOCAL_SCOPE_MANAGER_H

#include <opentracing/scope_manager.h>
#include <opentracing/version.h>

#include <memory>

namespace opentracing {
BEGIN_OPENTRACING_ABI_NAMESPACE

class Span;

// A ScopeManager for propagating Spans within the same thread.
//
// Once activated, during the lifetime of the Scope, the Span can be
// accessed only within the same thread. This behaviour is best for
// propagating Spans down the execution stack without requiring each
// component to forward it explicitly. This is achieved using
// thread_local variables.
class ThreadLocalScopeManager : public ScopeManager {
public:
// Activate the given Span, returning a Scope to track its duration.
//
// An activated Span is only accessible from the same thread as the
// Activation for the lifetime of the Scope. The Scope MUST be stored
// on the Stack and MUST NOT be moved beyond the time of
// instantiation, otherwise behaviour is undefined.
Scope Activate(std::shared_ptr<Span> span) noexcept override;

// Return the current active Span.
//
// A span is always guaranteed to be returned. If there is no span
// active, then a default noop span instance should be returned.
// Only active Spans from the same thread as the activation are
// returned.
std::shared_ptr<Span> ActiveSpan() noexcept override;
};

END_OPENTRACING_ABI_NAMESPACE
} // namespace opentracing

#endif // OPENTRACING_THREAD_LOCAL_SCOPE_MANAGER_H
15 changes: 15 additions & 0 deletions include/opentracing/tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
#define OPENTRACING_TRACER_H

#include <opentracing/propagation.h>
#include <opentracing/scope_manager.h>
#include <opentracing/span.h>
#include <opentracing/string_view.h>
#include <opentracing/symbols.h>
#include <opentracing/thread_local_scope_manager.h>
#include <opentracing/util.h>
#include <opentracing/version.h>
#include <chrono>
Expand Down Expand Up @@ -147,6 +149,13 @@ class OPENTRACING_API Tracer {
return reader.Extract(*this);
}

// Return a reference to the tracer's ScopeManager.
//
// If not overriden, the default ThreadLocalScopeManager will be returned.
// The ScopeManager is merged with the Tracer for convenience so as not to
// require users to manage ownership themselves.
virtual opentracing::ScopeManager& ScopeManager() const;

// Close is called when a tracer is finished processing spans. It is not
// required to be called and its effect is unspecified. For example, an
// implementation might use this function to flush buffered spans to its
Expand All @@ -163,6 +172,12 @@ class OPENTRACING_API Tracer {
std::shared_ptr<Tracer> tracer) noexcept;

static bool IsGlobalTracerRegistered() noexcept;

private:
// The default ScopeManager to use if the Tracer doesn't provide their own.
//
// This is also required for backwards compatiblity.
mutable ThreadLocalScopeManager scope_manager_;
};

// StartTimestamp is a StartSpanOption that sets an explicit start timestamp for
Expand Down
18 changes: 18 additions & 0 deletions src/scope_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <opentracing/noop.h>
#include <opentracing/scope_manager.h>

namespace opentracing {
BEGIN_OPENTRACING_ABI_NAMESPACE

Scope::Scope(Callback callback) noexcept : callback_(callback) {}

Scope::Scope(Scope&& scope) noexcept { std::swap(callback_, scope.callback_); }

Scope::~Scope() {
if (callback_) {
callback_();
}
}

END_OPENTRACING_ABI_NAMESPACE
} // namespace opentracing
38 changes: 38 additions & 0 deletions src/thread_local_scope_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <opentracing/noop.h>
#include <opentracing/thread_local_scope_manager.h>

#include <map>
#include <utility>

namespace opentracing {
BEGIN_OPENTRACING_ABI_NAMESPACE

namespace {

const std::shared_ptr<Span> noopspan{MakeNoopTracer()->StartSpan("")};
using ThreadLocalSpanMap =
std::map<ThreadLocalScopeManager*, std::shared_ptr<Span>>;
thread_local ThreadLocalSpanMap thread_local_span_map;

} // anonymous namespace

Scope ThreadLocalScopeManager::Activate(std::shared_ptr<Span> span) noexcept {
auto it_bool = thread_local_span_map.insert(std::make_pair(this, span));
if (it_bool.second) {
return Scope{[this]() { thread_local_span_map.erase(this); }};
} else {
std::swap(it_bool.first->second, span);
return Scope{[this, span]() { thread_local_span_map[this] = span; }};
}
}

std::shared_ptr<Span> ThreadLocalScopeManager::ActiveSpan() noexcept {
auto active_it = thread_local_span_map.find(this);
if (active_it != thread_local_span_map.end()) {
return active_it->second;
}
return noopspan;
}

END_OPENTRACING_ABI_NAMESPACE
} // namespace opentracing
3 changes: 3 additions & 0 deletions src/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@ std::shared_ptr<Tracer> Tracer::InitGlobal(
bool Tracer::IsGlobalTracerRegistered() noexcept {
return TracerRegistry::instance().is_registered();
}

opentracing::ScopeManager& Tracer::ScopeManager() const { return scope_manager_; }

END_OPENTRACING_ABI_NAMESPACE
} // namespace opentracing
15 changes: 15 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
TEST_NAMES = [
"scope_manager_test",
"string_view_test",
"tracer_test",
"util_test",
Expand All @@ -14,6 +15,20 @@ TEST_NAMES = [
],
) for test_name in TEST_NAMES]

cc_test(
name = "thread_local_scope_manager_test",
srcs = [
"thread_local_scope_manager_test.cpp"
],
deps = [
"//:opentracing",
"//3rd_party:catch2",
],
linkopts = [
"-lpthread",
]
)

cc_test(
name = "mutiple_tracer_link_test",
srcs = [
Expand Down
10 changes: 10 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ else()
set(OPENTRACING_LIBRARY opentracing-static)
endif()

find_package(Threads)

add_executable(tracer_test tracer_test.cpp)
target_link_libraries(tracer_test ${OPENTRACING_LIBRARY})
add_test(NAME tracer_test COMMAND tracer_test)
Expand All @@ -17,6 +19,14 @@ add_test(NAME value_test COMMAND value_test)
add_executable(util_test util_test.cpp)
add_test(NAME util_test COMMAND util_test)

add_executable(scope_manager_test scope_manager_test.cpp)
target_link_libraries(scope_manager_test ${OPENTRACING_LIBRARY})
add_test(NAME scope_manager_test COMMAND scope_manager_test)

add_executable(thread_local_scope_manager_test thread_local_scope_manager_test.cpp)
target_link_libraries(thread_local_scope_manager_test ${OPENTRACING_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
add_test(NAME thread_local_scope_manager_test COMMAND thread_local_scope_manager_test)

if (BUILD_SHARED_LIBS AND BUILD_MOCKTRACER AND BUILD_DYNAMIC_LOADING)
add_executable(dynamic_load_test dynamic_load_test.cpp)
target_link_libraries(dynamic_load_test ${OPENTRACING_LIBRARY})
Expand Down
27 changes: 27 additions & 0 deletions test/scope_manager_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <opentracing/scope_manager.h>

using namespace opentracing;

#define CATCH_CONFIG_MAIN
#include <opentracing/catch2/catch.hpp>

TEST_CASE("scope") {
SECTION("Scope invokes callback on destruction") {
int called = 0;
{
Scope scope{[&called]() { ++called; }};
CHECK(called == 0);
}
CHECK(called == 1);
}

SECTION("Scope can be moved") {
int called = 0;
{
Scope scope{[&called]() { ++called; }};
{ Scope scope2{std::move(scope)}; }
CHECK(called == 1);
}
CHECK(called == 1); // check for double calls
}
}
57 changes: 57 additions & 0 deletions test/thread_local_scope_manager_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <opentracing/noop.h>
#include <opentracing/thread_local_scope_manager.h>
#include <memory>
#include <thread>

using namespace opentracing;

#define CATCH_CONFIG_MAIN
#include <opentracing/catch2/catch.hpp>

TEST_CASE("thread_local_scope_manager") {
ThreadLocalScopeManager sm;
std::shared_ptr<Span> default_span = sm.ActiveSpan();

SECTION("Returns noop span with no activations") { CHECK(default_span); }

auto tracer = MakeNoopTracer();

SECTION("Basic span activation/deactivation") {
std::shared_ptr<Span> span{tracer->StartSpan("a")};
CHECK(sm.ActiveSpan() == default_span);
{
auto scope = sm.Activate(span);
CHECK(sm.ActiveSpan() == span);
}
CHECK(sm.ActiveSpan() == default_span);
}

SECTION("Nested span activation/deactivation") {
std::shared_ptr<Span> span1{tracer->StartSpan("1")};
std::shared_ptr<Span> span2{tracer->StartSpan("2")};
CHECK(sm.ActiveSpan() == default_span);
{
auto scope1 = sm.Activate(span1);
CHECK(sm.ActiveSpan() == span1);
{
auto scope2 = sm.Activate(span2);
CHECK(sm.ActiveSpan() == span2);
}
CHECK(sm.ActiveSpan() == span1);
}
CHECK(sm.ActiveSpan() == default_span);
}

SECTION("Validate span activation is local to thread") {
std::shared_ptr<Span> thread_span;
std::shared_ptr<Span> span{tracer->StartSpan("a")};
auto scope = sm.Activate(span);

// Start thread which captures active span
std::thread{[&thread_span, &sm]() {
thread_span = sm.ActiveSpan();
}}.join();

CHECK(thread_span != span);
}
}
6 changes: 6 additions & 0 deletions test/tracer_test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <opentracing/ext/tags.h>
#include <opentracing/noop.h>
#include <opentracing/tracer.h>
#include <memory>
using namespace opentracing;

#define CATCH_CONFIG_MAIN
Expand Down Expand Up @@ -33,6 +34,11 @@ TEST_CASE("tracer") {
ChildOf(nullptr).Apply(options);
CHECK(options.references.size() == 0);
}

SECTION("Tracer provides a valid ScopeManager") {
std::shared_ptr<Span> span{tracer->StartSpan("s")};
tracer->ScopeManager().Activate(span);
}
}

TEST_CASE("A tracer can be globally registered") {
Expand Down