Bug 1774773 - Added atomic locking around marker schema functions - r=florian

To avoid real or apparent races to the list of marker schema functions, some
bits of the atomic count are used to effectively implement a RWLock, so that
any number of threads can add their own new marker schema concurrently, while
the profile-capture reading of the whole list can only be done on its own.

Differential Revision: https://phabricator.services.mozilla.com/D157310
This commit is contained in:
Gerald Squelart 2022-09-15 03:39:00 +00:00
parent b9b608626d
commit 92a9be7744
4 changed files with 93 additions and 12 deletions

View file

@ -16,14 +16,28 @@ namespace base_profiler_markers_detail {
// work with too-small types.)
using DeserializerTagAtomic = unsigned;
// The atomic sDeserializerCount still also include bits that act as a "RWLock":
// Whoever can set this bit gets exclusive access to the count and the whole
// sMarkerTypeFunctions1Based array, guaranteeing that it cannot be modified.
static constexpr DeserializerTagAtomic scExclusiveLock = 0x80'00'00'00u;
// Code that wants shared access can add this value, then ensure there is no
// exclusive lock, after which it's guaranteed that no exclusive lock can be
// taken until the shared lock count goes back to zero.
static constexpr DeserializerTagAtomic scSharedLockUnit = 0x00'01'00'00u;
// This mask isolates the actual count value from the lock bits.
static constexpr DeserializerTagAtomic scTagMask = 0x00'00'FF'FFu;
// Number of currently-registered deserializers and other marker type functions.
static Atomic<DeserializerTagAtomic, MemoryOrdering::Relaxed>
// The high bits contain lock bits, see above.
static Atomic<DeserializerTagAtomic, MemoryOrdering::ReleaseAcquire>
sDeserializerCount{0};
// This needs to be big enough to handle all possible marker types. If one day
// this needs to be higher, the underlying DeserializerTag type will have to be
// changed.
static constexpr DeserializerTagAtomic DeserializerMax = 250;
static_assert(DeserializerMax <= scTagMask,
"DeserializerMax doesn't fit in scTagMask");
static_assert(
DeserializerMax <= std::numeric_limits<Streaming::DeserializerTag>::max(),
@ -49,7 +63,28 @@ static Streaming::MarkerTypeFunctions
MOZ_RELEASE_ASSERT(!!aMarkerTypeNameFunction);
MOZ_RELEASE_ASSERT(!!aMarkerSchemaFunction);
DeserializerTagAtomic tag = ++sDeserializerCount;
// Add a shared lock request, which will prevent future exclusive locking.
DeserializerTagAtomic tagWithLock = (sDeserializerCount += scSharedLockUnit);
// An exclusive locker may have arrived before us, just wait for it to finish.
while ((tagWithLock & scExclusiveLock) != 0u) {
tagWithLock = sDeserializerCount;
}
MOZ_ASSERT(
// This is equivalent to shifting right to only keep the lock counts.
tagWithLock / scSharedLockUnit <
// This is effectively half of the permissible shared lock range,
// that would mean way too many threads doing this work here!
scExclusiveLock / scSharedLockUnit / 2,
"The shared lock count is getting unexpectedly high, verify the "
"algorithm, and tweak constants if needed");
// Reserve a tag. Even if there are multiple shared-lock holders here, each
// one will get a different value, and therefore will access a different part
// of the sMarkerTypeFunctions1Based array.
const DeserializerTagAtomic tag = ++sDeserializerCount & scTagMask;
MOZ_RELEASE_ASSERT(
tag <= DeserializerMax,
"Too many deserializers, consider increasing DeserializerMax. "
@ -57,6 +92,9 @@ static Streaming::MarkerTypeFunctions
sMarkerTypeFunctions1Based[tag - 1] = {aDeserializer, aMarkerTypeNameFunction,
aMarkerSchemaFunction};
// And release our shared lock, to allow exclusive readers.
sDeserializerCount -= scSharedLockUnit;
return static_cast<DeserializerTag>(tag);
}
@ -69,9 +107,38 @@ static Streaming::MarkerTypeFunctions
return sMarkerTypeFunctions1Based[aTag - 1].mMarkerDataDeserializer;
}
/* static */ Span<const Streaming::MarkerTypeFunctions>
Streaming::MarkerTypeFunctionsArray() {
return {sMarkerTypeFunctions1Based, sDeserializerCount};
Streaming::LockedMarkerTypeFunctionsList::LockedMarkerTypeFunctionsList() {
for (;;) {
const DeserializerTagAtomic count = sDeserializerCount;
if ((count & scTagMask) != count) {
// Someone already has a lock, loop around.
continue;
}
// There are currently no locks, try to add our exclusive lock.
if (!sDeserializerCount.compareExchange(count, count | scExclusiveLock)) {
// Someone else modified sDeserializerCount since our read, loop around.
continue;
}
// We applied our exclusive lock, we can now read the list of functions,
// without interference until ~LockedMarkerTypeFunctionsList().
// (Note that sDeserializerCount may receive shared lock requests, but the
// count won't change.)
mMarkerTypeFunctionsSpan = {sMarkerTypeFunctions1Based, count};
break;
}
}
Streaming::LockedMarkerTypeFunctionsList::~LockedMarkerTypeFunctionsList() {
MOZ_ASSERT(
(sDeserializerCount & scExclusiveLock) == scExclusiveLock,
"sDeserializerCount should still have the the exclusive lock bit set");
MOZ_ASSERT(
(sDeserializerCount & scTagMask) ==
DeserializerTagAtomic(mMarkerTypeFunctionsSpan.size()),
"sDeserializerCount should have the same count since construction");
sDeserializerCount &= ~scExclusiveLock;
}
// Only accessed on the main thread.

View file

@ -1816,9 +1816,8 @@ static void StreamCategories(SpliceableJSONWriter& aWriter) {
static void StreamMarkerSchema(SpliceableJSONWriter& aWriter) {
// Get an array view with all registered marker-type-specific functions.
Span<const base_profiler_markers_detail::Streaming::MarkerTypeFunctions>
markerTypeFunctionsArray =
base_profiler_markers_detail::Streaming::MarkerTypeFunctionsArray();
base_profiler_markers_detail::Streaming::LockedMarkerTypeFunctionsList
markerTypeFunctionsArray;
// List of streamed marker names, this is used to spot duplicates.
std::set<std::string> names;
// Stream the display schema for each different one. (Duplications may come

View file

@ -72,7 +72,23 @@ struct Streaming {
DeserializerTag aTag);
// Retrieve all MarkerTypeFunctions's.
MFBT_API static Span<const MarkerTypeFunctions> MarkerTypeFunctionsArray();
// While this object lives, no other operations can happen on this list.
class LockedMarkerTypeFunctionsList {
public:
MFBT_API LockedMarkerTypeFunctionsList();
MFBT_API ~LockedMarkerTypeFunctionsList();
LockedMarkerTypeFunctionsList(const LockedMarkerTypeFunctionsList&) =
delete;
LockedMarkerTypeFunctionsList& operator=(
const LockedMarkerTypeFunctionsList&) = delete;
auto begin() const { return mMarkerTypeFunctionsSpan.begin(); }
auto end() const { return mMarkerTypeFunctionsSpan.end(); }
private:
Span<const MarkerTypeFunctions> mMarkerTypeFunctionsSpan;
};
};
// This helper will examine a marker type's `StreamJSONMarkerData` function, see

View file

@ -2652,9 +2652,8 @@ static void StreamCategories(SpliceableJSONWriter& aWriter) {
static void StreamMarkerSchema(SpliceableJSONWriter& aWriter) {
// Get an array view with all registered marker-type-specific functions.
Span<const base_profiler_markers_detail::Streaming::MarkerTypeFunctions>
markerTypeFunctionsArray =
base_profiler_markers_detail::Streaming::MarkerTypeFunctionsArray();
base_profiler_markers_detail::Streaming::LockedMarkerTypeFunctionsList
markerTypeFunctionsArray;
// List of streamed marker names, this is used to spot duplicates.
std::set<std::string> names;
// Stream the display schema for each different one. (Duplications may come