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