Fix Centurion name.
[0ad.git] / libraries / source / spidermonkey / include-win32-release / mozilla / ProfileBufferChunkManagerWithLocalLimit.h
blobedd0fb8205c0f50c30eebf8738113b9b2535198a
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/. */
7 #ifndef ProfileBufferChunkManagerWithLocalLimit_h
8 #define ProfileBufferChunkManagerWithLocalLimit_h
10 #include "BaseProfiler.h"
11 #include "mozilla/BaseProfilerDetail.h"
12 #include "mozilla/ProfileBufferChunkManager.h"
13 #include "mozilla/ProfileBufferControlledChunkManager.h"
15 namespace mozilla {
17 // Manages the Chunks for this process in a thread-safe manner, with a maximum
18 // size per process.
20 // "Unreleased" chunks are not owned here, only "released" chunks can be
21 // destroyed or recycled when reaching the memory limit, so it is theoretically
22 // possible to break that limit, if:
23 // - The user of this class doesn't release their chunks, AND/OR
24 // - The limit is too small (e.g., smaller than 2 or 3 chunks, which should be
25 // the usual number of unreleased chunks in flight).
26 // In this case, it just means that we will use more memory than allowed,
27 // potentially risking OOMs. Hopefully this shouldn't happen in real code,
28 // assuming that the user is doing the right thing and releasing chunks ASAP,
29 // and that the memory limit is reasonably large.
30 class ProfileBufferChunkManagerWithLocalLimit final
31 : public ProfileBufferChunkManager,
32 public ProfileBufferControlledChunkManager {
33 public:
34 using Length = ProfileBufferChunk::Length;
36 // MaxTotalBytes: Maximum number of bytes allocated in all local Chunks.
37 // ChunkMinBufferBytes: Minimum number of user-available bytes in each Chunk.
38 // Note that Chunks use a bit more memory for their header.
39 explicit ProfileBufferChunkManagerWithLocalLimit(size_t aMaxTotalBytes,
40 Length aChunkMinBufferBytes)
41 : mMaxTotalBytes(aMaxTotalBytes),
42 mChunkMinBufferBytes(aChunkMinBufferBytes) {}
44 ~ProfileBufferChunkManagerWithLocalLimit() {
45 if (mUpdateCallback) {
46 // Signal the end of this callback.
47 std::move(mUpdateCallback)(Update(nullptr));
51 [[nodiscard]] size_t MaxTotalSize() const final {
52 // `mMaxTotalBytes` is `const` so there is no need to lock the mutex.
53 return mMaxTotalBytes;
56 [[nodiscard]] UniquePtr<ProfileBufferChunk> GetChunk() final {
57 AUTO_PROFILER_STATS(Local_GetChunk);
58 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
59 return GetChunk(lock);
62 void RequestChunk(std::function<void(UniquePtr<ProfileBufferChunk>)>&&
63 aChunkReceiver) final {
64 AUTO_PROFILER_STATS(Local_RequestChunk);
65 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
66 if (mChunkReceiver) {
67 // We already have a chunk receiver, meaning a request is pending.
68 return;
70 // Store the chunk receiver. This indicates that a request is pending, and
71 // it will be handled in the next `FulfillChunkRequests()` call.
72 mChunkReceiver = std::move(aChunkReceiver);
75 void FulfillChunkRequests() final {
76 AUTO_PROFILER_STATS(Local_FulfillChunkRequests);
77 std::function<void(UniquePtr<ProfileBufferChunk>)> chunkReceiver;
78 UniquePtr<ProfileBufferChunk> chunk;
80 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
81 if (!mChunkReceiver) {
82 // No receiver means no pending request, we're done.
83 return;
85 // Otherwise there is a request, extract the receiver to call below.
86 std::swap(chunkReceiver, mChunkReceiver);
87 MOZ_ASSERT(!mChunkReceiver, "mChunkReceiver should have been emptied");
88 // And allocate the requested chunk. This may fail, it's fine, we're
89 // letting the receiver know about it.
90 AUTO_PROFILER_STATS(Local_FulfillChunkRequests_GetChunk);
91 chunk = GetChunk(lock);
93 // Invoke callback outside of lock, so that it can use other chunk manager
94 // functions if needed.
95 // Note that this means there could be a race, where another request happens
96 // now and even gets fulfilled before this one is! It should be rare, and
97 // shouldn't be a problem anyway, the user will still get their requested
98 // chunks, new/recycled chunks look the same so their order doesn't matter.
99 MOZ_ASSERT(!!chunkReceiver, "chunkReceiver shouldn't be empty here");
100 std::move(chunkReceiver)(std::move(chunk));
103 void ReleaseChunks(UniquePtr<ProfileBufferChunk> aChunks) final {
104 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
105 MOZ_ASSERT(mUser, "Not registered yet");
106 // Keep a pointer to the first newly-released chunk, so we can use it to
107 // prepare an update (after `aChunks` is moved-from).
108 const ProfileBufferChunk* const newlyReleasedChunks = aChunks.get();
109 // Compute the size of all provided chunks.
110 size_t bytes = 0;
111 for (const ProfileBufferChunk* chunk = newlyReleasedChunks; chunk;
112 chunk = chunk->GetNext()) {
113 bytes += chunk->BufferBytes();
114 MOZ_ASSERT(!chunk->ChunkHeader().mDoneTimeStamp.IsNull(),
115 "All released chunks should have a 'Done' timestamp");
116 MOZ_ASSERT(
117 !chunk->GetNext() || (chunk->ChunkHeader().mDoneTimeStamp <
118 chunk->GetNext()->ChunkHeader().mDoneTimeStamp),
119 "Released chunk groups must have increasing timestamps");
121 // Transfer the chunks size from the unreleased bucket to the released one.
122 mUnreleasedBufferBytes -= bytes;
123 if (!mReleasedChunks) {
124 // No other released chunks at the moment, we're starting the list.
125 MOZ_ASSERT(mReleasedBufferBytes == 0);
126 mReleasedBufferBytes = bytes;
127 mReleasedChunks = std::move(aChunks);
128 } else {
129 // Add to the end of the released chunks list (oldest first, most recent
130 // last.)
131 MOZ_ASSERT(mReleasedChunks->Last()->ChunkHeader().mDoneTimeStamp <
132 aChunks->ChunkHeader().mDoneTimeStamp,
133 "Chunks must be released in increasing timestamps");
134 mReleasedBufferBytes += bytes;
135 mReleasedChunks->SetLast(std::move(aChunks));
138 if (mUpdateCallback) {
139 mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
140 mReleasedChunks.get(), newlyReleasedChunks));
144 void SetChunkDestroyedCallback(
145 std::function<void(const ProfileBufferChunk&)>&& aChunkDestroyedCallback)
146 final {
147 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
148 MOZ_ASSERT(mUser, "Not registered yet");
149 mChunkDestroyedCallback = std::move(aChunkDestroyedCallback);
152 [[nodiscard]] UniquePtr<ProfileBufferChunk> GetExtantReleasedChunks() final {
153 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
154 MOZ_ASSERT(mUser, "Not registered yet");
155 mReleasedBufferBytes = 0;
156 if (mUpdateCallback) {
157 mUpdateCallback(Update(mUnreleasedBufferBytes, 0, nullptr, nullptr));
159 return std::move(mReleasedChunks);
162 void ForgetUnreleasedChunks() final {
163 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
164 MOZ_ASSERT(mUser, "Not registered yet");
165 mUnreleasedBufferBytes = 0;
166 if (mUpdateCallback) {
167 mUpdateCallback(
168 Update(0, mReleasedBufferBytes, mReleasedChunks.get(), nullptr));
172 [[nodiscard]] size_t SizeOfExcludingThis(
173 MallocSizeOf aMallocSizeOf) const final {
174 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
175 return SizeOfExcludingThis(aMallocSizeOf, lock);
178 [[nodiscard]] size_t SizeOfIncludingThis(
179 MallocSizeOf aMallocSizeOf) const final {
180 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
181 MOZ_ASSERT(mUser, "Not registered yet");
182 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf, lock);
185 void SetUpdateCallback(UpdateCallback&& aUpdateCallback) final {
186 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
187 if (mUpdateCallback) {
188 // Signal the end of the previous callback.
189 std::move(mUpdateCallback)(Update(nullptr));
191 mUpdateCallback = std::move(aUpdateCallback);
192 if (mUpdateCallback) {
193 mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
194 mReleasedChunks.get(), nullptr));
198 void DestroyChunksAtOrBefore(TimeStamp aDoneTimeStamp) final {
199 MOZ_ASSERT(!aDoneTimeStamp.IsNull());
200 baseprofiler::detail::BaseProfilerAutoLock lock(mMutex);
201 for (;;) {
202 if (!mReleasedChunks) {
203 // We don't own any released chunks (anymore), we're done.
204 break;
206 if (mReleasedChunks->ChunkHeader().mDoneTimeStamp > aDoneTimeStamp) {
207 // The current chunk is strictly after the given timestamp, we're done.
208 break;
210 // We've found a chunk at or before the timestamp, discard it.
211 DiscardOldestReleasedChunk(lock);
215 protected:
216 const ProfileBufferChunk* PeekExtantReleasedChunksAndLock() final {
217 mMutex.Lock();
218 MOZ_ASSERT(mUser, "Not registered yet");
219 return mReleasedChunks.get();
221 void UnlockAfterPeekExtantReleasedChunks() final { mMutex.Unlock(); }
223 private:
224 void MaybeRecycleChunk(
225 UniquePtr<ProfileBufferChunk>&& chunk,
226 const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
227 // Try to recycle big-enough chunks. (All chunks should have the same size,
228 // but it's a cheap test and may allow future adjustments based on actual
229 // data rate.)
230 if (chunk->BufferBytes() >= mChunkMinBufferBytes) {
231 // We keep up to two recycled chunks at any time.
232 if (!mRecycledChunks) {
233 mRecycledChunks = std::move(chunk);
234 } else if (!mRecycledChunks->GetNext()) {
235 mRecycledChunks->InsertNext(std::move(chunk));
240 UniquePtr<ProfileBufferChunk> TakeRecycledChunk(
241 const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
242 UniquePtr<ProfileBufferChunk> recycled;
243 if (mRecycledChunks) {
244 recycled = std::exchange(mRecycledChunks, mRecycledChunks->ReleaseNext());
245 recycled->MarkRecycled();
247 return recycled;
250 void DiscardOldestReleasedChunk(
251 const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
252 MOZ_ASSERT(!!mReleasedChunks);
253 UniquePtr<ProfileBufferChunk> oldest =
254 std::exchange(mReleasedChunks, mReleasedChunks->ReleaseNext());
255 mReleasedBufferBytes -= oldest->BufferBytes();
256 if (mChunkDestroyedCallback) {
257 // Inform the user that we're going to destroy this chunk.
258 mChunkDestroyedCallback(*oldest);
260 MaybeRecycleChunk(std::move(oldest), aLock);
263 [[nodiscard]] UniquePtr<ProfileBufferChunk> GetChunk(
264 const baseprofiler::detail::BaseProfilerAutoLock& aLock) {
265 MOZ_ASSERT(mUser, "Not registered yet");
266 // After this function, the total memory consumption will be the sum of:
267 // - Bytes from released (i.e., full) chunks,
268 // - Bytes from unreleased (still in use) chunks,
269 // - Bytes from the chunk we want to create/recycle. (Note that we don't
270 // count the extra bytes of chunk header, and of extra allocation ability,
271 // for the new chunk, as it's assumed to be negligible compared to the
272 // total memory limit.)
273 // If this total is higher than the local limit, we'll want to destroy
274 // the oldest released chunks until we're under the limit; if any, we may
275 // recycle one of them to avoid a deallocation followed by an allocation.
276 while (mReleasedBufferBytes + mUnreleasedBufferBytes +
277 mChunkMinBufferBytes >=
278 mMaxTotalBytes &&
279 !!mReleasedChunks) {
280 // We have reached the local limit, discard the oldest released chunk.
281 DiscardOldestReleasedChunk(aLock);
284 // Extract the recycled chunk, if any.
285 UniquePtr<ProfileBufferChunk> chunk = TakeRecycledChunk(aLock);
287 if (!chunk) {
288 // No recycled chunk -> Create a chunk now. (This could still fail.)
289 chunk = ProfileBufferChunk::Create(mChunkMinBufferBytes);
292 if (chunk) {
293 // We do have a chunk (recycled or new), record its size as "unreleased".
294 mUnreleasedBufferBytes += chunk->BufferBytes();
296 if (mUpdateCallback) {
297 mUpdateCallback(Update(mUnreleasedBufferBytes, mReleasedBufferBytes,
298 mReleasedChunks.get(), nullptr));
302 return chunk;
305 [[nodiscard]] size_t SizeOfExcludingThis(
306 MallocSizeOf aMallocSizeOf,
307 const baseprofiler::detail::BaseProfilerAutoLock&) const {
308 MOZ_ASSERT(mUser, "Not registered yet");
309 size_t size = 0;
310 if (mReleasedChunks) {
311 size += mReleasedChunks->SizeOfIncludingThis(aMallocSizeOf);
313 if (mRecycledChunks) {
314 size += mRecycledChunks->SizeOfIncludingThis(aMallocSizeOf);
316 // Note: Missing size of std::function external resources (if any).
317 return size;
320 // Maxumum number of bytes that should be used by all unreleased and released
321 // chunks. Note that only released chunks can be destroyed here, so it is the
322 // responsibility of the user to properly release their chunks when possible.
323 const size_t mMaxTotalBytes;
325 // Minimum number of bytes that new chunks should be able to store.
326 // Used when calling `ProfileBufferChunk::Create()`.
327 const Length mChunkMinBufferBytes;
329 // Mutex guarding the following members.
330 mutable baseprofiler::detail::BaseProfilerMutex mMutex;
332 // Number of bytes currently held in chunks that have been given away (through
333 // `GetChunk` or `RequestChunk`) and not released yet.
334 size_t mUnreleasedBufferBytes = 0;
336 // Number of bytes currently held in chunks that have been released and stored
337 // in `mReleasedChunks` below.
338 size_t mReleasedBufferBytes = 0;
340 // List of all released chunks. The oldest one should be at the start of the
341 // list, and may be destroyed or recycled when the memory limit is reached.
342 UniquePtr<ProfileBufferChunk> mReleasedChunks;
344 // This may hold chunks that were released then slated for destruction, they
345 // will be reused next time an allocation would have been needed.
346 UniquePtr<ProfileBufferChunk> mRecycledChunks;
348 // Optional callback used to notify the user when a chunk is about to be
349 // destroyed or recycled. (The data content is always destroyed, but the chunk
350 // container may be reused.)
351 std::function<void(const ProfileBufferChunk&)> mChunkDestroyedCallback;
353 // Callback set from `RequestChunk()`, until it is serviced in
354 // `FulfillChunkRequests()`. There can only be one request in flight.
355 std::function<void(UniquePtr<ProfileBufferChunk>)> mChunkReceiver;
357 UpdateCallback mUpdateCallback;
360 } // namespace mozilla
362 #endif // ProfileBufferChunkManagerWithLocalLimit_h