ProfileBufferControlledChunkManager.h (8271B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef ProfileBufferControlledChunkManager_h 8 #define ProfileBufferControlledChunkManager_h 9 10 #include "mozilla/ProfileBufferChunk.h" 11 12 #include <functional> 13 #include <vector> 14 15 namespace mozilla { 16 17 // A "Controlled" chunk manager will provide updates about chunks that it 18 // creates, releases, and destroys; and it can destroy released chunks as 19 // requested. 20 class ProfileBufferControlledChunkManager { 21 public: 22 using Length = ProfileBufferChunk::Length; 23 24 virtual ~ProfileBufferControlledChunkManager() = default; 25 26 // Minimum amount of chunk metadata to be transferred between processes. 27 struct ChunkMetadata { 28 // Timestamp when chunk was marked "done", which is used to: 29 // - determine its age, so the oldest one will be destroyed first, 30 // - uniquely identify this chunk in this process. (The parent process is 31 // responsible for associating this timestamp to its process id.) 32 TimeStamp mDoneTimeStamp; 33 // Size of this chunk's buffer. 34 Length mBufferBytes; 35 36 ChunkMetadata(TimeStamp aDoneTimeStamp, Length aBufferBytes) 37 : mDoneTimeStamp(aDoneTimeStamp), mBufferBytes(aBufferBytes) {} 38 }; 39 40 // Class collecting all information necessary to describe updates that 41 // happened in a chunk manager. 42 // An update can be folded into a previous update. 43 class Update { 44 public: 45 // Construct a "not-an-Update" object, which should only be used after a 46 // real update is folded into it. 47 Update() = default; 48 49 // Construct a "final" Update, which marks the end of all updates from a 50 // chunk manager. 51 explicit Update(decltype(nullptr)) : mUnreleasedBytes(FINAL) {} 52 53 // Construct an Update from the given data and released chunks. 54 // The chunk pointers may be null, and it doesn't matter if 55 // `aNewlyReleasedChunks` is already linked to `aExistingReleasedChunks` or 56 // not. 57 Update(size_t aUnreleasedBytes, size_t aReleasedBytes, 58 const ProfileBufferChunk* aExistingReleasedChunks, 59 const ProfileBufferChunk* aNewlyReleasedChunks) 60 : mUnreleasedBytes(aUnreleasedBytes), 61 mReleasedBytes(aReleasedBytes), 62 mOldestDoneTimeStamp( 63 aExistingReleasedChunks 64 ? aExistingReleasedChunks->ChunkHeader().mDoneTimeStamp 65 : TimeStamp{}) { 66 MOZ_RELEASE_ASSERT( 67 !IsNotUpdate(), 68 "Empty update should only be constructed with default constructor"); 69 MOZ_RELEASE_ASSERT( 70 !IsFinal(), 71 "Final update should only be constructed with nullptr constructor"); 72 for (const ProfileBufferChunk* chunk = aNewlyReleasedChunks; chunk; 73 chunk = chunk->GetNext()) { 74 mNewlyReleasedChunks.emplace_back(ChunkMetadata{ 75 chunk->ChunkHeader().mDoneTimeStamp, chunk->BufferBytes()}); 76 } 77 } 78 79 // Construct an Update from raw data. 80 // This may be used to re-construct an Update that was previously 81 // serialized. 82 Update(size_t aUnreleasedBytes, size_t aReleasedBytes, 83 TimeStamp aOldestDoneTimeStamp, 84 std::vector<ChunkMetadata>&& aNewlyReleasedChunks) 85 : mUnreleasedBytes(aUnreleasedBytes), 86 mReleasedBytes(aReleasedBytes), 87 mOldestDoneTimeStamp(aOldestDoneTimeStamp), 88 mNewlyReleasedChunks(std::move(aNewlyReleasedChunks)) {} 89 90 // Clear the Update completely and return it to a "not-an-Update" state. 91 void Clear() { 92 mUnreleasedBytes = NO_UPDATE; 93 mReleasedBytes = 0; 94 mOldestDoneTimeStamp = TimeStamp{}; 95 mNewlyReleasedChunks.clear(); 96 } 97 98 bool IsNotUpdate() const { return mUnreleasedBytes == NO_UPDATE; } 99 100 bool IsFinal() const { return mUnreleasedBytes == FINAL; } 101 102 size_t UnreleasedBytes() const { 103 MOZ_RELEASE_ASSERT(!IsNotUpdate(), 104 "Cannot access UnreleasedBytes from empty update"); 105 MOZ_RELEASE_ASSERT(!IsFinal(), 106 "Cannot access UnreleasedBytes from final update"); 107 return mUnreleasedBytes; 108 } 109 110 size_t ReleasedBytes() const { 111 MOZ_RELEASE_ASSERT(!IsNotUpdate(), 112 "Cannot access ReleasedBytes from empty update"); 113 MOZ_RELEASE_ASSERT(!IsFinal(), 114 "Cannot access ReleasedBytes from final update"); 115 return mReleasedBytes; 116 } 117 118 TimeStamp OldestDoneTimeStamp() const { 119 MOZ_RELEASE_ASSERT(!IsNotUpdate(), 120 "Cannot access OldestDoneTimeStamp from empty update"); 121 MOZ_RELEASE_ASSERT(!IsFinal(), 122 "Cannot access OldestDoneTimeStamp from final update"); 123 return mOldestDoneTimeStamp; 124 } 125 126 const std::vector<ChunkMetadata>& NewlyReleasedChunksRef() const { 127 MOZ_RELEASE_ASSERT( 128 !IsNotUpdate(), 129 "Cannot access NewlyReleasedChunksRef from empty update"); 130 MOZ_RELEASE_ASSERT( 131 !IsFinal(), "Cannot access NewlyReleasedChunksRef from final update"); 132 return mNewlyReleasedChunks; 133 } 134 135 // Fold a later update into this one. 136 void Fold(Update&& aNewUpdate) { 137 MOZ_ASSERT( 138 !IsFinal() || aNewUpdate.IsFinal(), 139 "There shouldn't be another non-final update after the final update"); 140 141 if (IsNotUpdate() || aNewUpdate.IsFinal()) { 142 // We were empty, or the new update is the final update, we just switch 143 // to that new update. 144 *this = std::move(aNewUpdate); 145 return; 146 } 147 148 mUnreleasedBytes = aNewUpdate.mUnreleasedBytes; 149 mReleasedBytes = aNewUpdate.mReleasedBytes; 150 if (!aNewUpdate.mOldestDoneTimeStamp.IsNull()) { 151 MOZ_ASSERT(mOldestDoneTimeStamp.IsNull() || 152 mOldestDoneTimeStamp <= aNewUpdate.mOldestDoneTimeStamp); 153 mOldestDoneTimeStamp = aNewUpdate.mOldestDoneTimeStamp; 154 auto it = mNewlyReleasedChunks.begin(); 155 while (it != mNewlyReleasedChunks.end() && 156 it->mDoneTimeStamp < mOldestDoneTimeStamp) { 157 it = mNewlyReleasedChunks.erase(it); 158 } 159 } 160 if (!aNewUpdate.mNewlyReleasedChunks.empty()) { 161 mNewlyReleasedChunks.reserve(mNewlyReleasedChunks.size() + 162 aNewUpdate.mNewlyReleasedChunks.size()); 163 mNewlyReleasedChunks.insert(mNewlyReleasedChunks.end(), 164 aNewUpdate.mNewlyReleasedChunks.begin(), 165 aNewUpdate.mNewlyReleasedChunks.end()); 166 } 167 } 168 169 private: 170 static const size_t NO_UPDATE = size_t(-1); 171 static const size_t FINAL = size_t(-2); 172 173 size_t mUnreleasedBytes = NO_UPDATE; 174 size_t mReleasedBytes = 0; 175 TimeStamp mOldestDoneTimeStamp; 176 std::vector<ChunkMetadata> mNewlyReleasedChunks; 177 }; 178 179 using UpdateCallback = std::function<void(Update&&)>; 180 181 // This *may* be set (or reset) by an object that needs to know about all 182 // chunk updates that happen in this manager. The main use will be to 183 // coordinate the global memory usage of Firefox. 184 // If a non-empty callback is given, it will be immediately invoked with the 185 // current state. 186 // When the callback is about to be destroyed (by overwriting it here, or in 187 // the class destructor), it will be invoked one last time with an empty 188 // update. 189 // Note that the callback (even the first current-state callback) will be 190 // invoked from inside a locked scope, so it should *not* call other functions 191 // of the chunk manager. A side benefit of this locking is that it guarantees 192 // that no two invocations can overlap. 193 virtual void SetUpdateCallback(UpdateCallback&& aUpdateCallback) = 0; 194 195 // This is a request to destroy all chunks before the given timestamp. 196 // This timestamp should be one that was given in a previous UpdateCallback 197 // call. Obviously, only released chunks can be destroyed. 198 virtual void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) = 0; 199 }; 200 201 } // namespace mozilla 202 203 #endif // ProfileBufferControlledChunkManager_h