diff --git a/mozglue/baseprofiler/public/ProfileChunkedBuffer.h b/mozglue/baseprofiler/public/ProfileChunkedBuffer.h index 529555891545..927781290ce7 100644 --- a/mozglue/baseprofiler/public/ProfileChunkedBuffer.h +++ b/mozglue/baseprofiler/public/ProfileChunkedBuffer.h @@ -71,6 +71,25 @@ class ProfileChunkedBuffer { explicit ProfileChunkedBuffer(ThreadSafety aThreadSafety) : mMutex(aThreadSafety != ThreadSafety::WithoutMutex) {} + // Start in-session with external chunk manager. + ProfileChunkedBuffer(ThreadSafety aThreadSafety, + ProfileBufferChunkManager& aChunkManager) + : mMutex(aThreadSafety != ThreadSafety::WithoutMutex) { + SetChunkManager(aChunkManager); + } + + // Start in-session with owned chunk manager. + ProfileChunkedBuffer(ThreadSafety aThreadSafety, + UniquePtr&& aChunkManager) + : mMutex(aThreadSafety != ThreadSafety::WithoutMutex) { + SetChunkManager(std::move(aChunkManager)); + } + + ~ProfileChunkedBuffer() { + // Do proper clean-up by resetting the chunk manager. + ResetChunkManager(); + } + // This cannot change during the lifetime of this buffer, so there's no need // to lock. [[nodiscard]] bool IsThreadSafe() const { return mMutex.IsActivated(); } @@ -80,6 +99,40 @@ class ProfileChunkedBuffer { return !!mChunkManager; } + // Stop using the current chunk manager. + // If we own the current chunk manager, it will be destroyed. + // This will always clear currently-held chunks, if any. + void ResetChunkManager() { + baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex); + Unused << ResetChunkManager(lock); + } + + // Set the current chunk manager. + // The caller is responsible for keeping the chunk manager alive as along as + // it's used here (until the next (Re)SetChunkManager, or + // ~ProfileChunkedBuffer). + void SetChunkManager(ProfileBufferChunkManager& aChunkManager) { + baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex); + Unused << ResetChunkManager(lock); + SetChunkManager(aChunkManager, lock); + } + + // Set the current chunk manager, and keep ownership of it. + void SetChunkManager(UniquePtr&& aChunkManager) { + baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex); + Unused << ResetChunkManager(lock); + mOwnedChunkManager = std::move(aChunkManager); + if (mOwnedChunkManager) { + SetChunkManager(*mOwnedChunkManager, lock); + } + } + + // Stop using the current chunk manager, and return it if owned here. + [[nodiscard]] UniquePtr ExtractChunkManager() { + baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex); + return ResetChunkManager(lock); + } + void Clear() { baseprofiler::detail::BaseProfilerMaybeAutoLock lock(mMutex); if (MOZ_UNLIKELY(!mChunkManager)) { @@ -219,6 +272,57 @@ class ProfileChunkedBuffer { #endif // DEBUG private: + [[nodiscard]] UniquePtr ResetChunkManager( + const baseprofiler::detail::BaseProfilerMaybeAutoLock&) { + UniquePtr chunkManager; + if (mChunkManager) { + mChunkManager->ForgetUnreleasedChunks(); +#ifdef DEBUG + mChunkManager->DeregisteredFrom(this); +#endif + mChunkManager = nullptr; + chunkManager = std::move(mOwnedChunkManager); + if (mCurrentChunk) { + mCurrentChunk->MarkDone(); + mCurrentChunk = nullptr; + } + mNextChunks = nullptr; + mNextChunkRangeStart = mRangeEnd; + mRangeStart = mRangeEnd; + mPushedBlockCount = 0; + mClearedBlockCount = 0; + } + return chunkManager; + } + + void SetChunkManager( + ProfileBufferChunkManager& aChunkManager, + const baseprofiler::detail::BaseProfilerMaybeAutoLock& aLock) { + MOZ_ASSERT(!mChunkManager); + mChunkManager = &aChunkManager; +#ifdef DEBUG + mChunkManager->RegisteredWith(this); +#endif + + mChunkManager->SetChunkDestroyedCallback( + [this](const ProfileBufferChunk& aChunk) { + for (;;) { + ProfileBufferIndex rangeStart = mRangeStart; + if (MOZ_LIKELY(rangeStart <= aChunk.RangeStart())) { + if (MOZ_LIKELY(mRangeStart.compareExchange( + rangeStart, + aChunk.RangeStart() + aChunk.BufferBytes()))) { + break; + } + } + } + mClearedBlockCount += aChunk.BlockCount(); + }); + + // We start with one chunk right away. + SetAndInitializeCurrentChunk(mChunkManager->GetChunk(), aLock); + } + [[nodiscard]] size_t SizeOfExcludingThis( MallocSizeOf aMallocSizeOf, const baseprofiler::detail::BaseProfilerMaybeAutoLock&) const { @@ -260,6 +364,9 @@ class ProfileChunkedBuffer { // It may be owned locally (see below) or externally. ProfileBufferChunkManager* mChunkManager = nullptr; + // Only non-null when we own the current Chunk Manager. + UniquePtr mOwnedChunkManager; + UniquePtr mCurrentChunk; UniquePtr mNextChunks; diff --git a/mozglue/tests/TestBaseProfiler.cpp b/mozglue/tests/TestBaseProfiler.cpp index 2280d1ac7d43..3ff3b33b594a 100644 --- a/mozglue/tests/TestBaseProfiler.cpp +++ b/mozglue/tests/TestBaseProfiler.cpp @@ -850,6 +850,31 @@ static void TestChunkedBuffer() { "UniquePtr"); MOZ_RELEASE_ASSERT(!chunks, "Expected no chunks when out-of-session"); + // Use ProfileBufferChunkManagerWithLocalLimit, which will give away + // ProfileBufferChunks that can contain 128 bytes, using up to 1KB of memory + // (including usable 128 bytes and headers). + constexpr size_t bufferMaxSize = 1024; + constexpr ProfileChunkedBuffer::Length chunkMinSize = 128; + ProfileBufferChunkManagerWithLocalLimit cm(bufferMaxSize, chunkMinSize); + cb.SetChunkManager(cm); + + // Let the chunk manager fulfill the initial request for an extra chunk. + cm.FulfillChunkRequests(); + + MOZ_RELEASE_ASSERT(cm.MaxTotalSize() == bufferMaxSize); + MOZ_RELEASE_ASSERT(cb.BufferLength().isSome()); + MOZ_RELEASE_ASSERT(*cb.BufferLength() == bufferMaxSize); + + // Steal the underlying ProfileBufferChunks from the ProfileChunkedBuffer. + chunks = cb.GetAllChunks(); + MOZ_RELEASE_ASSERT(!!chunks, "Expected at least one chunk"); + MOZ_RELEASE_ASSERT(!chunks->GetNext(), "Expected only one chunk"); + const ProfileChunkedBuffer::Length chunkActualSize = chunks->BufferBytes(); + MOZ_RELEASE_ASSERT(chunkActualSize >= chunkMinSize); + MOZ_RELEASE_ASSERT(chunks->RangeStart() == 1); + MOZ_RELEASE_ASSERT(chunks->OffsetFirstBlock() == 0); + MOZ_RELEASE_ASSERT(chunks->OffsetPastLastBlock() == 0); + #ifdef DEBUG // cb.Dump(); #endif @@ -860,9 +885,37 @@ static void TestChunkedBuffer() { // cb.Dump(); #endif + // Reset to out-of-session. + cb.ResetChunkManager(); + + chunks = cb.GetAllChunks(); + MOZ_RELEASE_ASSERT(!chunks, "Expected no chunks when out-of-session"); + printf("TestChunkedBuffer done\n"); } +static void TestChunkedBufferSingle() { + printf("TestChunkedBufferSingle...\n"); + + constexpr ProfileChunkedBuffer::Length chunkMinSize = 128; + + // Create a ProfileChunkedBuffer that will own&use a + // ProfileBufferChunkManagerSingle, which will give away one + // ProfileBufferChunk that can contain 128 bytes. + ProfileChunkedBuffer cbSingle( + ProfileChunkedBuffer::ThreadSafety::WithoutMutex, + MakeUnique(chunkMinSize)); + + MOZ_RELEASE_ASSERT(cbSingle.BufferLength().isSome()); + MOZ_RELEASE_ASSERT(*cbSingle.BufferLength() >= chunkMinSize); + +#ifdef DEBUG + // cbSingle.Dump(); +#endif + + printf("TestChunkedBufferSingle done\n"); +} + static void TestModuloBuffer(ModuloBuffer<>& mb, uint32_t MBSize) { using MB = ModuloBuffer<>; @@ -2059,6 +2112,7 @@ void TestProfilerDependencies() { TestChunkManagerSingle(); TestChunkManagerWithLocalLimit(); TestChunkedBuffer(); + TestChunkedBufferSingle(); TestModuloBuffer(); TestBlocksRingBufferAPI(); TestBlocksRingBufferUnderlyingBufferChanges();