Skip to content

Context api alternative implementation #180

Merged
reyang merged 105 commits into
open-telemetry:masterfrom
satac2:context_api_content
Jul 21, 2020
Merged

Context api alternative implementation #180
reyang merged 105 commits into
open-telemetry:masterfrom
satac2:context_api_content

Conversation

@satac2
Copy link
Copy Markdown
Contributor

@satac2 satac2 commented Jul 16, 2020

Links the context objects together as a linked list where each time new data is written to a context, a new node is added containing that new information and each node has access to all the data before it and none of the data after. Within each node there is another linked list actually containing the key value pairs. This implementation uses no STL and therefore I believe should have no ABI compatibility issues. @reyang @maxgolov.

satac2 and others added 30 commits June 8, 2020 14:25
Co-authored-by: Kirk Kelsey <kirkmkelsey@gmail.com>
OPENTELEMETRY_BEGIN_NAMESPACE
namespace context
{
using ContextValue = nostd::variant<bool, int64_t, uint64_t, double>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we'll need nostd::shared_ptr<SpanContext> too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When I tried to add it I got a: variant is implicitly deleted because the default definition would be ill-formed error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So the nostd::shared_ptr does work. But whenever I try to add a unique_ptr of any type, I am getting the implicitly deleted because of ill formed definition error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nostd::shared_pr has been added is the unique_ptr necessary?

// Creates a context object from a map of keys and identifiers, this will
// hold a shared_ptr to the head of the DataList linked list
template <class T, nostd::enable_if_t<trace::detail::is_key_value_iterable<T>::value> * = nullptr>
Context(const T &keys_and_values)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not 100% sure, maybe I'm missing some part of the high-level view.

I think we'll use Context to obtain the currently active span: we will have a method to obtain the current context, the current context will hold the SpanContext of the active span. I'm trying to figure out necessary steps to make this possible:

  1. When a new span starts, we need to set a new current context. We will be able to get this new context via SetValue("current_span", span_context). Then the new span will be stored in the new current context.
  2. When the span ends, we will need to restore the previous context, as this should be the current context now (returning the parent span as active span).

I think we'll need some kind of parenting of contexts, but I'm not sure if it will be managed by the Context itself or by some other class.

{
Context context = Context(values);
nostd::shared_ptr<DataList> last_node;
for (nostd::shared_ptr<DataList> data = context.head_; data != nullptr; data = data->next_)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need shared_ptr while iterating through the linked list or there can be an optimization?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to just a ptr.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be simplified a bit:

nostd::shared_ptr<DataList>& last = context.head_;                          
while (last->next_ != nullptr) { last = last->next_; } 
                                                                                 
last->next_ = head_; 

}
}
}
return (int64_t)0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not blocking this PR, do we need a nil type?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Boost has a boost::blank type for this purpose. The more C++ idiomatic way would be top wrap the return value in a std::optional, but if feel skeptical about extending nostd by this (it only was introduced in C++17).

I also think this shouldn't block this PR.

Copy link
Copy Markdown
Member

@reyang reyang left a comment

Choose a reason for hiding this comment

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

LGTM.

@reyang
Copy link
Copy Markdown
Member

reyang commented Jul 21, 2020

The change looks good enough to me.

