Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1640)

Unified Diff: base/metrics/persistent_memory_allocator.cc

Issue 1803253002: Improved iterator for persistent memory allocator. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@refactor-hp
Patch Set: rebased Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/metrics/persistent_memory_allocator.h ('k') | base/metrics/persistent_memory_allocator_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/metrics/persistent_memory_allocator.cc
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index 1d5df333d04e68d0e9ccacb819a8a1afa79985fd..6edbc2fee74d7660fe8eb5b19ad53c5321c650f9 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -45,12 +45,12 @@ enum : int {
};
bool CheckFlag(const volatile std::atomic<uint32_t>* flags, int flag) {
- uint32_t loaded_flags = flags->load();
+ uint32_t loaded_flags = flags->load(std::memory_order_relaxed);
return (loaded_flags & flag) != 0;
}
void SetFlag(volatile std::atomic<uint32_t>* flags, int flag) {
- uint32_t loaded_flags = flags->load();
+ uint32_t loaded_flags = flags->load(std::memory_order_relaxed);
for (;;) {
uint32_t new_flags = (loaded_flags & ~flag) | flag;
// In the failue case, actual "flags" value stored in loaded_flags.
@@ -119,6 +119,115 @@ const PersistentMemoryAllocator::Reference
const PersistentMemoryAllocator::Reference
PersistentMemoryAllocator::kReferenceNull = 0;
+PersistentMemoryAllocator::Iterator::Iterator(
+ const PersistentMemoryAllocator* allocator)
+ : allocator_(allocator), last_record_(kReferenceQueue), record_count_(0) {}
+
+PersistentMemoryAllocator::Iterator::Iterator(
+ const PersistentMemoryAllocator* allocator,
+ Reference starting_after)
+ : allocator_(allocator), last_record_(starting_after), record_count_(0) {
+ // Ensure that the starting point is a valid, iterable block (meaning it can
+ // be read and has a non-zero "next" pointer).
+ const volatile BlockHeader* block =
+ allocator_->GetBlock(starting_after, 0, 0, false, false);
+ if (!block || block->next.load(std::memory_order_relaxed) == 0) {
+ NOTREACHED();
+ last_record_.store(kReferenceQueue, std::memory_order_release);
+ }
+}
+
+PersistentMemoryAllocator::Reference
+PersistentMemoryAllocator::Iterator::GetNext(uint32_t* type_return) {
+ // Make a copy of the existing count of found-records, acquiring all changes
+ // made to the allocator, notably "freeptr" (see comment in loop for why
+ // the load of that value cannot be moved above here) that occurred during
+ // any previous runs of this method, including those by parallel threads
+ // that interrupted it. It pairs with the Release at the end of this method.
+ //
+ // Otherwise, if the compiler were to arrange the two loads such that
+ // "count" was fetched _after_ "freeptr" then it would be possible for
+ // this thread to be interrupted between them and other threads perform
+ // multiple allocations, make-iterables, and iterations (with the included
+ // increment of |record_count_|) culminating in the check at the bottom
+ // mistakenly determining that a loop exists. Isn't this stuff fun?
+ uint32_t count = record_count_.load(std::memory_order_acquire);
+
+ Reference last = last_record_.load(std::memory_order_acquire);
+ Reference next;
+ while (true) {
+ const volatile BlockHeader* block =
+ allocator_->GetBlock(last, 0, 0, true, false);
+ if (!block) // Invalid iterator state.
+ return kReferenceNull;
+
+ // The compiler and CPU can freely reorder all memory accesses on which
+ // there are no dependencies. It could, for example, move the load of
+ // "freeptr" to above this point because there are no explicit dependencies
+ // between it and "next". If it did, however, then another block could
+ // be queued after that but before the following load meaning there is
+ // one more queued block than the future "detect loop by having more
+ // blocks that could fit before freeptr" will allow.
+ //
+ // By "acquiring" the "next" value here, it's synchronized to the enqueue
+ // of the node which in turn is synchronized to the allocation (which sets
+ // freeptr). Thus, the scenario above cannot happen.
+ next = block->next.load(std::memory_order_acquire);
+ if (next == kReferenceQueue) // No next allocation in queue.
+ return kReferenceNull;
+ block = allocator_->GetBlock(next, 0, 0, false, false);
+ if (!block) { // Memory is corrupt.
+ allocator_->SetCorrupt();
+ return kReferenceNull;
+ }
+
+ // Update the "last_record" pointer to be the reference being returned.
+ // If it fails then another thread has already iterated past it so loop
+ // again. Failing will also load the existing value into "last" so there
+ // is no need to do another such load when the while-loop restarts. A
+ // "strong" compare-exchange is used because failing unnecessarily would
+ // mean repeating some fairly costly validations above.
+ if (last_record_.compare_exchange_strong(last, next)) {
+ *type_return = block->type_id;
+ break;
+ }
+ }
+
+ // Memory corruption could cause a loop in the list. Such must be detected
+ // so as to not cause an infinite loop in the caller. This is done by simply
+ // making sure it doesn't iterate more times than the absolute maximum
+ // number of allocations that could have been made. Callers are likely
+ // to loop multiple times before it is detected but at least it stops.
+ const uint32_t freeptr = std::min(
+ allocator_->shared_meta()->freeptr.load(std::memory_order_relaxed),
+ allocator_->mem_size_);
+ const uint32_t max_records =
+ freeptr / (sizeof(BlockHeader) + kAllocAlignment);
+ if (count > max_records) {
+ allocator_->SetCorrupt();
+ return kReferenceNull;
+ }
+
+ // Increment the count and release the changes made above. It pairs with
+ // the Acquire at the top of this method. Note that this operation is not
+ // strictly synchonized with fetching of the object to return, which would
+ // have to be done inside the loop and is somewhat complicated to achieve.
+ // It does not matter if it falls behind temporarily so long as it never
+ // gets ahead.
+ record_count_.fetch_add(1, std::memory_order_release);
+ return next;
+}
+
+PersistentMemoryAllocator::Reference
+PersistentMemoryAllocator::Iterator::GetNextOfType(uint32_t type_match) {
+ Reference ref;
+ uint32_t type_found;
+ while ((ref = GetNext(&type_found)) != 0) {
+ if (type_found == type_match)
+ return ref;
+ }
+ return kReferenceNull;
+}
// static
bool PersistentMemoryAllocator::IsMemoryAcceptable(const void* base,
@@ -177,13 +286,13 @@ PersistentMemoryAllocator::PersistentMemoryAllocator(
if (shared_meta()->cookie != 0 ||
shared_meta()->size != 0 ||
shared_meta()->version != 0 ||
- shared_meta()->freeptr.load() != 0 ||
- shared_meta()->flags.load() != 0 ||
+ shared_meta()->freeptr.load(std::memory_order_relaxed) != 0 ||
+ shared_meta()->flags.load(std::memory_order_relaxed) != 0 ||
shared_meta()->id != 0 ||
shared_meta()->name != 0 ||
shared_meta()->tailptr != 0 ||
shared_meta()->queue.cookie != 0 ||
- shared_meta()->queue.next.load() != 0 ||
+ shared_meta()->queue.next.load(std::memory_order_relaxed) != 0 ||
first_block->size != 0 ||
first_block->cookie != 0 ||
first_block->type_id != 0 ||
@@ -199,13 +308,14 @@ PersistentMemoryAllocator::PersistentMemoryAllocator(
shared_meta()->page_size = mem_page_;
shared_meta()->version = kGlobalVersion;
shared_meta()->id = id;
- shared_meta()->freeptr.store(sizeof(SharedMetadata));
+ shared_meta()->freeptr.store(sizeof(SharedMetadata),
+ std::memory_order_release);
// Set up the queue of iterable allocations.
shared_meta()->queue.size = sizeof(BlockHeader);
shared_meta()->queue.cookie = kBlockCookieQueue;
- shared_meta()->queue.next.store(kReferenceQueue);
- shared_meta()->tailptr.store(kReferenceQueue);
+ shared_meta()->queue.next.store(kReferenceQueue, std::memory_order_release);
+ shared_meta()->tailptr.store(kReferenceQueue, std::memory_order_release);
// Allocate space for the name so other processes can learn it.
if (!name.empty()) {
@@ -218,10 +328,10 @@ PersistentMemoryAllocator::PersistentMemoryAllocator(
} else {
if (shared_meta()->size == 0 ||
shared_meta()->version == 0 ||
- shared_meta()->freeptr.load() == 0 ||
+ shared_meta()->freeptr.load(std::memory_order_relaxed) == 0 ||
shared_meta()->tailptr == 0 ||
shared_meta()->queue.cookie == 0 ||
- shared_meta()->queue.next.load() == 0) {
+ shared_meta()->queue.next.load(std::memory_order_relaxed) == 0) {
SetCorrupt();
}
if (!readonly) {
@@ -281,7 +391,8 @@ void PersistentMemoryAllocator::CreateTrackingHistograms(
}
size_t PersistentMemoryAllocator::used() const {
- return std::min(shared_meta()->freeptr.load(), mem_size_);
+ return std::min(shared_meta()->freeptr.load(std::memory_order_relaxed),
+ mem_size_);
}
size_t PersistentMemoryAllocator::GetAllocSize(Reference ref) const {
@@ -354,7 +465,8 @@ PersistentMemoryAllocator::Reference PersistentMemoryAllocator::AllocateImpl(
// the code below but recognize that any failed compare-exchange operation
// involving it will cause it to be loaded with a more recent value. The
// code should either exit or restart the loop in that case.
- /* const */ uint32_t freeptr = shared_meta()->freeptr.load();
+ /* const */ uint32_t freeptr =
+ shared_meta()->freeptr.load(std::memory_order_acquire);
// Allocation is lockless so we do all our caculation and then, if saving
// indicates a change has occurred since we started, scrap everything and
@@ -424,7 +536,7 @@ PersistentMemoryAllocator::Reference PersistentMemoryAllocator::AllocateImpl(
if (block->size != 0 ||
block->cookie != kBlockCookieFree ||
block->type_id != 0 ||
- block->next.load() != 0) {
+ block->next.load(std::memory_order_relaxed) != 0) {
SetCorrupt();
return kReferenceNull;
}
@@ -437,8 +549,9 @@ PersistentMemoryAllocator::Reference PersistentMemoryAllocator::AllocateImpl(
}
void PersistentMemoryAllocator::GetMemoryInfo(MemoryInfo* meminfo) const {
- uint32_t remaining = std::max(mem_size_ - shared_meta()->freeptr.load(),
- (uint32_t)sizeof(BlockHeader));
+ uint32_t remaining = std::max(
+ mem_size_ - shared_meta()->freeptr.load(std::memory_order_relaxed),
+ (uint32_t)sizeof(BlockHeader));
meminfo->total = mem_size_;
meminfo->free = IsCorrupt() ? 0 : remaining - sizeof(BlockHeader);
}
@@ -501,68 +614,6 @@ void PersistentMemoryAllocator::MakeIterable(Reference ref) {
}
}
-void PersistentMemoryAllocator::CreateIterator(Iterator* state,
- Reference starting_after) const {
- if (starting_after) {
- // Ensure that the starting point is a valid, iterable block.
- const volatile BlockHeader* block =
- GetBlock(starting_after, 0, 0, false, false);
- if (!block || !block->next.load()) {
- NOTREACHED();
- starting_after = kReferenceQueue;
- }
- } else {
- // A zero beginning is really the Queue reference.
- starting_after = kReferenceQueue;
- }
-
- state->last = starting_after;
- state->niter = 0;
-}
-
-PersistentMemoryAllocator::Reference PersistentMemoryAllocator::GetNextIterable(
- Iterator* state,
- uint32_t* type_id) const {
- const volatile BlockHeader* block = GetBlock(state->last, 0, 0, true, false);
- if (!block) // invalid iterator state
- return kReferenceNull;
-
- // The compiler and CPU can freely reorder all memory accesses on which
- // there are no dependencies. It could, for example, move the load of
- // "freeptr" above this point because there are no explicit dependencies
- // between it and "next". If it did, however, then another block could
- // be queued after that but before the following load meaning there is
- // one more queued block than the future "detect loop by having more
- // blocks that could fit before freeptr" will allow.
- //
- // By "acquiring" the "next" value here, it's synchronized to the enqueue
- // of the node which in turn is synchronized to the allocation (which sets
- // freeptr). Thus, the scenario above cannot happen.
- uint32_t next = block->next.load(std::memory_order_acquire);
- block = GetBlock(next, 0, 0, false, false);
- if (!block) // no next allocation in queue
- return kReferenceNull;
-
- // Memory corruption could cause a loop in the list. We need to detect
- // that so as to not cause an infinite loop in the caller. We do this
- // simply by making sure we don't iterate more than the absolute maximum
- // number of allocations that could have been made. Callers are likely
- // to loop multiple times before it is detected but at least it stops.
- uint32_t freeptr = std::min(
- shared_meta()->freeptr.load(std::memory_order_acquire),
- mem_size_);
- if (state->niter > freeptr / (sizeof(BlockHeader) + kAllocAlignment)) {
- SetCorrupt();
- return kReferenceNull;
- }
-
- state->last = next;
- state->niter++;
- *type_id = block->type_id;
-
- return next;
-}
-
// The "corrupted" state is held both locally and globally (shared). The
// shared flag can't be trusted since a malicious actor could overwrite it.
// Because corruption can be detected during read-only operations such as
@@ -571,7 +622,8 @@ PersistentMemoryAllocator::Reference PersistentMemoryAllocator::GetNextIterable(
// maybe even the shared flag if the underlying data isn't actually read-only.
void PersistentMemoryAllocator::SetCorrupt() const {
LOG(ERROR) << "Corruption detected in shared-memory segment.";
- const_cast<std::atomic<bool>*>(&corrupt_)->store(true);
+ const_cast<std::atomic<bool>*>(&corrupt_)->store(true,
+ std::memory_order_relaxed);
if (!readonly_) {
SetFlag(const_cast<volatile std::atomic<uint32_t>*>(&shared_meta()->flags),
kFlagCorrupt);
@@ -579,7 +631,8 @@ void PersistentMemoryAllocator::SetCorrupt() const {
}
bool PersistentMemoryAllocator::IsCorrupt() const {
- if (corrupt_.load() || CheckFlag(&shared_meta()->flags, kFlagCorrupt)) {
+ if (corrupt_.load(std::memory_order_relaxed) ||
+ CheckFlag(&shared_meta()->flags, kFlagCorrupt)) {
SetCorrupt(); // Make sure all indicators are set.
return true;
}
@@ -610,13 +663,16 @@ PersistentMemoryAllocator::GetBlock(Reference ref, uint32_t type_id,
// Validation of referenced block-header.
if (!free_ok) {
- uint32_t freeptr = shared_meta()->freeptr.load();
+ uint32_t freeptr = std::min(
+ shared_meta()->freeptr.load(std::memory_order_relaxed), mem_size_);
if (ref + size > freeptr)
return nullptr;
const volatile BlockHeader* const block =
reinterpret_cast<volatile BlockHeader*>(mem_base_ + ref);
if (block->size < size)
return nullptr;
+ if (ref + block->size > freeptr)
+ return nullptr;
if (ref != kReferenceQueue && block->cookie != kBlockCookieAllocated)
return nullptr;
if (type_id != 0 && block->type_id != type_id)
« no previous file with comments | « base/metrics/persistent_memory_allocator.h ('k') | base/metrics/persistent_memory_allocator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698