Skip to content

Commit bfa23b5

Browse files
committed
Store profiling context in the CPED
1 parent 51fa650 commit bfa23b5

File tree

2 files changed

+125
-18
lines changed

2 files changed

+125
-18
lines changed

bindings/profilers/wall.cc

Lines changed: 107 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ void SignalHandler::HandleProfilerSignal(int sig,
323323
auto time_from = Now();
324324
old_handler(sig, info, context);
325325
auto time_to = Now();
326-
prof->PushContext(time_from, time_to, cpu_time);
326+
prof->PushContext(time_from, time_to, cpu_time, isolate);
327327
}
328328
#else
329329
class SignalHandler {
@@ -472,6 +472,14 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile,
472472
return contextsByNode;
473473
}
474474

475+
void GCPrologueCallback(Isolate* isolate, GCType type, GCCallbackFlags flags, void* data) {
476+
static_cast<WallProfiler*>(data)->OnGCStart();
477+
}
478+
479+
void GCEpilogueCallback(Isolate* isolate, GCType type, GCCallbackFlags flags, void* data) {
480+
static_cast<WallProfiler*>(data)->OnGCEnd();
481+
}
482+
475483
WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
476484
std::chrono::microseconds duration,
477485
bool includeLines,
@@ -509,6 +517,11 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
509517
#endif
510518
jsArray_ = v8::Global<v8::Uint32Array>(isolate, jsArray);
511519
std::fill(fields_, fields_ + kFieldCount, 0);
520+
521+
cpedSymbol_ = v8::Global<v8::Symbol>(isolate, v8::Symbol::New(isolate));
522+
gcCount = 0;
523+
isolate->AddGCPrologueCallback(&GCPrologueCallback, this);
524+
isolate->AddGCEpilogueCallback(&GCEpilogueCallback, this);
512525
}
513526

514527
WallProfiler::~WallProfiler() {
@@ -522,6 +535,10 @@ void WallProfiler::Dispose(Isolate* isolate) {
522535

523536
g_profilers.RemoveProfiler(isolate, this);
524537
}
538+
if (isolate != nullptr) {
539+
isolate->RemoveGCPrologueCallback(&GCPrologueCallback, this);
540+
isolate->RemoveGCEpilogueCallback(&GCEpilogueCallback, this);
541+
}
525542
}
526543

527544
NAN_METHOD(WallProfiler::New) {
@@ -953,25 +970,99 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
953970
}
954971

955972
v8::Local<v8::Value> WallProfiler::GetContext(Isolate* isolate) {
956-
auto context = GetContextPtr();
973+
auto context = GetContextPtr(isolate);
957974
if (!context) return v8::Undefined(isolate);
958975
return context->Get(isolate);
959976
}
960977

978+
struct PersistentContextPtr {
979+
ContextPtr ptr1;
980+
ContextPtr ptr2;
981+
std::atomic<ContextPtr*> currentPtr;
982+
Persistent<Object> per;
983+
984+
void Set(Isolate* isolate, Local<Value> value) {
985+
auto newPtr = currentPtr.load(std::memory_order_relaxed) == &ptr1 ? &ptr2 : &ptr1;
986+
if (!value->IsNullOrUndefined()) {
987+
*newPtr = std::make_shared<Global<Value>>(isolate, value);
988+
} else {
989+
newPtr->reset();
990+
}
991+
std::atomic_signal_fence(std::memory_order_release);
992+
currentPtr.store(newPtr, std::memory_order_relaxed);
993+
}
994+
995+
ContextPtr Get() {
996+
auto ptr = currentPtr.load(std::memory_order_relaxed);
997+
std::atomic_signal_fence(std::memory_order_acquire);
998+
return ptr ? *ptr : std::shared_ptr<Global<Value>>();
999+
}
1000+
};
1001+
9611002
void WallProfiler::SetContext(Isolate* isolate, Local<Value> value) {
962-
std::atomic_signal_fence(std::memory_order_release);
963-
std::atomic_store_explicit(
964-
&curContext_,
965-
value->IsNullOrUndefined()
966-
? std::shared_ptr<Global<Value>>()
967-
: std::make_shared<Global<Value>>(isolate, value),
968-
std::memory_order_relaxed);
1003+
auto cped = isolate->GetContinuationPreservedEmbedderData();
1004+
// No Node AsyncContextFrame in this continuation yet
1005+
if (!cped->IsObject()) return;
1006+
1007+
auto cpedObj = cped.As<Object>();
1008+
auto localSymbol = cpedSymbol_.Get(isolate);
1009+
auto v8Ctx = isolate->GetCurrentContext();
1010+
auto maybeProfData = cpedObj->Get(v8Ctx, localSymbol);
1011+
if (maybeProfData.IsEmpty()) return;
1012+
auto profData = maybeProfData.ToLocalChecked();
1013+
1014+
PersistentContextPtr* contextPtr = nullptr;
1015+
if (profData->IsUndefined()) {
1016+
contextPtr = new PersistentContextPtr();
1017+
1018+
auto maybeSetResult = cpedObj->Set(v8Ctx, localSymbol, External::New(isolate, contextPtr));
1019+
if (maybeSetResult.IsNothing()) {
1020+
delete contextPtr;
1021+
return;
1022+
}
1023+
1024+
// Register a callback to delete contextPtr when the CPED object is GCed
1025+
contextPtr->per.Reset(isolate, cpedObj);
1026+
contextPtr->per.SetWeak(contextPtr, [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1027+
auto &per = data.GetParameter()->per;
1028+
if (!per.IsEmpty()) {
1029+
per.ClearWeak();
1030+
per.Reset();
1031+
}
1032+
// Using SetSecondPassCallback as shared_ptr can trigger ~Global and any V8 API use needs to be in the second pass
1033+
data.SetSecondPassCallback([](const WeakCallbackInfo<PersistentContextPtr>& data) {
1034+
delete data.GetParameter();
1035+
});
1036+
}, WeakCallbackType::kParameter);
1037+
} else {
1038+
contextPtr = static_cast<PersistentContextPtr*>(profData.As<External>()->Value());
1039+
}
1040+
1041+
contextPtr->Set(isolate, value);
1042+
9691043
}
9701044