I think we might need some memory testing (e.g. checking if all the allocated linked-list nodes are freed up before exiting the application (e.g. by using our custom malloc/free) to make sure we're actually doing the correct/efficient ref-counting. Probably in another PR.

Looks like there are still open questions/comments, I'll wait until we got approval from @pyohannes.

Copy link
Copy Markdown
Contributor

@pyohannes pyohannes left a comment

Choose a reason for hiding this comment

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

A few more nits, but I think this is close to being done.

key_ = new char[nostd::string_view(iter->first).size()];
key_length_ = nostd::string_view(iter->first).size();
memcpy(key_, nostd::string_view(iter->first).data(),
nostd::string_view(iter->first).size() * sizeof(char));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please leave out the explicit nostd::string_view here, and below. It makes the code easier to read.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed.

// and returns that head.
DataList(nostd::string_view key, ContextValue value)
{
key_ = new char[nostd::string_view(key).size()];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No need for the explicit nostd::string_view here, this can just be key.size().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed.

// Builds a data list off of a key and value iterable and returns the head
template <class T,
nostd::enable_if_t<trace::detail::is_key_value_iterable<T>::value> * = nullptr>
DataList(const T &keys_and_vals)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add an additional move constructor:

DataList &operator=(DataList &&other)
{
  key_length_ = other.key_length_;
  value_      = std::move(other.value_);
  next_       = std::move(other.next_);

  key_       = other.key_;
  other.key_ = nullptr;

  return *this;
}

Then you can simplify this constructor and avoid code duplication:

template <class T>
DataList(const T &keys_and_vals) : key_{nullptr}
{
  bool first = true;
  auto *node = this;
  for (auto &iter : keys_and_vals)
  {
    if (first)
    {
      *node = std::move(DataList(iter.first, iter.second));
      first = false;
    } 
    else
    {
      node->next_ = nostd::shared_ptr<DataList>(new DataList(iter.first, iter.second));
      node        = node->next_.get();
    } 
  } 
} 

}
}
}
return (int64_t)0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Boost has a boost::blank type for this purpose. The more C++ idiomatic way would be top wrap the return value in a std::optional, but if feel skeptical about extending nostd by this (it only was introduced in C++17).

I also think this shouldn't block this PR.

}

// Returns the value associated with the passed in key.
context::ContextValue GetValue(nostd::string_view key)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be const and noexcept.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added.

}

// Checks for key and returns true if found
bool HasKey(nostd::string_view key)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be const and noexcept.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added.

// Returns the value associated with the passed in key.
context::ContextValue GetValue(nostd::string_view key)
{
for (DataList *data = &*head_; data != nullptr; data = &*(data->next_))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is a bit more readable:

for (DataList *data = head_.get(); data != nullptr; data = data->next_.get())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Change made.

{
Context context = Context(values);
nostd::shared_ptr<DataList> last_node;
for (nostd::shared_ptr<DataList> data = context.head_; data != nullptr; data = data->next_)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be simplified a bit:

nostd::shared_ptr<DataList>& last = context.head_;                          
while (last->next_ != nullptr) { last = last->next_; } 
                                                                                 
last->next_ = head_; 

// Checks for key and returns true if found
bool HasKey(nostd::string_view key)
{
for (nostd::shared_ptr<DataList> data = head_; data != nullptr; data = data->next_)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can avoid copies of shared_ptr here:

for (DataList *data = head_.get(); data != nullptr; data = data->next_.get())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Change made.

@pyohannes
Copy link
Copy Markdown
Contributor

I think we might need some memory testing (e.g. checking if all the allocated linked-list nodes are freed up before exiting the application (e.g. by using our custom malloc/free) to make sure we're actually doing the correct/efficient ref-counting. Probably in another PR.

I ran the unit test through valgrind, and it looks fine. It would be nice to have some automated memory checking though in our CI.

@satac2
Copy link
Copy Markdown
Contributor Author

satac2 commented Jul 21, 2020

Isn't there memory checking in the CI?

Copy link
Copy Markdown
Contributor

@pyohannes pyohannes left a comment

Choose a reason for hiding this comment

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

Thanks for all you efforts on this PR. I have one minor nit with the include lists, otherwise this is ready to merge.

#include "opentelemetry/context/context_value.h"
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/trace/key_value_iterable_view.h"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some include nits: I think atomic and key_value_iterable_view.h are not needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have removed them.

@pyohannes
Copy link
Copy Markdown
Contributor

Please rebase and then @reyang please merge.

@reyang reyang merged commit 37b275c into open-telemetry:master Jul 21, 2020
GerHobbelt pushed a commit to GerHobbelt/opentelemetry-cpp that referenced this pull request Jun 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:please-merge This PR is ready to be merged by a Maintainer (rebased, CI passed, has enough valid approvals, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants