@@ -58,6 +58,8 @@ using namespace v8;
58
58
59
59
namespace dd {
60
60
61
+ using ContextPtr = std::shared_ptr<Global<Value>>;
62
+
61
63
// Maximum number of rounds in the GetV8ToEpochOffset
62
64
static constexpr int MAX_EPOCH_OFFSET_ATTEMPTS = 20 ;
63
65
@@ -318,8 +320,7 @@ void SignalHandler::HandleProfilerSignal(int sig,
318
320
auto time_from = Now ();
319
321
old_handler (sig, info, context);
320
322
auto time_to = Now ();
321
- auto async_id = prof->GetAsyncId (isolate);
322
- prof->PushContext (time_from, time_to, cpu_time, async_id);
323
+ prof->PushContext (time_from, time_to, cpu_time, isolate);
323
324
}
324
325
#else
325
326
class SignalHandler {
@@ -516,8 +517,10 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
516
517
bool workaroundV8Bug,
517
518
bool collectCpuTime,
518
519
bool collectAsyncId,
519
- bool isMainThread)
520
+ bool isMainThread,
521
+ bool useCPED)
520
522
: samplingPeriod_(samplingPeriod),
523
+ useCPED_ (useCPED),
521
524
includeLines_(includeLines),
522
525
withContexts_(withContexts),
523
526
isMainThread_(isMainThread) {
@@ -533,7 +536,6 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
533
536
contexts_.reserve (duration * 2 / samplingPeriod);
534
537
}
535
538
536
- curContext_.store (&context1_, std::memory_order_relaxed);
537
539
collectionMode_.store (CollectionMode::kNoCollect , std::memory_order_relaxed);
538
540
gcCount.store (0 , std::memory_order_relaxed);
539
541
@@ -548,7 +550,7 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
548
550
jsArray_ = v8::Global<v8::Uint32Array>(isolate, jsArray);
549
551
std::fill (fields_, fields_ + kFieldCount , 0 );
550
552
551
- if (collectAsyncId_) {
553
+ if (collectAsyncId_ || useCPED_ ) {
552
554
isolate->AddGCPrologueCallback (&GCPrologueCallback, this );
553
555
isolate->AddGCEpilogueCallback (&GCEpilogueCallback, this );
554
556
}
@@ -624,6 +626,7 @@ NAN_METHOD(WallProfiler::New) {
624
626
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (collectCpuTime);
625
627
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (collectAsyncId);
626
628
DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (isMainThread);
629
+ DD_WALL_PROFILER_GET_BOOLEAN_CONFIG (useCPED);
627
630
628
631
if (withContexts && !DD_WALL_USE_SIGPROF) {
629
632
return Nan::ThrowTypeError (" Contexts are not supported." );
@@ -663,7 +666,8 @@ NAN_METHOD(WallProfiler::New) {
663
666
workaroundV8Bug,
664
667
collectCpuTime,
665
668
collectAsyncId,
666
- isMainThread);
669
+ isMainThread,
670
+ useCPED);
667
671
obj->Wrap (info.This ());
668
672
info.GetReturnValue ().Set (info.This ());
669
673
} else {
@@ -978,28 +982,111 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
978
982
}
979
983
980
984
v8::Local<v8::Value> WallProfiler::GetContext (Isolate* isolate) {
981
- auto context = *curContext_. load (std::memory_order_relaxed );
985
+ auto context = GetContextPtr (isolate );
982
986
if (!context) return v8::Undefined (isolate);
983
987
return context->Get (isolate);
984
988
}
985
989
990
+ class PersistentContextPtr : AtomicContextPtr {
991
+ Persistent<Object> per;
992
+
993
+ void BindLifecycleTo (Isolate* isolate, Local<Object>& obj) {
994
+ // Register a callback to delete this object when the object is GCed
995
+ per.Reset (isolate, obj);
996
+ per.SetWeak (
997
+ this ,
998
+ [](const WeakCallbackInfo<PersistentContextPtr>& data) {
999
+ auto & per = data.GetParameter ()->per ;
1000
+ if (!per.IsEmpty ()) {
1001
+ per.ClearWeak ();
1002
+ per.Reset ();
1003
+ }
1004
+ // Using SetSecondPassCallback as shared_ptr can trigger ~Global and
1005
+ // any V8 API use needs to be in the second pass
1006
+ data.SetSecondPassCallback (
1007
+ [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1008
+ delete data.GetParameter ();
1009
+ });
1010
+ },
1011
+ WeakCallbackType::kParameter );
1012
+ }
1013
+
1014
+ friend class WallProfiler ;
1015
+ };
1016
+
986
1017
void WallProfiler::SetContext (Isolate* isolate, Local<Value> value) {
987
- // Need to be careful here, because we might be interrupted by a
988
- // signal handler that will make use of curContext_.
989
- // Update of shared_ptr is not atomic, so instead we use a pointer
990
- // (curContext_) that points on two shared_ptr (context1_ and context2_),
991
- // update the shared_ptr that is not currently in use and then atomically
992
- // update curContext_.
993
- auto newCurContext = curContext_.load (std::memory_order_relaxed) == &context1_
994
- ? &context2_
995
- : &context1_;
996
- if (!value->IsNullOrUndefined ()) {
997
- *newCurContext = std::make_shared<Global<Value>>(isolate, value);
1018
+ if (!useCPED_) {
1019
+ curContext_.Set (isolate, value);
1020
+ return ;
1021
+ }
1022
+
1023
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1024
+ // No Node AsyncContextFrame in this continuation yet
1025
+ if (!cped->IsObject ()) return ;
1026
+
1027
+ auto cpedObj = cped.As <Object>();
1028
+ auto localSymbol = cpedSymbol_.Get (isolate);
1029
+ auto v8Ctx = isolate->GetCurrentContext ();
1030
+ auto maybeProfData = cpedObj->Get (v8Ctx, localSymbol);
1031
+ if (maybeProfData.IsEmpty ()) return ;
1032
+ auto profData = maybeProfData.ToLocalChecked ();
1033
+
1034
+ PersistentContextPtr* contextPtr = nullptr ;
1035
+ if (profData->IsUndefined ()) {
1036
+ contextPtr = new PersistentContextPtr ();
1037
+
1038
+ auto maybeSetResult =
1039
+ cpedObj->Set (v8Ctx, localSymbol, External::New (isolate, contextPtr));
1040
+ if (maybeSetResult.IsNothing ()) {
1041
+ delete contextPtr;
1042
+ return ;
1043
+ }
1044
+ contextPtr->BindLifecycleTo (isolate, cpedObj);
998
1045
} else {
999
- newCurContext->reset ();
1046
+ contextPtr =
1047
+ static_cast <PersistentContextPtr*>(profData.As <External>()->Value ());
1000
1048
}
1001
- std::atomic_signal_fence (std::memory_order_release);
1002
- curContext_.store (newCurContext, std::memory_order_relaxed);
1049
+
1050
+ contextPtr->Set (isolate, value);
1051
+ }
1052
+
1053
+ ContextPtr WallProfiler::GetContextPtrSignalSafe (Isolate* isolate) {
1054
+ if (!useCPED_) {
1055
+ // Not strictly necessary but we can avoid HandleScope creation for this
1056
+ // case.
1057
+ return curContext_.Get ();
1058
+ }
1059
+
1060
+ auto curGcCount = gcCount.load (std::memory_order_relaxed);
1061
+ std::atomic_signal_fence (std::memory_order_acquire);
1062
+ if (curGcCount > 0 ) {
1063
+ return gcContext;
1064
+ } else if (isolate->InContext ()) {
1065
+ auto handleScope = HandleScope (isolate);
1066
+ return GetContextPtr (isolate);
1067
+ }
1068
+ // not in a V8 Context
1069
+ return std::shared_ptr<Global<Value>>();
1070
+ }
1071
+
1072
+ ContextPtr WallProfiler::GetContextPtr (Isolate* isolate) {
1073
+ if (!useCPED_) {
1074
+ return curContext_.Get ();
1075
+ }
1076
+
1077
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1078
+ if (!cped->IsObject ()) return std::shared_ptr<Global<Value>>();
1079
+
1080
+ auto cpedObj = cped.As <Object>();
1081
+ auto localSymbol = cpedSymbol_.Get (isolate);
1082
+ auto maybeProfData = cpedObj->Get (isolate->GetCurrentContext (), localSymbol);
1083
+ if (maybeProfData.IsEmpty ()) return std::shared_ptr<Global<Value>>();
1084
+ auto profData = maybeProfData.ToLocalChecked ();
1085
+
1086
+ if (profData->IsUndefined ()) return std::shared_ptr<Global<Value>>();
1087
+
1088
+ return static_cast <PersistentContextPtr*>(profData.As <External>()->Value ())
1089
+ ->Get ();
1003
1090
}
1004
1091
1005
1092
NAN_GETTER (WallProfiler::GetContext) {
@@ -1050,8 +1137,13 @@ void WallProfiler::OnGCStart(v8::Isolate* isolate) {
1050
1137
auto curCount = gcCount.load (std::memory_order_relaxed);
1051
1138
std::atomic_signal_fence (std::memory_order_acquire);
1052
1139
if (curCount == 0 ) {
1053
- gcAsyncId = GetAsyncIdNoGC (isolate);
1054
- }
1140
+ if (collectAsyncId_) {
1141
+ gcAsyncId = GetAsyncIdNoGC (isolate);
1142
+ }
1143
+ if (useCPED_) {
1144
+ gcContext = GetContextPtrSignalSafe (isolate);
1145
+ }
1146
+ }
1055
1147
gcCount.store (curCount + 1 , std::memory_order_relaxed);
1056
1148
std::atomic_signal_fence (std::memory_order_release);
1057
1149
}
@@ -1060,23 +1152,28 @@ void WallProfiler::OnGCEnd() {
1060
1152
auto newCount = gcCount.load (std::memory_order_relaxed) - 1 ;
1061
1153
std::atomic_signal_fence (std::memory_order_acquire);
1062
1154
gcCount.store (newCount, std::memory_order_relaxed);
1063
- std::atomic_signal_fence (std::memory_order_release);
1064
1155
if (newCount == 0 ) {
1065
1156
gcAsyncId = -1 ;
1157
+ if (useCPED_) {
1158
+ gcContext.reset ();
1159
+ }
1066
1160
}
1161
+ std::atomic_signal_fence (std::memory_order_release);
1067
1162
}
1068
1163
1069
1164
void WallProfiler::PushContext (int64_t time_from,
1070
1165
int64_t time_to,
1071
1166
int64_t cpu_time,
1072
- int64_t async_id ) {
1167
+ Isolate* isolate ) {
1073
1168
// Be careful this is called in a signal handler context therefore all
1074
1169
// operations must be async signal safe (in particular no allocations).
1075
1170
// Our ring buffer avoids allocations.
1076
- auto context = curContext_.load (std::memory_order_relaxed);
1077
- std::atomic_signal_fence (std::memory_order_acquire);
1078
1171
if (contexts_.size () < contexts_.capacity ()) {
1079
- contexts_.push_back ({*context, time_from, time_to, cpu_time, async_id});
1172
+ contexts_.push_back ({GetContextPtrSignalSafe (isolate),
1173
+ time_from,
1174
+ time_to,
1175
+ cpu_time,
1176
+ GetAsyncId (isolate)});
1080
1177
std::atomic_fetch_add_explicit (
1081
1178
reinterpret_cast <std::atomic<uint32_t >*>(&fields_[kSampleCount ]),
1082
1179
1U ,
0 commit comments