Index: base/metrics/persistent_memory_allocator.h |
diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h |
index ab9dd9d5eff2ea66603dd92ece6edbfe5871d5de..fd66da23563b99ed1d17c43d90331b9263785aae 100644 |
--- a/base/metrics/persistent_memory_allocator.h |
+++ b/base/metrics/persistent_memory_allocator.h |
@@ -49,14 +49,50 @@ class SharedMemory; |
// use virtual memory, including memory-mapped files, as backing storage with |
// the OS "pinning" new (zeroed) physical RAM pages only as they are needed. |
// |
-// All persistent memory segments can be freely accessed by builds of different |
-// natural word widths (i.e. 32/64-bit) but users of this module must manually |
-// ensure that the data recorded within are similarly safe. The GetAsObject<>() |
-// methods use the kExpectedInstanceSize attribute of the structs to check this. |
+// 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 |
+// 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: |
// |
-// Memory segments can NOT, however, be exchanged between CPUs of different |
-// endianess. Attempts to do so will simply see the existing data as corrupt |
-// and refuse to access any of it. |
+// struct MyPersistentObjectType { |
+// // SHA1(MyPersistentObjectType): Increment this if structure changes! |
+// static constexpr uint32_t kPersistentTypeId = 0x3E15F6DE + 1; |
+// |
+// // Expected size for 32/64-bit check. Update this if structure changes! |
+// static constexpr size_t kExpectedInstanceSize = 20; |
+// |
+// ... |
+// }; |
+// |
+// kPersistentTypeId: This value is an arbitrary identifier that allows the |
+// identification of these objects in the allocator, including the ability |
+// to find them via iteration. The number is arbitrary but using the first |
+// four bytes of the SHA1 hash of the type name means that there shouldn't |
+// be any conflicts with other types that may also be stored in the memory. |
+// The fully qualified name (e.g. base::debug::MyPersistentObjectType) could |
+// be used to generate the hash if the type name seems common. Use a command |
+// like this to get the hash: echo -n "MyPersistentObjectType" | sha1sum |
+// If the structure layout changes, ALWAYS increment this number so that |
+// newer versions of the code don't try to interpret persistent data written |
+// by older versions with a different layout. |
+// |
+// kExpectedInstanceSize: This value is the hard-coded number that matches |
+// what sizeof(T) would return. By providing it explicitly, the allocator can |
+// 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. |
+// |
+// Though persistent memory segments are transferrable between programs built |
+// for different natural word widths, they CANNOT be exchanged between CPUs |
+// of different endianess. Attempts to do so will simply see the existing data |
+// as corrupt and refuse to access any of it. |
class BASE_EXPORT PersistentMemoryAllocator { |
public: |
typedef uint32_t Reference; |
@@ -114,6 +150,18 @@ class BASE_EXPORT PersistentMemoryAllocator { |
// calls to GetNext() meaning it's possible to completely miss entries. |
Reference GetNextOfType(uint32_t type_match); |
+ // As above but works using object type. |
+ template <typename T> |
+ Reference GetNextOfType() { |
+ return GetNextOfType(T::kPersistentTypeId); |
+ } |
+ |
+ // As above but works using objects and returns null if not found. |
+ template <typename T> |
+ const T* GetNextOfObject() { |
+ return GetAsObject<T>(GetNextOfType<T>()); |
+ } |
+ |
// Converts references to objects. This is a convenience method so that |
// users of the iterator don't need to also have their own pointer to the |
// allocator over which the iterator runs in order to retrieve objects. |
@@ -122,16 +170,29 @@ class BASE_EXPORT PersistentMemoryAllocator { |
// non-const (external) pointer to the same allocator (or use const_cast |
// to remove the qualifier). |
template <typename T> |
- const T* GetAsObject(Reference ref, uint32_t type_id) const { |
- return allocator_->GetAsObject<T>(ref, type_id); |
+ const T* GetAsObject(Reference ref) const { |
+ return allocator_->GetAsObject<T>(ref); |
} |
- // Similar to GetAsObject() but converts references to arrays of objects. |
+ // Similar to GetAsObject() but converts references to arrays of things. |
template <typename T> |
const T* GetAsArray(Reference ref, uint32_t type_id, size_t count) const { |
return allocator_->GetAsArray<T>(ref, type_id, count); |
} |
+ // Convert a generic pointer back into a reference. A null reference will |
+ // be returned if |memory| is not inside the persistent segment or does not |
+ // point to an object of the specified |type_id|. |
+ Reference GetAsReference(const void* memory, uint32_t type_id) const { |
+ return allocator_->GetAsReference(memory, type_id); |
+ } |
+ |
+ // As above but convert an object back into a reference. |
+ template <typename T> |
+ Reference GetAsReference(const T* obj) const { |
+ return allocator_->GetAsReference(obj); |
+ } |
+ |
private: |
// Weak-pointer to memory allocator being iterated over. |
const PersistentMemoryAllocator* allocator_; |
@@ -152,11 +213,12 @@ class BASE_EXPORT PersistentMemoryAllocator { |
}; |
enum : Reference { |
- kReferenceNull = 0 // A common "null" reference value. |
- }; |
+ // A common "null" reference value. |
+ kReferenceNull = 0, |
- enum : uint32_t { |
- kTypeIdAny = 0 // Match any type-id inside GetAsObject(). |
+ // 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, |
}; |
enum : size_t { |
@@ -277,19 +339,20 @@ class BASE_EXPORT PersistentMemoryAllocator { |
// nature of that keyword to the caller. It can add it back, if necessary, |
// based on knowledge of how the allocator is being used. |
template <typename T> |
- T* GetAsObject(Reference ref, uint32_t type_id) { |
- static_assert(std::is_pod<T>::value, "only simple objects"); |
+ T* GetAsObject(Reference ref) { |
+ static_assert(std::is_standard_layout<T>::value, "only standard objects"); |
+ static_assert(!std::is_array<T>::value, "use GetAsArray<>()"); |
static_assert(T::kExpectedInstanceSize == sizeof(T), "inconsistent size"); |
- return const_cast<T*>( |
- reinterpret_cast<volatile T*>(GetBlockData(ref, type_id, sizeof(T)))); |
+ return const_cast<T*>(reinterpret_cast<volatile T*>( |
+ GetBlockData(ref, T::kPersistentTypeId, sizeof(T)))); |
} |
template <typename T> |
- const T* GetAsObject(Reference ref, uint32_t type_id) const { |
- static_assert(std::is_pod<T>::value, "only simple objects"); |
+ const T* GetAsObject(Reference ref) const { |
+ static_assert(std::is_standard_layout<T>::value, "only standard objects"); |
+ static_assert(!std::is_array<T>::value, "use GetAsArray<>()"); |
static_assert(T::kExpectedInstanceSize == sizeof(T), "inconsistent size"); |
- return const_cast<const T*>( |
- reinterpret_cast<const volatile T*>(GetBlockData( |
- ref, type_id, sizeof(T)))); |
+ return const_cast<const T*>(reinterpret_cast<const volatile T*>( |
+ GetBlockData(ref, T::kPersistentTypeId, sizeof(T)))); |
} |
// Like GetAsObject but get an array of simple, fixed-size types. |
@@ -321,6 +384,12 @@ class BASE_EXPORT PersistentMemoryAllocator { |
// 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 |
@@ -332,15 +401,105 @@ class BASE_EXPORT PersistentMemoryAllocator { |
// 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! |
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); |
+ } |
+ |
// 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 |
// another way, such as by having its reference within a different object |
@@ -348,8 +507,15 @@ class BASE_EXPORT PersistentMemoryAllocator { |
// succeeds unless corruption is detected; check IsCorrupted() to find out. |
// Once an object is made iterable, its position in iteration can never |
// change; new iterable objects will always be added after it in the series. |
+ // 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 |