971-
ContextPtr WallProfiler::GetContextPtr() {
972-
auto contextPtr = atomic_load_explicit(&curContext_, std::memory_order_relaxed);
973-
std::atomic_signal_fence(std::memory_order_acquire);
974-
return contextPtr;
1045+
ContextPtr WallProfiler::GetContextPtrSignalSafe(Isolate* isolate) {
1046+
if (gcCount == 0) {
1047+
auto handleScope = HandleScope(isolate);
1048+
return GetContextPtr(isolate);
1049+
}
1050+
return gcContext;
1051+
}
1052+
1053+
ContextPtr WallProfiler::GetContextPtr(Isolate* isolate) {
1054+
auto cped = isolate->GetContinuationPreservedEmbedderData(); // signal safe?
1055+
if (!cped->IsObject()) return std::shared_ptr<Global<Value>>();
1056+
1057+
auto cpedObj = cped.As<Object>();
1058+
auto localSymbol = cpedSymbol_.Get(isolate); // signal safe?
1059+
auto maybeProfData = cpedObj->Get(isolate->GetEnteredOrMicrotaskContext(), localSymbol); // signal safe?
1060+
if (maybeProfData.IsEmpty()) return std::shared_ptr<Global<Value>>();
1061+
auto profData = maybeProfData.ToLocalChecked();
1062+
1063+
if (profData->IsUndefined()) return std::shared_ptr<Global<Value>>();
1064+
1065+
return static_cast<PersistentContextPtr*>(profData.As<External>()->Value())->Get();
9751066
}
9761067

9771068
NAN_GETTER(WallProfiler::GetContext) {
@@ -1001,12 +1092,13 @@ NAN_METHOD(WallProfiler::Dispose) {
10011092

10021093
void WallProfiler::PushContext(int64_t time_from,
10031094
int64_t time_to,
1004-
int64_t cpu_time) {
1095+
int64_t cpu_time,
1096+
Isolate* isolate) {
10051097
// Be careful this is called in a signal handler context therefore all
10061098
// operations must be async signal safe (in particular no allocations).
10071099
// Our ring buffer avoids allocations.
10081100
if (contexts_.size() < contexts_.capacity()) {
1009-
contexts_.push_back({GetContextPtr(), time_from, time_to, cpu_time});
1101+
contexts_.push_back({GetContextPtrSignalSafe(isolate), time_from, time_to, cpu_time});
10101102
std::atomic_fetch_add_explicit(
10111103
reinterpret_cast<std::atomic<uint32_t>*>(&fields_[kSampleCount]),
10121104
1U,

bindings/profilers/wall.hh

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ class WallProfiler : public Nan::ObjectWrap {
5151
// avoid heap allocation. Need to figure out the right move/copy semantics in
5252
// and out of the ring buffer.
5353

54-
ContextPtr curContext_;
54+
v8::Global<v8::Symbol> cpedSymbol_;
55+
std::atomic<int> gcCount = 0;
56+
ContextPtr gcContext;
5557

5658
std::atomic<CollectionMode> collectionMode_;
5759
std::atomic<uint64_t> noCollectCallCount_;
@@ -95,7 +97,8 @@ class WallProfiler : public Nan::ObjectWrap {
9597
int64_t startCpuTime);
9698

9799
bool waitForSignal(uint64_t targetCallCount = 0);
98-
ContextPtr GetContextPtr();
100+
ContextPtr GetContextPtr(v8::Isolate* isolate);
101+
ContextPtr GetContextPtrSignalSafe(v8::Isolate* isolate);
99102

100103
public:
101104
/**
@@ -115,7 +118,7 @@ class WallProfiler : public Nan::ObjectWrap {
115118

116119
v8::Local<v8::Value> GetContext(v8::Isolate*);
117120
void SetContext(v8::Isolate*, v8::Local<v8::Value>);
118-
void PushContext(int64_t time_from, int64_t time_to, int64_t cpu_time);
121+
void PushContext(int64_t time_from, int64_t time_to, int64_t cpu_time, v8::Isolate* isolate);
119122
Result StartImpl();
120123
std::string StartInternal();
121124
Result StopImpl(bool restart, v8::Local<v8::Value>& profile);
@@ -139,6 +142,18 @@ class WallProfiler : public Nan::ObjectWrap {
139142
return threadCpuStopWatch_.GetAndReset();
140143
}
141144

145+
void OnGCStart() {
146+
if (gcCount++ == 0) {
147+
gcContext = GetContextPtr(v8::Isolate::GetCurrent());
148+
}
149+
}
150+
151+
void OnGCEnd() {
152+
if (--gcCount == 0) {
153+
gcContext.reset();
154+
}
155+
}
156+
142157
static NAN_METHOD(New);
143158
static NAN_METHOD(Start);
144159
static NAN_METHOD(Stop);

0 commit comments

Comments
 (0)