Skip to content

Commit f58d99d

Browse files
Riley Dulinfacebook-github-bot
authored andcommitted
Add a mutex to AllocationLocationTracker functions
Summary: In concurrent mode Hades needs to have some extra synchronization when enabling/disabling the memory profiler, and when adding new allocs. Add a `Mutex` used to protect the `stackMap_` member. The `enabled_` member is protected by ensuring that no background GC is running while enabling/disabling. The cost of acquiring mutexes is only paid when the profiler is on, which is only in debug modes. This should have no effect on release performance. Reviewed By: neildhar Differential Revision: D25284428 fbshipit-source-id: 0964c070f2ad2cbe01cf004d5c5e1b9d9c814f61
1 parent 35e9566 commit f58d99d

6 files changed

Lines changed: 85 additions & 9 deletions

File tree

include/hermes/VM/GCBase.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ class GCBase {
419419
/// Flush out heap profiler data to the callback after a new kFlushThreshold
420420
/// bytes are allocated.
421421
static constexpr uint64_t kFlushThreshold = 128 * (1 << 10);
422+
/// This mutex protects stackMap_. Specifically does not protect enabled_,
423+
/// because enabled_ should only be changed while the GC isn't running
424+
/// anyway. Also doesn't protect fragments_ because only the allocator
425+
/// modifies fragments_.
426+
Mutex mtx_;
422427
/// Associates allocations at their current location with their stack trace
423428
/// data.
424429
llvh::DenseMap<const void *, StackTracesTreeNode *> stackMap_;
@@ -580,6 +585,11 @@ class GCBase {
580585
/// the Chrome snapshot viewer.
581586
static constexpr HeapSnapshot::NodeID kIDStep = 2;
582587

588+
/// This mutex protects objectIDMap_, symbolIDMap_, and numberIDMap_.
589+
/// Specifically does not protect lastID_, since there's only one allocator
590+
/// at a time, and only new allocations affect lastID_.
591+
Mutex mtx_;
592+
583593
/// The last ID assigned to a non-native object. Object IDs are not
584594
/// recycled so that snapshots don't confuse two objects with each other.
585595
/// NOTE: Reserved guarantees that this is an odd number.
@@ -747,6 +757,21 @@ class GCBase {
747757
virtual void createSnapshot(llvh::raw_ostream &os) = 0;
748758
void createSnapshot(GC *gc, llvh::raw_ostream &os);
749759

760+
/// Turn on the heap profiler, which will track when allocations are made and
761+
/// the stack trace of when they were created.
762+
virtual void enableHeapProfiler(
763+
std::function<void(
764+
uint64_t,
765+
std::chrono::microseconds,
766+
std::vector<GCBase::AllocationLocationTracker::HeapStatsUpdate>)>
767+
fragmentCallback);
768+
769+
/// Turn off the heap profiler, which will stop tracking new allocations and
770+
/// not record any stack traces.
771+
/// Disabling will not forget any objects that are still alive. This way
772+
/// re-enabling later will still remember earlier objects.
773+
virtual void disableHeapProfiler();
774+
750775
#ifdef HERMESVM_SERIALIZE
751776
/// Serialize WeakRefs.
752777
virtual void serializeWeakRefs(Serializer &s) = 0;
@@ -1564,6 +1589,7 @@ inline bool GCBase::IDTracker::isTrackingIDs() const {
15641589
}
15651590

15661591
inline HeapSnapshot::NodeID GCBase::IDTracker::getObjectID(const void *cell) {
1592+
std::lock_guard<Mutex> lk{mtx_};
15671593
auto iter = objectIDMap_.find(cell);
15681594
if (iter != objectIDMap_.end()) {
15691595
return iter->second;
@@ -1576,12 +1602,14 @@ inline HeapSnapshot::NodeID GCBase::IDTracker::getObjectID(const void *cell) {
15761602

15771603
inline HeapSnapshot::NodeID GCBase::IDTracker::getObjectIDMustExist(
15781604
const void *cell) {
1605+
std::lock_guard<Mutex> lk{mtx_};
15791606
auto iter = objectIDMap_.find(cell);
15801607
assert(iter != objectIDMap_.end() && "cell must already have an ID");
15811608
return iter->second;
15821609
}
15831610

15841611
inline HeapSnapshot::NodeID GCBase::IDTracker::getObjectID(uint32_t symIdx) {
1612+
std::lock_guard<Mutex> lk{mtx_};
15851613
auto iter = symbolIDMap_.find(symIdx);
15861614
if (iter != symbolIDMap_.end()) {
15871615
return iter->second;
@@ -1593,6 +1621,7 @@ inline HeapSnapshot::NodeID GCBase::IDTracker::getObjectID(uint32_t symIdx) {
15931621
}
15941622

15951623
inline HeapSnapshot::NodeID GCBase::IDTracker::getNativeID(const void *mem) {
1624+
std::lock_guard<Mutex> lk{mtx_};
15961625
auto iter = objectIDMap_.find(mem);
15971626
if (iter != objectIDMap_.end()) {
15981627
return iter->second;
@@ -1612,6 +1641,7 @@ inline void GCBase::IDTracker::moveObject(
16121641
// happen in old generations where it is compacted to the same location.
16131642
return;
16141643
}
1644+
std::lock_guard<Mutex> lk{mtx_};
16151645
auto old = objectIDMap_.find(oldLocation);
16161646
if (old == objectIDMap_.end()) {
16171647
// Avoid making new keys for objects that don't need to be tracked.
@@ -1627,6 +1657,7 @@ inline void GCBase::IDTracker::moveObject(
16271657
}
16281658

16291659
inline void GCBase::IDTracker::untrackObject(const void *cell) {
1660+
std::lock_guard<Mutex> lk{mtx_};
16301661
objectIDMap_.erase(cell);
16311662
}
16321663

@@ -1642,6 +1673,7 @@ inline const GCExecTrace &GCBase::getGCExecTrace() const {
16421673

16431674
template <typename F>
16441675
inline void GCBase::IDTracker::forEachID(F callback) {
1676+
std::lock_guard<Mutex> lk{mtx_};
16451677
for (auto &p : objectIDMap_) {
16461678
callback(p.first, p.second);
16471679
}

include/hermes/VM/HadesGC.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ class HadesGC final : public GCBase {
8484
void getHeapInfoWithMallocSize(HeapInfo &info) override;
8585
void getCrashManagerHeapInfo(CrashManager::HeapInformation &info) override;
8686
void createSnapshot(llvh::raw_ostream &os) override;
87+
void enableHeapProfiler(
88+
std::function<void(
89+
uint64_t,
90+
std::chrono::microseconds,
91+
std::vector<GCBase::AllocationLocationTracker::HeapStatsUpdate>)>
92+
fragmentCallback) override;
93+
void disableHeapProfiler() override;
8794
void printStats(JSONEmitter &json) override;
8895

8996
/// \}

lib/VM/GCBase.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,19 @@ void GCBase::createSnapshot(GC *gc, llvh::raw_ostream &os) {
536536
snap.endSection(HeapSnapshot::Section::Locations);
537537
}
538538

539+
void GCBase::enableHeapProfiler(
540+
std::function<void(
541+
uint64_t,
542+
std::chrono::microseconds,
543+
std::vector<GCBase::AllocationLocationTracker::HeapStatsUpdate>)>
544+
fragmentCallback) {
545+
getAllocationLocationTracker().enable(std::move(fragmentCallback));
546+
}
547+
548+
void GCBase::disableHeapProfiler() {
549+
getAllocationLocationTracker().disable();
550+
}
551+
539552
void GCBase::checkTripwire(size_t dataSize) {
540553
if (LLVM_LIKELY(!tripwireCallback_) ||
541554
LLVM_LIKELY(dataSize < tripwireLimit_) || tripwireCalled_) {
@@ -905,6 +918,7 @@ void GCBase::IDTracker::deserialize(Deserializer &d) {
905918

906919
void GCBase::IDTracker::untrackUnmarkedSymbols(
907920
const llvh::BitVector &markedSymbols) {
921+
std::lock_guard<Mutex> lk{mtx_};
908922
std::vector<uint32_t> toUntrack;
909923
for (const auto &pair : symbolIDMap_) {
910924
if (!markedSymbols.test(pair.first)) {
@@ -917,6 +931,7 @@ void GCBase::IDTracker::untrackUnmarkedSymbols(
917931
}
918932

919933
HeapSnapshot::NodeID GCBase::IDTracker::getNumberID(double num) {
934+
std::lock_guard<Mutex> lk{mtx_};
920935
auto &numberRef = numberIDMap_[num];
921936
// If the entry didn't exist, the value was initialized to 0.
922937
if (numberRef != 0) {
@@ -992,6 +1007,8 @@ void GCBase::AllocationLocationTracker::newAlloc(const void *ptr, uint32_t sz) {
9921007
flushCallback();
9931008
}
9941009
if (auto node = gc_->gcCallbacks_->getCurrentStackTracesTreeNode(ip)) {
1010+
// Hold a lock while modifying stackMap_.
1011+
std::lock_guard<Mutex> lk{mtx_};
9951012
auto itAndDidInsert = stackMap_.try_emplace(ptr, node);
9961013
assert(itAndDidInsert.second && "Failed to create a new node");
9971014
(void)itAndDidInsert;
@@ -1006,6 +1023,9 @@ void GCBase::AllocationLocationTracker::moveAlloc(
10061023
// This can happen in old generations when compacting to the same location.
10071024
return;
10081025
}
1026+
// Hold a lock in case concurrent Hades tries to free an alloc at the same
1027+
// time.
1028+
std::lock_guard<Mutex> lk{mtx_};
10091029
auto oldIt = stackMap_.find(oldPtr);
10101030
if (oldIt == stackMap_.end()) {
10111031
// This can happen if the tracker is turned on between collections, and
@@ -1023,6 +1043,9 @@ void GCBase::AllocationLocationTracker::moveAlloc(
10231043
void GCBase::AllocationLocationTracker::freeAlloc(
10241044
const void *ptr,
10251045
uint32_t sz) {
1046+
// Hold a lock during freeAlloc because concurrent Hades might be creating an
1047+
// alloc (newAlloc) at the same time.
1048+
std::lock_guard<Mutex> lk{mtx_};
10261049
stackMap_.erase(ptr);
10271050
if (!enabled_) {
10281051
// Fragments won't exist if the heap profiler isn't enabled.

lib/VM/Runtime.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,11 +2242,11 @@ void Runtime::enableAllocationLocationTracker(
22422242
stackTracesTree_ = make_unique<StackTracesTree>();
22432243
}
22442244
stackTracesTree_->syncWithRuntimeStack(this);
2245-
heap_.getAllocationLocationTracker().enable(std::move(fragmentCallback));
2245+
heap_.enableHeapProfiler(std::move(fragmentCallback));
22462246
}
22472247

22482248
void Runtime::disableAllocationLocationTracker(bool clearExistingTree) {
2249-
heap_.getAllocationLocationTracker().disable();
2249+
heap_.disableHeapProfiler();
22502250
if (clearExistingTree) {
22512251
stackTracesTree_.reset();
22522252
}

lib/VM/gcs/HadesGC.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -900,10 +900,6 @@ bool HadesGC::OldGen::sweepNext() {
900900
// Cell is dead, run its finalizer first if it has one.
901901
cell->getVT()->finalizeIfExists(cell, gc_);
902902
if (isTracking) {
903-
// FIXME: There could be a race condition here if newAlloc is
904-
// being called at the same time and using a shared data structure
905-
// with freeAlloc. freeAlloc relies on the ID, so call it before
906-
// untrackObject.
907903
gc_->getAllocationLocationTracker().freeAlloc(cell, sz);
908904
gc_->getIDTracker().untrackObject(cell);
909905
}
@@ -1085,6 +1081,25 @@ void HadesGC::createSnapshot(llvh::raw_ostream &os) {
10851081
}
10861082
}
10871083

1084+
void HadesGC::enableHeapProfiler(
1085+
std::function<void(
1086+
uint64_t,
1087+
std::chrono::microseconds,
1088+
std::vector<GCBase::AllocationLocationTracker::HeapStatsUpdate>)>
1089+
fragmentCallback) {
1090+
std::lock_guard<Mutex> lk{gcMutex_};
1091+
// Let any existing collections complete before enabling the profiler.
1092+
waitForCollectionToFinish();
1093+
GCBase::enableHeapProfiler(std::move(fragmentCallback));
1094+
}
1095+
1096+
void HadesGC::disableHeapProfiler() {
1097+
std::lock_guard<Mutex> lk{gcMutex_};
1098+
// Let any existing collections complete before disabling the profiler.
1099+
waitForCollectionToFinish();
1100+
GCBase::disableHeapProfiler();
1101+
}
1102+
10881103
void HadesGC::printStats(JSONEmitter &json) {
10891104
GCBase::printStats(json);
10901105
json.emitKey("specific");

unittests/VMRuntime/StackTracesTreeTest.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77

88
#include "hermes/VM/StackTracesTree-NoRuntime.h"
99

10-
#if defined(HERMES_ENABLE_ALLOCATION_LOCATION_TRACES) and \
11-
!defined(HERMESVM_GC_HADES)
10+
#if defined(HERMES_ENABLE_ALLOCATION_LOCATION_TRACES)
1211

1312
#include "TestHelpers.h"
1413

@@ -61,7 +60,7 @@ struct StackTracesTreeTest : public RuntimeTestFixtureBase {
6160
std::string res;
6261
llvh::raw_string_ostream resStream(res);
6362
auto stringTable = runtime->getStackTracesTree()->getStringTable();
64-
auto allocationLocationTracker =
63+
auto &allocationLocationTracker =
6564
runtime->getHeap().getAllocationLocationTracker();
6665
auto node = allocationLocationTracker.getStackTracesTreeNodeForAlloc(
6766
runRes->getPointer());

0 commit comments

Comments
 (0)