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

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 and fixed up a bit Created 4 years, 9 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
Index: base/metrics/persistent_memory_allocator.cc
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index a1a960c9bbc907601d83c461810d1c60ab0de1c8..ab3fb3476894f05681f7fefd2cb20c1044b6a7f1 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -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() == 0) {
+ NOTREACHED();
+ last_record_ = kReferenceQueue;
Ilya Sherman 2016/03/24 02:03:58 FWIW, NOTREACHED() and DCHECK failures should be u
bcwhite 2016/03/24 14:22:34 In a properly running instance, this should never
+ }
+}
+
+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();
+ 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_acquire),
+ 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;
+}
Ilya Sherman 2016/03/24 02:03:58 Sorry, I don't feel qualified to review this code
bcwhite 2016/03/24 14:22:34 Will do.
+
+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,
@@ -501,68 +610,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
@@ -610,13 +657,15 @@ 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(), 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)

Powered by Google App Engine
This is Rietveld 408576698