summaryrefslogtreecommitdiffstats
path: root/sync/syncable
diff options
context:
space:
mode:
authorstanisc <stanisc@chromium.org>2015-06-15 15:01:00 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-15 22:01:26 +0000
commit65c51c30fd4c79384c222fc571913145dbea86f8 (patch)
treea4dce5de77672c8bc1c3a9f644e4a3a2b9b899b4 /sync/syncable
parent36e848dac55dd8b29659f52eeb6ec1bcfd8b6c23 (diff)
downloadchromium_src-65c51c30fd4c79384c222fc571913145dbea86f8.zip
chromium_src-65c51c30fd4c79384c222fc571913145dbea86f8.tar.gz
chromium_src-65c51c30fd4c79384c222fc571913145dbea86f8.tar.bz2
Sync: Initial implementation of EntryProtoFieldPtr.
EntryProtoFieldPtr will be used in EntryKernel to enable sharing of EntitySpecifics and AttachmentMetadata. This patch introduces EntryProtoFieldPtr, the wrapper class for the shareable sync data, and adds a couple of unit tests for it. BUG=499443 Review URL: https://codereview.chromium.org/1170423002 Cr-Commit-Position: refs/heads/master@{#334469}
Diffstat (limited to 'sync/syncable')
-rw-r--r--sync/syncable/proto_value_ptr.h94
-rw-r--r--sync/syncable/proto_value_ptr_unittest.cc175
2 files changed, 269 insertions, 0 deletions
diff --git a/sync/syncable/proto_value_ptr.h b/sync/syncable/proto_value_ptr.h
new file mode 100644
index 0000000..8036ff5
--- /dev/null
+++ b/sync/syncable/proto_value_ptr.h
@@ -0,0 +1,94 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SYNC_SYNCABLE_ENTRY_PROTO_FIELD_PTR_H_
+#define SYNC_SYNCABLE_ENTRY_PROTO_FIELD_PTR_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "sync/protocol/attachments.pb.h"
+#include "sync/protocol/sync.pb.h"
+
+namespace syncer {
+namespace syncable {
+
+// Default traits struct for ProtoValuePtr - adapts a
+// ::google::protobuf::MessageLite derived type to be used with ProtoValuePtr.
+template <typename T>
+struct DefaultProtoValuePtrTraits {
+ // Deep copy the value from |src| to |dest|.
+ static void CopyValue(T* dest, const T& src) { dest->CopyFrom(src); }
+ // True if the |value| is a non-default value.
+ static bool HasValue(const T& value) { return value.ByteSize() > 0; }
+ // Default value for the type.
+ static const T& DefaultValue() { return T::default_instance(); }
+};
+
+// This is a smart pointer to a ::google::protobuf::MessageLite derived type
+// that implements immutable, shareable, copy-on-write semantics.
+//
+// Additionally this class helps to avoid storing multiple copies of default
+// instances of the wrapped type.
+//
+// Copying ProtoValuePtr results in ref-counted sharing of the
+// underlying wrapper and the value contained in the wrapper.
+//
+// The public interface includes only immutable access to the wrapped value.
+// The only way to assign a value to ProtoValuePtr is through a
+// private SetValue function which is called from EntryKernel. That results
+// in stopping sharing the previous value and creating a wrapper to the new
+// value.
+template <typename T, typename Traits = DefaultProtoValuePtrTraits<T>>
+class ProtoValuePtr {
+ private:
+ // Immutable shareable ref-counted wrapper that embeds the value.
+ class Wrapper : public base::RefCountedThreadSafe<Wrapper> {
+ public:
+ Wrapper(const T& value) { Traits::CopyValue(&value_, value); }
+ const T& value() const { return value_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<Wrapper>;
+ ~Wrapper() {}
+
+ T value_;
+ };
+
+ ProtoValuePtr() {}
+ ~ProtoValuePtr() {}
+
+ public:
+ const T& value() const {
+ return wrapper_ ? wrapper_->value() : Traits::DefaultValue();
+ }
+
+ const T* operator->() const {
+ const T& wrapped_instance = value();
+ return &wrapped_instance;
+ }
+
+ private:
+ friend struct EntryKernel;
+ FRIEND_TEST_ALL_PREFIXES(ProtoValuePtrTest, BasicTest);
+ FRIEND_TEST_ALL_PREFIXES(ProtoValuePtrTest, SharingTest);
+
+ void set_value(const T& new_value) {
+ if (Traits::HasValue(new_value)) {
+ wrapper_ = new Wrapper(new_value);
+ } else {
+ // Don't store default value.
+ wrapper_ = nullptr;
+ }
+ }
+
+ scoped_refptr<Wrapper> wrapper_;
+};
+
+typedef ProtoValuePtr<sync_pb::EntitySpecifics> EntitySpecificsPtr;
+typedef ProtoValuePtr<sync_pb::AttachmentMetadata> AttachmentMetadataPtr;
+
+} // namespace syncable
+} // namespace syncer
+
+#endif // SYNC_SYNCABLE_ENTRY_PROTO_FIELD_PTR_H_
diff --git a/sync/syncable/proto_value_ptr_unittest.cc b/sync/syncable/proto_value_ptr_unittest.cc
new file mode 100644
index 0000000..27bf540
--- /dev/null
+++ b/sync/syncable/proto_value_ptr_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sync/syncable/proto_value_ptr.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+namespace syncable {
+
+namespace {
+
+// TestValue class is used as a template argument with ProtoValuePtr<T>
+class TestValue {
+ public:
+ TestValue() : value_(0), is_initialized_(false), is_default_(false) {}
+ explicit TestValue(int value)
+ : value_(value), is_initialized_(true), is_default_(false) {}
+
+ ~TestValue() { g_delete_count++; }
+
+ static void ResetCounters() {
+ g_copy_count = 0;
+ g_delete_count = 0;
+ }
+
+ static int copy_count() { return g_copy_count; }
+ static int delete_count() { return g_delete_count; }
+
+ int value() const { return value_; }
+ bool is_initialized() const { return is_initialized_; }
+ bool is_default() const { return is_default_; }
+
+ // TestValue uses the default traits struct with ProtoValuePtr<TestValue>.
+ // The following 3 functions are expected by the traits struct to exist
+ // in this class.
+ void CopyFrom(const TestValue& from) {
+ // Expected to always copy from an initialized instance
+ // to an uninitialized one.
+ // Not expected either value to be default.
+ ASSERT_FALSE(is_initialized());
+ ASSERT_FALSE(is_default());
+ ASSERT_TRUE(from.is_initialized());
+ ASSERT_FALSE(from.is_default());
+ value_ = from.value();
+ is_initialized_ = false;
+ g_copy_count++;
+ }
+
+ int ByteSize() const { return is_initialized() ? sizeof(int) : 0; }
+
+ static const TestValue& default_instance() {
+ static TestValue default_instance;
+ default_instance.is_default_ = true;
+ return default_instance;
+ }
+
+ private:
+ static int g_copy_count;
+ static int g_delete_count;
+
+ int value_;
+ bool is_initialized_;
+ bool is_default_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestValue);
+};
+
+// Static initializers.
+int TestValue::g_copy_count = 0;
+int TestValue::g_delete_count = 0;
+
+} // namespace
+
+typedef ProtoValuePtr<TestValue> TestPtr;
+
+class ProtoValuePtrTest : public testing::Test {
+ public:
+ void SetUp() override { TestValue::ResetCounters(); }
+
+ static bool WrappedValuesAreShared(const TestPtr& ptr1, const TestPtr& ptr2) {
+ const TestValue& wrapped_value_1 = ptr1.value();
+ const TestValue& wrapped_value_2 = ptr2.value();
+ // Compare addresses.
+ return &wrapped_value_1 == &wrapped_value_2;
+ }
+};
+
+TEST_F(ProtoValuePtrTest, BasicTest) {
+ // Basic assignment and default value.
+ TestValue t1(1);
+ {
+ TestPtr ptr1;
+ EXPECT_TRUE(ptr1->is_default());
+
+ ptr1.set_value(t1);
+ EXPECT_FALSE(ptr1->is_default());
+ EXPECT_EQ(1, ptr1->value());
+ }
+
+ EXPECT_EQ(1, TestValue::copy_count());
+ EXPECT_EQ(1, TestValue::delete_count());
+}
+
+TEST_F(ProtoValuePtrTest, SharingTest) {
+ // Sharing between two pointers.
+ TestValue empty;
+ TestValue t2(2);
+ TestValue t3(3);
+ {
+ TestPtr ptr2;
+ TestPtr ptr3;
+
+ EXPECT_TRUE(ptr2->is_default());
+ EXPECT_TRUE(ptr3->is_default());
+ EXPECT_EQ(0, TestValue::copy_count());
+ EXPECT_EQ(0, TestValue::delete_count());
+
+ ptr2.set_value(t2);
+ EXPECT_EQ(1, TestValue::copy_count());
+ EXPECT_EQ(0, TestValue::delete_count());
+
+ ptr3 = ptr2;
+ // Both |ptr2| and |ptr3| now share the same value "2".
+ // No additional copies expected.
+ EXPECT_EQ(1, TestValue::copy_count());
+ EXPECT_EQ(0, TestValue::delete_count());
+ EXPECT_FALSE(ptr3->is_default());
+ EXPECT_EQ(2, ptr3->value());
+ EXPECT_TRUE(WrappedValuesAreShared(ptr2, ptr3));
+
+ // Stop sharing - |ptr2| is "3" and |ptr3| is still "2".
+ ptr2.set_value(t3);
+ EXPECT_FALSE(WrappedValuesAreShared(ptr2, ptr3));
+ EXPECT_EQ(3, ptr2->value());
+ EXPECT_EQ(2, ptr3->value());
+ // No extra copies or deletions expected.
+ EXPECT_EQ(2, TestValue::copy_count());
+ EXPECT_EQ(0, TestValue::delete_count());
+
+ // |ptr3| still has the old value.
+ EXPECT_EQ(2, ptr3->value());
+
+ // Share again. Both values are "3".
+ ptr3 = ptr2;
+ EXPECT_EQ(3, ptr3->value());
+ // This should have resulted in deleting the wrapper for the value "2".
+ EXPECT_EQ(1, TestValue::delete_count());
+ // No extra copies expected.
+ EXPECT_EQ(2, TestValue::copy_count());
+
+ // Set default value to one of the pointers.
+ ptr2.set_value(empty);
+ EXPECT_TRUE(ptr2->is_default());
+ // The other one is still intact.
+ EXPECT_FALSE(ptr3->is_default());
+ EXPECT_EQ(3, ptr3->value());
+ // No extra copies or deletions expected.
+ EXPECT_EQ(1, TestValue::delete_count());
+ EXPECT_EQ(2, TestValue::copy_count());
+
+ // Copy the default value between the pointers.
+ ptr3 = ptr2;
+ EXPECT_TRUE(ptr3->is_default());
+ // The wrapper for "3" is now deleted.
+ EXPECT_EQ(2, TestValue::delete_count());
+ }
+
+ // No extra deletions expected upon leaving the scope.
+ EXPECT_EQ(2, TestValue::delete_count());
+}
+
+} // namespace syncable
+} // namespace syncer