Introduce Context::Key with pointer comparison speeds (#1988)

This commit is contained in:
Anton Kolesnyk 2021-04-05 18:26:50 +00:00 committed by GitHub
parent 642c311676
commit a7fdea42e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 208 additions and 113 deletions

View File

@ -16,6 +16,7 @@
- `GetMinorVersion`.
- Removed from `Azure::Core::Http::Policies` namespace: `HttpPolicyOrder`, `TransportPolicy`, `RetryPolicy`, `RequestIdPolicy`, `TelemetryPolicy`, `BearerTokenAuthenticationPolicy`, `LogPolicy`.
- Renamed `Azure::Core::Http::RawResponse::GetBodyStream()` to `ExtractBodyStream()`.
- Introduced `Azure::Core::Context::Key` class which takes place of `std::string` used for `Azure::Core::Context` keys previously.
## 1.0.0-beta.7 (2021-03-11)

View File

@ -39,6 +39,23 @@ namespace Azure { namespace Core {
*/
class Context {
public:
/**
* @brief A context key.
*/
class Key final {
Key const* m_uniqueAddress;
public:
Key() : m_uniqueAddress(this) {}
bool operator==(Key const& other) const
{
return this->m_uniqueAddress == other.m_uniqueAddress;
}
bool operator!=(Key const& other) const { return !(*this == other); }
};
/**
* @brief A type used to provide a point in time for context expiration.
*/
@ -49,7 +66,7 @@ namespace Azure { namespace Core {
{
std::shared_ptr<ContextSharedState> Parent;
std::atomic_int64_t CancelAtMsecSinceEpoch;
std::string Key;
Context::Key Key;
std::shared_ptr<void> Value;
const std::type_info& ValueType;
@ -82,7 +99,7 @@ namespace Azure { namespace Core {
explicit ContextSharedState(
const std::shared_ptr<ContextSharedState>& parent,
time_point cancelAt,
const std::string& key,
Context::Key const& key,
T value) // NOTE, should this be T&&
: Parent(parent), CancelAtMsecSinceEpoch(ToMsecSinceEpoch(cancelAt)), Key(key),
Value(std::make_shared<T>(std::move(value))), ValueType(typeid(T))
@ -131,7 +148,7 @@ namespace Azure { namespace Core {
*
* @return A child context with no expiration and the \p key and \p value associated with it.
*/
template <class T> Context WithValue(const std::string& key, T&& value) const
template <class T> Context WithValue(Key const& key, T&& value) const
{
return Context{std::make_shared<ContextSharedState>(
m_contextSharedState, (time_point::max)(), key, std::forward<T>(value))};
@ -146,21 +163,18 @@ namespace Azure { namespace Core {
* @return A value associated with the context found; an empty value if a specific value can't
* be found.
*/
template <class T> const T& Get(const std::string& key) const
template <class T> const T& Get(Key const& key) const
{
if (!key.empty())
for (auto ptr = m_contextSharedState; ptr; ptr = ptr->Parent)
{
for (auto ptr = m_contextSharedState; ptr; ptr = ptr->Parent)
if (ptr->Key == key)
{
if (ptr->Key == key)
if (typeid(T) != ptr->ValueType)
{
if (typeid(T) != ptr->ValueType)
{
// type mismatch
std::abort();
}
return *reinterpret_cast<const T*>(ptr->Value.get());
// type mismatch
std::abort();
}
return *reinterpret_cast<const T*>(ptr->Value.get());
}
}
std::abort();
@ -177,16 +191,13 @@ namespace Azure { namespace Core {
* @return `true` if this context, or the tree branch this context belongs to has a \p key
* associated with it. `false` otherwise.
*/
bool HasKey(const std::string& key) const
bool HasKey(Key const& key) const
{
if (!key.empty())
for (auto ptr = m_contextSharedState; ptr; ptr = ptr->Parent)
{
for (auto ptr = m_contextSharedState; ptr; ptr = ptr->Parent)
if (ptr->Key == key)
{
if (ptr->Key == key)
{
return true;
}
return true;
}
}
return false;

View File

@ -144,7 +144,9 @@ bool ShouldRetryOnResponse(
return true;
}
static constexpr char RetryKey[] = "AzureSdkRetryPolicyCounter";
namespace {
Context::Key const RetryKey;
}
/**
* @brief Creates a new #Context node from \p parent with the information about the retrying while

View File

@ -16,25 +16,30 @@ using namespace Azure::Core;
TEST(Context, Basic)
{
Context context;
EXPECT_FALSE(context.HasKey(""));
EXPECT_FALSE(context.HasKey("key"));
Context::Key const key;
EXPECT_FALSE(context.HasKey(key));
}
TEST(Context, BasicBool)
{
Context context;
Context::Key const key;
// New context from previous
auto c2 = context.WithValue("key", true);
auto& value = c2.Get<bool>("key");
auto c2 = context.WithValue(key, true);
auto& value = c2.Get<bool>(key);
EXPECT_TRUE(value == true);
}
TEST(Context, BasicInt)
{
Context context;
Context::Key const key;
// New context from previous
auto c2 = context.WithValue("key", 123);
auto& value = c2.Get<int>("key");
auto c2 = context.WithValue(key, 123);
auto& value = c2.Get<int>(key);
EXPECT_TRUE(value == 123);
}
@ -43,9 +48,11 @@ TEST(Context, BasicStdString)
std::string s("Test String");
Context context;
Context::Key const key;
// New context from previous
auto c2 = context.WithValue("key", s);
auto& value = c2.Get<std::string>("key");
auto c2 = context.WithValue(key, s);
auto& value = c2.Get<std::string>(key);
EXPECT_TRUE(value == s);
}
@ -55,9 +62,11 @@ TEST(Context, BasicChar)
std::string s(str);
Context context;
Context::Key const key;
// New context from previous
auto c2 = context.WithValue("key", str);
auto& value = c2.Get<const char*>("key");
auto c2 = context.WithValue(key, str);
auto& value = c2.Get<const char*>(key);
EXPECT_TRUE(value == s);
EXPECT_TRUE(value == str);
}
@ -80,10 +89,12 @@ TEST(Context, NestedIsCancelled)
auto deadline = std::chrono::system_clock::now() + duration;
Context context;
auto c2 = context.WithValue("Key", "Value");
Context::Key const key;
auto c2 = context.WithValue(key, "Value");
EXPECT_FALSE(c2.IsCancelled());
EXPECT_TRUE(c2.HasKey("Key"));
EXPECT_FALSE(context.HasKey("Key"));
EXPECT_TRUE(c2.HasKey(key));
EXPECT_FALSE(context.HasKey(key));
auto c3 = context.WithDeadline(deadline);
EXPECT_FALSE(context.IsCancelled());
@ -95,26 +106,28 @@ TEST(Context, NestedIsCancelled)
EXPECT_FALSE(c2.IsCancelled());
EXPECT_TRUE(c3.IsCancelled());
EXPECT_TRUE(c2.HasKey("Key"));
EXPECT_FALSE(context.HasKey("Key"));
EXPECT_FALSE(c3.HasKey("Key"));
EXPECT_TRUE(c2.HasKey(key));
EXPECT_FALSE(context.HasKey(key));
EXPECT_FALSE(c3.HasKey(key));
}
TEST(Context, CancelWithValue)
{
Context context;
auto c2 = context.WithValue("Key", "Value");
Context::Key const key;
auto c2 = context.WithValue(key, "Value");
EXPECT_FALSE(context.IsCancelled());
EXPECT_FALSE(c2.IsCancelled());
EXPECT_TRUE(c2.HasKey("Key"));
EXPECT_FALSE(context.HasKey("Key"));
EXPECT_TRUE(c2.HasKey(key));
EXPECT_FALSE(context.HasKey(key));
c2.Cancel();
EXPECT_TRUE(c2.IsCancelled());
EXPECT_FALSE(context.IsCancelled());
EXPECT_TRUE(c2.HasKey("Key"));
EXPECT_FALSE(context.HasKey("Key"));
EXPECT_TRUE(c2.HasKey(key));
EXPECT_FALSE(context.HasKey(key));
}
TEST(Context, ThrowIfCancelled)
@ -132,22 +145,30 @@ TEST(Context, ThrowIfCancelled)
TEST(Context, Chain)
{
Context context;
// New context from previous
auto c2 = context.WithValue("c2", 123);
auto c3 = c2.WithValue("c3", 456);
auto c4 = c3.WithValue("c4", 789);
auto c5 = c4.WithValue("c5", "5");
auto c6 = c5.WithValue("c6", "6");
auto c7 = c6.WithValue("c7", "7");
auto finalContext = c7.WithValue("finalContext", "Final");
Context::Key const key2;
Context::Key const key3;
Context::Key const key4;
Context::Key const key5;
Context::Key const key6;
Context::Key const key7;
Context::Key const keyFinal;
auto& valueT2 = finalContext.Get<int>("c2");
auto& valueT3 = finalContext.Get<int>("c3");
auto& valueT4 = finalContext.Get<int>("c4");
auto& valueT5 = finalContext.Get<const char*>("c5");
auto& valueT6 = finalContext.Get<const char*>("c6");
auto& valueT7 = finalContext.Get<const char*>("c7");
auto& valueT8 = finalContext.Get<const char*>("finalContext");
// New context from previous
auto c2 = context.WithValue(key2, 123);
auto c3 = c2.WithValue(key3, 456);
auto c4 = c3.WithValue(key4, 789);
auto c5 = c4.WithValue(key5, "5");
auto c6 = c5.WithValue(key6, "6");
auto c7 = c6.WithValue(key7, "7");
auto finalContext = c7.WithValue(keyFinal, "Final");
auto& valueT2 = finalContext.Get<int>(key2);
auto& valueT3 = finalContext.Get<int>(key3);
auto& valueT4 = finalContext.Get<int>(key4);
auto& valueT5 = finalContext.Get<const char*>(key5);
auto& valueT6 = finalContext.Get<const char*>(key6);
auto& valueT7 = finalContext.Get<const char*>(key7);
auto& valueT8 = finalContext.Get<const char*>(keyFinal);
EXPECT_TRUE(valueT2 == 123);
EXPECT_TRUE(valueT3 == 456);
@ -161,12 +182,14 @@ TEST(Context, Chain)
TEST(Context, MatchingKeys)
{
Context context;
// New context from previous
auto c2 = context.WithValue("key", 123);
auto c3 = c2.WithValue("key", 456);
Context::Key const key;
auto& valueT2 = c2.Get<int>("key");
auto& valueT3 = c3.Get<int>("key");
// New context from previous
auto c2 = context.WithValue(key, 123);
auto c3 = c2.WithValue(key, 456);
auto& valueT2 = c2.Get<int>(key);
auto& valueT3 = c3.Get<int>(key);
EXPECT_TRUE(valueT2 == 123);
EXPECT_TRUE(valueT3 == 456);
@ -179,68 +202,126 @@ struct SomeStructForContext
TEST(Context, InstanceValue)
{
auto contextP = Context::GetApplicationContext().WithValue("struct", SomeStructForContext());
auto& contextValueRef = contextP.Get<SomeStructForContext>("struct");
Context::Key const key;
auto contextP = Context::GetApplicationContext().WithValue(key, SomeStructForContext());
auto& contextValueRef = contextP.Get<SomeStructForContext>(key);
EXPECT_EQ(contextValueRef.someField, 12345);
}
TEST(Context, UniquePtr)
{
auto contextP = Context::GetApplicationContext().WithValue(
"struct", std::make_unique<SomeStructForContext>());
auto& contextValueRef = contextP.Get<std::unique_ptr<SomeStructForContext>>("struct");
Context::Key const key;
auto contextP
= Context::GetApplicationContext().WithValue(key, std::make_unique<SomeStructForContext>());
auto& contextValueRef = contextP.Get<std::unique_ptr<SomeStructForContext>>(key);
EXPECT_EQ(contextValueRef->someField, 12345);
}
TEST(Context, HeapLinkIntegrity)
{
Context::Key const a;
Context::Key const b;
Context::Key const c;
Context::Key const d;
Context::Key const e;
Context thirdGeneration; // To be used at the end
{
Context root;
auto firstGeneration = root.WithValue("a", std::string("a"));
EXPECT_TRUE(firstGeneration.HasKey("a"));
auto firstGeneration = root.WithValue(a, std::string("a"));
EXPECT_TRUE(firstGeneration.HasKey(a));
auto secondGeneration = firstGeneration.WithValue("b", std::string("b"));
EXPECT_TRUE(secondGeneration.HasKey("a"));
EXPECT_EQ("a", secondGeneration.Get<std::string>("a"));
EXPECT_TRUE(secondGeneration.HasKey("b"));
EXPECT_EQ("b", secondGeneration.Get<std::string>("b"));
auto secondGeneration = firstGeneration.WithValue(b, std::string("b"));
EXPECT_TRUE(secondGeneration.HasKey(a));
EXPECT_EQ("a", secondGeneration.Get<std::string>(a));
EXPECT_TRUE(secondGeneration.HasKey(b));
EXPECT_EQ("b", secondGeneration.Get<std::string>(b));
// Now overide the generation
secondGeneration = secondGeneration.WithValue("c", std::string("c"));
secondGeneration = secondGeneration.WithValue(c, std::string("c"));
EXPECT_TRUE(
secondGeneration.HasKey("a")); // Still know about first gen - The link is still in heap
EXPECT_EQ("a", secondGeneration.Get<std::string>("a"));
secondGeneration.HasKey(a)); // Still know about first gen - The link is still in heap
EXPECT_EQ("a", secondGeneration.Get<std::string>(a));
EXPECT_TRUE(secondGeneration.HasKey(
"b")); // Still knows about the initial second gen, as a shared_ptr, it is still on heap
EXPECT_EQ("b", secondGeneration.Get<std::string>("b"));
EXPECT_TRUE(secondGeneration.HasKey("c")); // Check new value
EXPECT_EQ("c", secondGeneration.Get<std::string>("c"));
b)); // Still knows about the initial second gen, as a shared_ptr, it is still on heap
EXPECT_EQ("b", secondGeneration.Get<std::string>(b));
EXPECT_TRUE(secondGeneration.HasKey(c)); // Check new value
EXPECT_EQ("c", secondGeneration.Get<std::string>(c));
// One more override
secondGeneration = secondGeneration.WithValue("d", std::string("d"));
EXPECT_TRUE(secondGeneration.HasKey("a"));
EXPECT_EQ("a", secondGeneration.Get<std::string>("a"));
EXPECT_TRUE(secondGeneration.HasKey("b"));
EXPECT_EQ("b", secondGeneration.Get<std::string>("b"));
EXPECT_TRUE(secondGeneration.HasKey("c"));
EXPECT_EQ("c", secondGeneration.Get<std::string>("c"));
EXPECT_TRUE(secondGeneration.HasKey("d"));
EXPECT_EQ("d", secondGeneration.Get<std::string>("d"));
secondGeneration = secondGeneration.WithValue(d, std::string("d"));
EXPECT_TRUE(secondGeneration.HasKey(a));
EXPECT_EQ("a", secondGeneration.Get<std::string>(a));
EXPECT_TRUE(secondGeneration.HasKey(b));
EXPECT_EQ("b", secondGeneration.Get<std::string>(b));
EXPECT_TRUE(secondGeneration.HasKey(c));
EXPECT_EQ("c", secondGeneration.Get<std::string>(c));
EXPECT_TRUE(secondGeneration.HasKey(d));
EXPECT_EQ("d", secondGeneration.Get<std::string>(d));
// New Gen
thirdGeneration = secondGeneration.WithValue("e", std::string("e"));
thirdGeneration = secondGeneration.WithValue(e, std::string("e"));
}
// Went out of scope, root and secondGeneration are destroyed. but should remain in heap for the
// third-generation since the previous geneations are still alive inside his heart <3.
EXPECT_TRUE(thirdGeneration.HasKey("a"));
EXPECT_EQ("a", thirdGeneration.Get<std::string>("a"));
EXPECT_TRUE(thirdGeneration.HasKey("b"));
EXPECT_EQ("b", thirdGeneration.Get<std::string>("b"));
EXPECT_TRUE(thirdGeneration.HasKey("c"));
EXPECT_EQ("c", thirdGeneration.Get<std::string>("c"));
EXPECT_TRUE(thirdGeneration.HasKey("d"));
EXPECT_EQ("d", thirdGeneration.Get<std::string>("d"));
EXPECT_TRUE(thirdGeneration.HasKey("e"));
EXPECT_EQ("e", thirdGeneration.Get<std::string>("e"));
EXPECT_TRUE(thirdGeneration.HasKey(a));
EXPECT_EQ("a", thirdGeneration.Get<std::string>(a));
EXPECT_TRUE(thirdGeneration.HasKey(b));
EXPECT_EQ("b", thirdGeneration.Get<std::string>(b));
EXPECT_TRUE(thirdGeneration.HasKey(c));
EXPECT_EQ("c", thirdGeneration.Get<std::string>(c));
EXPECT_TRUE(thirdGeneration.HasKey(d));
EXPECT_EQ("d", thirdGeneration.Get<std::string>(d));
EXPECT_TRUE(thirdGeneration.HasKey(e));
EXPECT_EQ("e", thirdGeneration.Get<std::string>(e));
}
Context::Key const GlobalKey1;
Context::Key const GlobalKey2;
namespace {
Context::Key const UnnamedNamespaceKey1;
Context::Key const UnnamedNamespaceKey2;
} // namespace
TEST(Context, KeyComparison)
{
EXPECT_EQ(GlobalKey1, GlobalKey1);
EXPECT_EQ(GlobalKey2, GlobalKey2);
EXPECT_NE(GlobalKey1, GlobalKey2);
EXPECT_NE(GlobalKey2, GlobalKey1);
EXPECT_EQ(UnnamedNamespaceKey1, UnnamedNamespaceKey1);
EXPECT_EQ(UnnamedNamespaceKey2, UnnamedNamespaceKey2);
EXPECT_NE(UnnamedNamespaceKey1, UnnamedNamespaceKey2);
EXPECT_NE(UnnamedNamespaceKey2, UnnamedNamespaceKey1);
Context::Key const localKey1;
Context::Key const localKey2;
EXPECT_EQ(localKey1, localKey1);
EXPECT_EQ(localKey2, localKey2);
EXPECT_NE(localKey1, localKey2);
EXPECT_NE(localKey2, localKey1);
Context::Key const localKey1Copy = localKey1;
Context::Key const localKey2Copy = localKey2;
EXPECT_EQ(localKey1Copy, localKey1Copy);
EXPECT_EQ(localKey2Copy, localKey2Copy);
EXPECT_NE(localKey1Copy, localKey2Copy);
EXPECT_NE(localKey2Copy, localKey1Copy);
EXPECT_EQ(localKey1, localKey1Copy);
EXPECT_EQ(localKey2, localKey2Copy);
EXPECT_EQ(localKey1Copy, localKey1);
EXPECT_EQ(localKey2Copy, localKey2);
EXPECT_NE(localKey1, localKey2Copy);
EXPECT_NE(localKey2, localKey1Copy);
EXPECT_NE(localKey1Copy, localKey2);
EXPECT_NE(localKey2Copy, localKey1);
}

View File

@ -24,12 +24,7 @@ TEST(Context, ApplicationContext)
{
Context appContext = Context::GetApplicationContext();
EXPECT_FALSE(appContext.HasKey("Key"));
EXPECT_FALSE(appContext.HasKey("key"));
EXPECT_FALSE(appContext.HasKey("Value"));
EXPECT_FALSE(appContext.HasKey("value"));
EXPECT_FALSE(appContext.HasKey("1"));
EXPECT_FALSE(appContext.HasKey(""));
EXPECT_FALSE(appContext.HasKey(Context::Key()));
auto duration = std::chrono::milliseconds(250);
EXPECT_FALSE(appContext.IsCancelled());

View File

@ -47,6 +47,8 @@ struct TestRetryPolicySharedState : public Azure::Core::Http::Policies::HttpPoli
}
};
Azure::Core::Context::Key const TheKey;
struct TestContextTreeIntegrity : public Azure::Core::Http::Policies::HttpPolicy
{
std::unique_ptr<HttpPolicy> Clone() const override
@ -59,10 +61,10 @@ struct TestContextTreeIntegrity : public Azure::Core::Http::Policies::HttpPolicy
Azure::Core::Http::Policies::NextHttpPolicy nextHttpPolicy,
Azure::Core::Context const& ctx) const override
{
EXPECT_TRUE(ctx.HasKey("TheKey"));
if (ctx.HasKey("TheKey"))
EXPECT_TRUE(ctx.HasKey(TheKey));
if (ctx.HasKey(TheKey))
{
auto value = ctx.Get<std::string>("TheKey");
auto value = ctx.Get<std::string>(TheKey);
EXPECT_EQ("TheValue", value);
}
return nextHttpPolicy.Send(request, ctx);
@ -223,6 +225,6 @@ TEST(Policy, RetryPolicyKeepContext)
HttpPipeline pipeline(policies);
Request request(HttpMethod::Get, Url("url"));
auto withValueContext
= Context::GetApplicationContext().WithValue("TheKey", std::string("TheValue"));
= Context::GetApplicationContext().WithValue(TheKey, std::string("TheValue"));
pipeline.Send(request, withValueContext);
}

View File

@ -8,10 +8,11 @@
#include <azure/core/http/policies/policy.hpp>
#include "azure/storage/common/dll_import_export.hpp"
namespace Azure { namespace Storage { namespace _internal {
static constexpr const char* SecondaryHostReplicaStatusKey
= "AzureSdkStorageSecondaryHostReplicaStatusKey";
AZ_STORAGE_COMMON_DLLEXPORT extern const Azure::Core::Context::Key SecondaryHostReplicaStatusKey;
inline Azure::Core::Context WithReplicaStatus(const Azure::Core::Context& context)
{

View File

@ -5,6 +5,8 @@
namespace Azure { namespace Storage { namespace _internal {
Azure::Core::Context::Key const SecondaryHostReplicaStatusKey;
std::unique_ptr<Azure::Core::Http::RawResponse> StorageSwitchToSecondaryPolicy::Send(
Azure::Core::Http::Request& request,
Azure::Core::Http::Policies::NextHttpPolicy nextHttpPolicy,