Fix ChangeType() to do thread-safe memory clear.
This is important because trying to clear the memory after changing
the type can lead to data races where another thread finds the block
using the new type and tries to use it while the clearing of memory
is still going on.
The code to do this already existed as part of the "object" interface.
This just moves into the lower-level ChangeType method.
Also, clean up the object interface. All of the methods have been
grouped together and their names changed to match those of standard
C++ objects. But other than the memset being removed (to ChangeType),
their operation is unchanged.
BUG=546019
Review-Url: https://codereview.chromium.org/2685723003
Cr-Commit-Position: refs/heads/master@{#449399}
diff --git a/base/debug/activity_tracker.cc b/base/debug/activity_tracker.cc
index ec2e13b..efdf8c8 100644
--- a/base/debug/activity_tracker.cc
+++ b/base/debug/activity_tracker.cc
@@ -121,8 +121,9 @@
Reference cached = cache_values_[--cache_used_];
// Change the type of the cached object to the proper type and return it.
// If the type-change fails that means another thread has taken this from
- // under us (via the search below) so ignore it and keep trying.
- if (allocator_->ChangeType(cached, object_type_, object_free_type_))
+ // under us (via the search below) so ignore it and keep trying. Don't
+ // clear the memory because that was done when the type was made "free".
+ if (allocator_->ChangeType(cached, object_type_, object_free_type_, false))
return cached;
}
@@ -140,7 +141,7 @@
// Found a free object. Change it to the proper type and return it. If
// the type-change fails that means another thread has taken this from
// under us so ignore it and keep trying.
- if (allocator_->ChangeType(found, object_type_, object_free_type_))
+ if (allocator_->ChangeType(found, object_type_, object_free_type_, false))
return found;
}
if (found == last) {
@@ -161,14 +162,9 @@
}
void ActivityTrackerMemoryAllocator::ReleaseObjectReference(Reference ref) {
- // Zero the memory so that it is ready for immediate use if needed later.
- char* mem_base = allocator_->GetAsArray<char>(
- ref, object_type_, PersistentMemoryAllocator::kSizeAny);
- DCHECK(mem_base);
- memset(mem_base, 0, object_size_);
-
// Mark object as free.
- bool success = allocator_->ChangeType(ref, object_free_type_, object_type_);
+ bool success = allocator_->ChangeType(ref, object_free_type_, object_type_,
+ /*clear=*/true);
DCHECK(success);
// Add this reference to our "free" cache if there is space. If not, the type
@@ -1217,8 +1213,7 @@
}
size_t required_size = ModuleInfoRecord::EncodedSize(info);
- ModuleInfoRecord* record =
- allocator_->AllocateObject<ModuleInfoRecord>(required_size);
+ ModuleInfoRecord* record = allocator_->New<ModuleInfoRecord>(required_size);
if (!record)
return;
diff --git a/base/feature_list.cc b/base/feature_list.cc
index 234d5be..353136c 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -165,7 +165,7 @@
pickle.WriteString(override.second.field_trial->trial_name());
size_t total_size = sizeof(FeatureEntry) + pickle.size();
- FeatureEntry* entry = allocator->AllocateObject<FeatureEntry>(total_size);
+ FeatureEntry* entry = allocator->New<FeatureEntry>(total_size);
if (!entry)
return;
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index d16d79f..6b38d55 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -1078,7 +1078,7 @@
pickle.WriteString(group_name);
size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size();
FieldTrial::FieldTrialEntry* new_entry =
- allocator->AllocateObject<FieldTrial::FieldTrialEntry>(total_size);
+ allocator->New<FieldTrial::FieldTrialEntry>(total_size);
subtle::NoBarrier_Store(&new_entry->activated,
subtle::NoBarrier_Load(&prev_entry->activated));
new_entry->pickle_size = pickle.size();
@@ -1098,7 +1098,8 @@
// Mark the existing entry as unused.
allocator->ChangeType(prev_ref, 0,
- FieldTrial::FieldTrialEntry::kPersistentTypeId);
+ FieldTrial::FieldTrialEntry::kPersistentTypeId,
+ /*clear=*/false);
}
for (const auto& ref : new_refs) {
diff --git a/base/metrics/persistent_histogram_allocator.cc b/base/metrics/persistent_histogram_allocator.cc
index 011d883..0b4a12d 100644
--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -322,7 +322,7 @@
// confusion by another process trying to read it. It will be corrected
// once histogram construction is complete.
PersistentHistogramData* histogram_data =
- memory_allocator_->AllocateObject<PersistentHistogramData>(
+ memory_allocator_->New<PersistentHistogramData>(
offsetof(PersistentHistogramData, name) + name.length() + 1);
if (histogram_data) {
memcpy(histogram_data->name, name.c_str(), name.size() + 1);
@@ -428,7 +428,8 @@
// be created. The allocator does not support releasing the acquired memory
// so just change the type to be empty.
memory_allocator_->ChangeType(ref, 0,
- PersistentHistogramData::kPersistentTypeId);
+ PersistentHistogramData::kPersistentTypeId,
+ /*clear=*/false);
}
}
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index ba0ef2a1..5ac7d56 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -511,16 +511,50 @@
bool PersistentMemoryAllocator::ChangeType(Reference ref,
uint32_t to_type_id,
- uint32_t from_type_id) {
+ uint32_t from_type_id,
+ bool clear) {
DCHECK(!readonly_);
volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
if (!block)
return false;
- // This is a "strong" exchange because there is no loop that can retry in
- // the wake of spurious failures possible with "weak" exchanges. Make this
- // an "acquire-release" so no memory accesses can be reordered either before
- // or after since changes based on type could happen on either side.
+ // "Strong" exchanges are used below because there is no loop that can retry
+ // in the wake of spurious failures possible with "weak" exchanges. It is,
+ // in aggregate, an "acquire-release" operation so no memory accesses can be
+ // reordered either before or after this method (since changes based on type
+ // could happen on either side).
+
+ if (clear) {
+ // If clearing the memory, first change it to the "transitioning" type so
+ // there can be no confusion by other threads. After the memory is cleared,
+ // it can be changed to its final type.
+ if (!block->type_id.compare_exchange_strong(
+ from_type_id, kTypeIdTransitioning, std::memory_order_acquire,
+ std::memory_order_acquire)) {
+ // Existing type wasn't what was expected: fail (with no changes)
+ return false;
+ }
+
+ // Clear the memory while the type doesn't match either "from" or "to".
+ memset(const_cast<char*>(reinterpret_cast<volatile char*>(block) +
+ sizeof(BlockHeader)),
+ 0, block->size - sizeof(BlockHeader));
+
+ // If the destination type is "transitioning" then skip the final exchange.
+ if (to_type_id == kTypeIdTransitioning)
+ return true;
+
+ // Finish the change to the desired type.
+ from_type_id = kTypeIdTransitioning; // Exchange needs modifiable original.
+ bool success = block->type_id.compare_exchange_strong(
+ from_type_id, to_type_id, std::memory_order_release,
+ std::memory_order_relaxed);
+ DCHECK(success); // Should never fail.
+ return success;
+ }
+
+ // One step change to the new type. Will return false if the existing value
+ // doesn't match what is expected.
return block->type_id.compare_exchange_strong(from_type_id, to_type_id,
std::memory_order_acq_rel,
std::memory_order_acquire);
diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h
index 9fdef019..7fd96a4 100644
--- a/base/metrics/persistent_memory_allocator.h
+++ b/base/metrics/persistent_memory_allocator.h
@@ -51,7 +51,7 @@
//
// OBJECTS: Although the allocator can be used in a "malloc" sense, fetching
// character arrays and manipulating that memory manually, the better way is
-// generally to use the "Object" methods to create and manage allocations. In
+// generally to use the "object" methods to create and manage allocations. In
// this way the sizing, type-checking, and construction are all automatic. For
// this to work, however, every type of stored object must define two public
// "constexpr" values, kPersistentTypeId and kExpectedInstanceSize, as such:
@@ -83,11 +83,10 @@
// verify that the structure is compatible between both 32-bit and 64-bit
// versions of the code.
//
-// Using AllocateObject (and ChangeObject) will zero the memory and then call
-// the default constructor for the object. Given that objects are persistent,
-// no destructor is ever called automatically though a caller can explicitly
-// call DeleteObject to destruct it and change the type to something indicating
-// it is no longer in use.
+// Using New manages the memory and then calls the default constructor for the
+// object. Given that objects are persistent, no destructor is ever called
+// automatically though a caller can explicitly call Delete to destruct it and
+// change the type to something indicating it is no longer in use.
//
// Though persistent memory segments are transferrable between programs built
// for different natural word widths, they CANNOT be exchanged between CPUs
@@ -215,10 +214,12 @@
enum : Reference {
// A common "null" reference value.
kReferenceNull = 0,
+ };
+ enum : uint32_t {
// A value indicating that the type is in transition. Work is being done
// on the contents to prepare it for a new type to come.
- kReferenceTransitioning = 0xFFFFFFFF,
+ kTypeIdTransitioning = 0xFFFFFFFF,
};
enum : size_t {
@@ -385,12 +386,6 @@
// result will be returned.
Reference GetAsReference(const void* memory, uint32_t type_id) const;
- // As above but works with objects allocated from persistent memory.
- template <typename T>
- Reference GetAsReference(const T* obj) const {
- return GetAsReference(obj, T::kPersistentTypeId);
- }
-
// Get the number of bytes allocated to a block. This is useful when storing
// arrays in order to validate the ending boundary. The returned value will
// include any padding added to achieve the required alignment and so could
@@ -402,104 +397,21 @@
// even though the memory stays valid and allocated. Changing the type is
// an atomic compare/exchange and so requires knowing the existing value.
// It will return false if the existing type is not what is expected.
+ //
// Changing the type doesn't mean the data is compatible with the new type.
- // It will likely be necessary to clear or reconstruct the type before it
- // can be used. Changing the type WILL NOT invalidate existing pointers to
- // the data, either in this process or others, so changing the data structure
- // could have unpredicatable results. USE WITH CARE!
+ // Passing true for |clear| will zero the memory after the type has been
+ // changed away from |from_type_id| but before it becomes |to_type_id| meaning
+ // that it is done in a manner that is thread-safe.
+ //
+ // It will likely be necessary to reconstruct the type before it can be used.
+ // Changing the type WILL NOT invalidate existing pointers to the data, either
+ // in this process or others, so changing the data structure could have
+ // unpredicatable results. USE WITH CARE!
uint32_t GetType(Reference ref) const;
- bool ChangeType(Reference ref, uint32_t to_type_id, uint32_t from_type_id);
-
- // Like ChangeType() but gets the "to" type from the object type, clears
- // the memory, and constructs a new object of the desired type just as
- // though it was fresh from AllocateObject<>(). The old type simply ceases
- // to exist; no destructor is called for it. Calling this will not invalidate
- // existing pointers to the object, either in this process or others, so
- // changing the object could have unpredictable results. USE WITH CARE!
- template <typename T>
- T* ChangeObject(Reference ref, uint32_t from_type_id) {
- DCHECK_LE(sizeof(T), GetAllocSize(ref)) << "alloc not big enough for obj";
- // Make sure the memory is appropriate. This won't be used until after
- // the type is changed but checking first avoids the possibility of having
- // to change the type back.
- void* mem = const_cast<void*>(GetBlockData(ref, 0, sizeof(T)));
- if (!mem)
- return nullptr;
- // Ensure the allocator's internal alignment is sufficient for this object.
- // This protects against coding errors in the allocator.
- DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (ALIGNOF(T) - 1));
- // First change the type to "transitioning" so that there is no race
- // condition with the clearing and construction of the object should
- // another thread be simultaneously iterating over data. This will
- // "acquire" the memory so no changes get reordered before it.
- if (!ChangeType(ref, kReferenceTransitioning, from_type_id))
- return nullptr;
- // Clear the memory so that the property of all memory being zero after an
- // allocation also applies here.
- memset(mem, 0, GetAllocSize(ref));
- // Construct an object of the desired type on this memory, just as if
- // AllocateObject had been called to create it.
- T* obj = new (mem) T();
- // Finally change the type to the desired one. This will "release" all of
- // the changes above and so provide a consistent view to other threads.
- bool success =
- ChangeType(ref, T::kPersistentTypeId, kReferenceTransitioning);
- DCHECK(success);
- return obj;
- }
-
- // Reserve space in the memory segment of the desired |size| and |type_id|.
- // A return value of zero indicates the allocation failed, otherwise the
- // returned reference can be used by any process to get a real pointer via
- // the GetAsObject() call.
- Reference Allocate(size_t size, uint32_t type_id);
-
- // Allocate and construct an object in persistent memory. The type must have
- // both (size_t) kExpectedInstanceSize and (uint32_t) kPersistentTypeId
- // static constexpr fields that are used to ensure compatibility between
- // software versions. An optional size parameter can be specified to force
- // the allocation to be bigger than the size of the object; this is useful
- // when the last field is actually variable length.
- template <typename T>
- T* AllocateObject(size_t size) {
- if (size < sizeof(T))
- size = sizeof(T);
- Reference ref = Allocate(size, T::kPersistentTypeId);
- void* mem =
- const_cast<void*>(GetBlockData(ref, T::kPersistentTypeId, size));
- if (!mem)
- return nullptr;
- DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (ALIGNOF(T) - 1));
- return new (mem) T();
- }
- template <typename T>
- T* AllocateObject() {
- return AllocateObject<T>(sizeof(T));
- }
-
- // Deletes an object by destructing it and then changing the type to a
- // different value (default 0).
- template <typename T>
- void DeleteObject(T* obj, uint32_t new_type) {
- // Get the reference for the object.
- Reference ref = GetAsReference<T>(obj);
- // First change the type to "transitioning" so there is no race condition
- // where another thread could find the object through iteration while it
- // is been destructed. This will "acquire" the memory so no changes get
- // reordered before it. It will fail if |ref| is invalid.
- if (!ChangeType(ref, kReferenceTransitioning, T::kPersistentTypeId))
- return;
- // Destruct the object.
- obj->~T();
- // Finally change the type to the desired value. This will "release" all
- // the changes above.
- bool success = ChangeType(ref, new_type, kReferenceTransitioning);
- DCHECK(success);
- }
- template <typename T>
- void DeleteObject(T* obj) {
- DeleteObject<T>(obj, 0);
- }
+ bool ChangeType(Reference ref,
+ uint32_t to_type_id,
+ uint32_t from_type_id,
+ bool clear);
// Allocated objects can be added to an internal list that can then be
// iterated over by other processes. If an allocated object can be found
@@ -511,12 +423,6 @@
// Changing the type does not alter its "iterable" status.
void MakeIterable(Reference ref);
- // As above but works with an object allocated from persistent memory.
- template <typename T>
- void MakeIterable(const T* obj) {
- MakeIterable(GetAsReference<T>(obj));
- }
-
// Get the information about the amount of free space in the allocator. The
// amount of free space should be treated as approximate due to extras from
// alignment and metadata. Concurrent allocations from other threads will
@@ -541,6 +447,113 @@
// called before such information is to be displayed or uploaded.
void UpdateTrackingHistograms();
+ // While the above works much like malloc & free, these next methods provide
+ // an "object" interface similar to new and delete.
+
+ // Reserve space in the memory segment of the desired |size| and |type_id|.
+ // A return value of zero indicates the allocation failed, otherwise the
+ // returned reference can be used by any process to get a real pointer via
+ // the GetAsObject() or GetAsArray calls.
+ Reference Allocate(size_t size, uint32_t type_id);
+
+ // Allocate and construct an object in persistent memory. The type must have
+ // both (size_t) kExpectedInstanceSize and (uint32_t) kPersistentTypeId
+ // static constexpr fields that are used to ensure compatibility between
+ // software versions. An optional size parameter can be specified to force
+ // the allocation to be bigger than the size of the object; this is useful
+ // when the last field is actually variable length.
+ template <typename T>
+ T* New(size_t size) {
+ if (size < sizeof(T))
+ size = sizeof(T);
+ Reference ref = Allocate(size, T::kPersistentTypeId);
+ void* mem =
+ const_cast<void*>(GetBlockData(ref, T::kPersistentTypeId, size));
+ if (!mem)
+ return nullptr;
+ DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (ALIGNOF(T) - 1));
+ return new (mem) T();
+ }
+ template <typename T>
+ T* New() {
+ return New<T>(sizeof(T));
+ }
+
+ // Similar to New, above, but construct the object out of an existing memory
+ // block and of an expected type. If |clear| is true, memory will be zeroed
+ // before construction. Though this is not standard object behavior, it
+ // is present to match with new allocations that always come from zeroed
+ // memory. Anything previously present simply ceases to exist; no destructor
+ // is called for it so explicitly Delete() the old object first if need be.
+ // Calling this will not invalidate existing pointers to the object, either
+ // in this process or others, so changing the object could have unpredictable
+ // results. USE WITH CARE!
+ template <typename T>
+ T* New(Reference ref, uint32_t from_type_id, bool clear) {
+ DCHECK_LE(sizeof(T), GetAllocSize(ref)) << "alloc not big enough for obj";
+ // Make sure the memory is appropriate. This won't be used until after
+ // the type is changed but checking first avoids the possibility of having
+ // to change the type back.
+ void* mem = const_cast<void*>(GetBlockData(ref, 0, sizeof(T)));
+ if (!mem)
+ return nullptr;
+ // Ensure the allocator's internal alignment is sufficient for this object.
+ // This protects against coding errors in the allocator.
+ DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (ALIGNOF(T) - 1));
+ // Change the type, clearing the memory if so desired. The new type is
+ // "transitioning" so that there is no race condition with the construction
+ // of the object should another thread be simultaneously iterating over
+ // data. This will "acquire" the memory so no changes get reordered before
+ // it.
+ if (!ChangeType(ref, kTypeIdTransitioning, from_type_id, clear))
+ return nullptr;
+ // Construct an object of the desired type on this memory, just as if
+ // New() had been called to create it.
+ T* obj = new (mem) T();
+ // Finally change the type to the desired one. This will "release" all of
+ // the changes above and so provide a consistent view to other threads.
+ bool success =
+ ChangeType(ref, T::kPersistentTypeId, kTypeIdTransitioning, false);
+ DCHECK(success);
+ return obj;
+ }
+
+ // Deletes an object by destructing it and then changing the type to a
+ // different value (default 0).
+ template <typename T>
+ void Delete(T* obj, uint32_t new_type) {
+ // Get the reference for the object.
+ Reference ref = GetAsReference<T>(obj);
+ // First change the type to "transitioning" so there is no race condition
+ // where another thread could find the object through iteration while it
+ // is been destructed. This will "acquire" the memory so no changes get
+ // reordered before it. It will fail if |ref| is invalid.
+ if (!ChangeType(ref, kTypeIdTransitioning, T::kPersistentTypeId, false))
+ return;
+ // Destruct the object.
+ obj->~T();
+ // Finally change the type to the desired value. This will "release" all
+ // the changes above.
+ bool success = ChangeType(ref, new_type, kTypeIdTransitioning, false);
+ DCHECK(success);
+ }
+ template <typename T>
+ void Delete(T* obj) {
+ Delete<T>(obj, 0);
+ }
+
+ // As above but works with objects allocated from persistent memory.
+ template <typename T>
+ Reference GetAsReference(const T* obj) const {
+ return GetAsReference(obj, T::kPersistentTypeId);
+ }
+
+ // As above but works with an object allocated from persistent memory.
+ template <typename T>
+ void MakeIterable(const T* obj) {
+ MakeIterable(GetAsReference<T>(obj));
+ }
+
protected:
enum MemoryType {
MEM_EXTERNAL,
diff --git a/base/metrics/persistent_memory_allocator_unittest.cc b/base/metrics/persistent_memory_allocator_unittest.cc
index f8c35332..ebe63eb45 100644
--- a/base/metrics/persistent_memory_allocator_unittest.cc
+++ b/base/metrics/persistent_memory_allocator_unittest.cc
@@ -112,7 +112,7 @@
// Validate allocation of test object and make sure it can be referenced
// and all metadata looks correct.
- TestObject1* obj1 = allocator_->AllocateObject<TestObject1>();
+ TestObject1* obj1 = allocator_->New<TestObject1>();
ASSERT_TRUE(obj1);
Reference block1 = allocator_->GetAsReference(obj1);
ASSERT_NE(0U, block1);
@@ -152,7 +152,7 @@
// Create second test-object and ensure everything is good and it cannot
// be confused with test-object of another type.
- TestObject2* obj2 = allocator_->AllocateObject<TestObject2>();
+ TestObject2* obj2 = allocator_->New<TestObject2>();
ASSERT_TRUE(obj2);
Reference block2 = allocator_->GetAsReference(obj2);
ASSERT_NE(0U, block2);
@@ -223,9 +223,9 @@
// Check that an object's type can be changed.
EXPECT_EQ(2U, allocator_->GetType(block2));
- allocator_->ChangeType(block2, 3, 2);
+ allocator_->ChangeType(block2, 3, 2, false);
EXPECT_EQ(3U, allocator_->GetType(block2));
- allocator_->ChangeObject<TestObject2>(block2, 3);
+ allocator_->New<TestObject2>(block2, 3, false);
EXPECT_EQ(2U, allocator_->GetType(block2));
// Create second allocator (read/write) using the same memory segment.
@@ -272,7 +272,7 @@
EXPECT_EQ(nullptr, iter1d.GetNextOfObject<TestObject2>());
// Ensure that deleting an object works.
- allocator_->DeleteObject(obj2);
+ allocator_->Delete(obj2);
PersistentMemoryAllocator::Iterator iter1z(allocator_.get());
EXPECT_EQ(nullptr, iter1z.GetNextOfObject<TestObject2>());
}
@@ -587,7 +587,7 @@
r456 = local.Allocate(456, 456);
r789 = local.Allocate(789, 789);
local.MakeIterable(r123);
- local.ChangeType(r456, 654, 456);
+ local.ChangeType(r456, 654, 456, false);
local.MakeIterable(r789);
local.GetMemoryInfo(&meminfo1);
EXPECT_FALSE(local.IsFull());
@@ -656,6 +656,25 @@
shalloc3.MakeIterable(obj);
EXPECT_EQ(obj, iter2.GetNext(&type));
EXPECT_EQ(42U, type);
+
+ // Clear-on-change test.
+ Reference data_ref = shalloc3.Allocate(sizeof(int) * 4, 911);
+ int* data = shalloc3.GetAsArray<int>(data_ref, 911, 4);
+ ASSERT_TRUE(data);
+ data[0] = 0;
+ data[1] = 1;
+ data[2] = 2;
+ data[3] = 3;
+ ASSERT_TRUE(shalloc3.ChangeType(data_ref, 119, 911, false));
+ EXPECT_EQ(0, data[0]);
+ EXPECT_EQ(1, data[1]);
+ EXPECT_EQ(2, data[2]);
+ EXPECT_EQ(3, data[3]);
+ ASSERT_TRUE(shalloc3.ChangeType(data_ref, 191, 119, true));
+ EXPECT_EQ(0, data[0]);
+ EXPECT_EQ(0, data[1]);
+ EXPECT_EQ(0, data[2]);
+ EXPECT_EQ(0, data[3]);
}
@@ -676,7 +695,7 @@
r456 = local.Allocate(456, 456);
r789 = local.Allocate(789, 789);
local.MakeIterable(r123);
- local.ChangeType(r456, 654, 456);
+ local.ChangeType(r456, 654, 456, false);
local.MakeIterable(r789);
local.GetMemoryInfo(&meminfo1);
EXPECT_FALSE(local.IsFull());
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index 131e47b6..51cc0c70 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -165,7 +165,7 @@
PersistentMemoryAllocator* allocator,
uint64_t sample_map_id,
Sample value) {
- SampleRecord* record = allocator->AllocateObject<SampleRecord>();
+ SampleRecord* record = allocator->New<SampleRecord>();
if (!record) {
NOTREACHED() << "full=" << allocator->IsFull()
<< ", corrupt=" << allocator->IsCorrupt();