/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ProfileBufferControlledChunkManager_h #define ProfileBufferControlledChunkManager_h #include "mozilla/ProfileBufferChunk.h" #include #include namespace mozilla { // A "Controlled" chunk manager will provide updates about chunks that it // creates, releases, and destroys; and it can destroy released chunks as // requested. class ProfileBufferControlledChunkManager { public: using Length = ProfileBufferChunk::Length; virtual ~ProfileBufferControlledChunkManager() = default; // Minimum amount of chunk metadata to be transferred between processes. struct ChunkMetadata { // Timestamp when chunk was marked "done", which is used to: // - determine its age, so the oldest one will be destroyed first, // - uniquely identify this chunk in this process. (The parent process is // responsible for associating this timestamp to its process id.) TimeStamp mDoneTimeStamp; // Size of this chunk's buffer. Length mBufferBytes; ChunkMetadata(TimeStamp aDoneTimeStamp, Length aBufferBytes) : mDoneTimeStamp(aDoneTimeStamp), mBufferBytes(aBufferBytes) {} }; // Class collecting all information necessary to describe updates that // happened in a chunk manager. // An update can be folded into a previous update. class Update { public: // Construct a "not-an-Update" object, which should only be used after a // real update is folded into it. Update() = default; // Construct a "final" Update, which marks the end of all updates from a // chunk manager. explicit Update(decltype(nullptr)) : mUnreleasedBytes(FINAL) {} // Construct an Update from the given data and released chunks. // The chunk pointers may be null, and it doesn't matter if // `aNewlyReleasedChunks` is already linked to `aExistingReleasedChunks` or // not. Update(size_t aUnreleasedBytes, size_t aReleasedBytes, const ProfileBufferChunk* aExistingReleasedChunks, const ProfileBufferChunk* aNewlyReleasedChunks) : mUnreleasedBytes(aUnreleasedBytes), mReleasedBytes(aReleasedBytes), mOldestDoneTimeStamp( aExistingReleasedChunks ? aExistingReleasedChunks->ChunkHeader().mDoneTimeStamp : TimeStamp{}) { MOZ_RELEASE_ASSERT( !IsNotUpdate(), "Empty update should only be constructed with default constructor"); MOZ_RELEASE_ASSERT( !IsFinal(), "Final update should only be constructed with nullptr constructor"); for (const ProfileBufferChunk* chunk = aNewlyReleasedChunks; chunk; chunk = chunk->GetNext()) { mNewlyReleasedChunks.emplace_back(ChunkMetadata{ chunk->ChunkHeader().mDoneTimeStamp, chunk->BufferBytes()}); } } // Construct an Update from raw data. // This may be used to re-construct an Update that was previously // serialized. Update(size_t aUnreleasedBytes, size_t aReleasedBytes, TimeStamp aOldestDoneTimeStamp, std::vector&& aNewlyReleasedChunks) : mUnreleasedBytes(aUnreleasedBytes), mReleasedBytes(aReleasedBytes), mOldestDoneTimeStamp(aOldestDoneTimeStamp), mNewlyReleasedChunks(std::move(aNewlyReleasedChunks)) {} // Clear the Update completely and return it to a "not-an-Update" state. void Clear() { mUnreleasedBytes = NO_UPDATE; mReleasedBytes = 0; mOldestDoneTimeStamp = TimeStamp{}; mNewlyReleasedChunks.clear(); } bool IsNotUpdate() const { return mUnreleasedBytes == NO_UPDATE; } bool IsFinal() const { return mUnreleasedBytes == FINAL; } size_t UnreleasedBytes() const { MOZ_RELEASE_ASSERT(!IsNotUpdate(), "Cannot access UnreleasedBytes from empty update"); MOZ_RELEASE_ASSERT(!IsFinal(), "Cannot access UnreleasedBytes from final update"); return mUnreleasedBytes; } size_t ReleasedBytes() const { MOZ_RELEASE_ASSERT(!IsNotUpdate(), "Cannot access ReleasedBytes from empty update"); MOZ_RELEASE_ASSERT(!IsFinal(), "Cannot access ReleasedBytes from final update"); return mReleasedBytes; } TimeStamp OldestDoneTimeStamp() const { MOZ_RELEASE_ASSERT(!IsNotUpdate(), "Cannot access OldestDoneTimeStamp from empty update"); MOZ_RELEASE_ASSERT(!IsFinal(), "Cannot access OldestDoneTimeStamp from final update"); return mOldestDoneTimeStamp; } const std::vector& NewlyReleasedChunksRef() const { MOZ_RELEASE_ASSERT( !IsNotUpdate(), "Cannot access NewlyReleasedChunksRef from empty update"); MOZ_RELEASE_ASSERT( !IsFinal(), "Cannot access NewlyReleasedChunksRef from final update"); return mNewlyReleasedChunks; } // Fold a later update into this one. void Fold(Update&& aNewUpdate) { MOZ_ASSERT( !IsFinal() || aNewUpdate.IsFinal(), "There shouldn't be another non-final update after the final update"); if (IsNotUpdate() || aNewUpdate.IsFinal()) { // We were empty, or the new update is the final update, we just switch // to that new update. *this = std::move(aNewUpdate); return; } mUnreleasedBytes = aNewUpdate.mUnreleasedBytes; mReleasedBytes = aNewUpdate.mReleasedBytes; if (!aNewUpdate.mOldestDoneTimeStamp.IsNull()) { MOZ_ASSERT(mOldestDoneTimeStamp.IsNull() || mOldestDoneTimeStamp <= aNewUpdate.mOldestDoneTimeStamp); mOldestDoneTimeStamp = aNewUpdate.mOldestDoneTimeStamp; auto it = mNewlyReleasedChunks.begin(); while (it != mNewlyReleasedChunks.end() && it->mDoneTimeStamp < mOldestDoneTimeStamp) { it = mNewlyReleasedChunks.erase(it); } } if (!aNewUpdate.mNewlyReleasedChunks.empty()) { mNewlyReleasedChunks.reserve(mNewlyReleasedChunks.size() + aNewUpdate.mNewlyReleasedChunks.size()); mNewlyReleasedChunks.insert(mNewlyReleasedChunks.end(), aNewUpdate.mNewlyReleasedChunks.begin(), aNewUpdate.mNewlyReleasedChunks.end()); } } private: static const size_t NO_UPDATE = size_t(-1); static const size_t FINAL = size_t(-2); size_t mUnreleasedBytes = NO_UPDATE; size_t mReleasedBytes = 0; TimeStamp mOldestDoneTimeStamp; std::vector mNewlyReleasedChunks; }; using UpdateCallback = std::function; // This *may* be set (or reset) by an object that needs to know about all // chunk updates that happen in this manager. The main use will be to // coordinate the global memory usage of Firefox. // If a non-empty callback is given, it will be immediately invoked with the // current state. // When the callback is about to be destroyed (by overwriting it here, or in // the class destructor), it will be invoked one last time with an empty // update. // Note that the callback (even the first current-state callback) will be // invoked from inside a locked scope, so it should *not* call other functions // of the chunk manager. A side benefit of this locking is that it guarantees // that no two invocations can overlap. virtual void SetUpdateCallback(UpdateCallback&& aUpdateCallback) = 0; // This is a request to destroy all chunks before the given timestamp. // This timestamp should be one that was given in a previous UpdateCallback // call. Obviously, only released chunks can be destroyed. virtual void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) = 0; }; } // namespace mozilla #endif // ProfileBufferControlledChunkManager_h