@@ -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 {
@@ -513,8 +514,10 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
513
514
bool withContexts,
514
515
bool workaroundV8Bug,
515
516
bool collectCpuTime,
516
- bool isMainThread)
517
+ bool isMainThread,
518
+ bool useCPED)
517
519
: samplingPeriod_(samplingPeriod),
520
+ useCPED_ (useCPED),
518
521
includeLines_(includeLines),
519
522
withContexts_(withContexts),
520
523
isMainThread_(isMainThread) {
@@ -529,7 +532,6 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
529
532
contexts_.reserve (duration * 2 / samplingPeriod);
530
533
}
531
534
532
- curContext_.store (&context1_, std::memory_order_relaxed);
533
535
collectionMode_.store (CollectionMode::kNoCollect , std::memory_order_relaxed);
534
536
535
537
// TODO: bind to this isolate? Would fix the Dispose(nullptr) issue.
@@ -543,6 +545,9 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
543
545
jsArray_ = v8::Global<v8::Uint32Array>(isolate, jsArray);
544
546
std::fill (fields_, fields_ + kFieldCount , 0 );
545
547
548
+ if (useCPED_) {
549
+ cpedSymbol_ = v8::Global<v8::Symbol>(isolate, v8::Symbol::New (isolate));
550
+ }
546
551
gcCount = 0 ;
547
552
isolate->AddGCPrologueCallback (&GCPrologueCallback, this );
548
553
isolate->AddGCEpilogueCallback (&GCEpilogueCallback, this );
@@ -676,13 +681,21 @@ NAN_METHOD(WallProfiler::New) {
676
681
" Include line option is not compatible with contexts." );
677
682
}
678
683
684
+ auto useCPEDValue =
685
+ Nan::Get (arg, Nan::New<v8::String>(" useCPED" ).ToLocalChecked ());
686
+ if (useCPEDValue.IsEmpty () || !useCPEDValue.ToLocalChecked ()->IsBoolean ()) {
687
+ return Nan::ThrowTypeError (" useCPED must be a boolean." );
688
+ }
689
+ bool useCPED = useCPEDValue.ToLocalChecked ().As <v8::Boolean>()->Value ();
690
+
679
691
WallProfiler* obj = new WallProfiler (interval,
680
692
duration,
681
693
lineNumbers,
682
694
withContexts,
683
695
workaroundV8Bug,
684
696
collectCpuTime,
685
- isMainThread);
697
+ isMainThread,
698
+ useCPED);
686
699
obj->Wrap (info.This ());
687
700
info.GetReturnValue ().Set (info.This ());
688
701
} else {
@@ -995,28 +1008,109 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
995
1008
}
996
1009
997
1010
v8::Local<v8::Value> WallProfiler::GetContext (Isolate* isolate) {
998
- auto context = *curContext_. load (std::memory_order_relaxed );
1011
+ auto context = GetContextPtr (isolate );
999
1012
if (!context) return v8::Undefined (isolate);
1000
1013
return context->Get (isolate);
1001
1014
}
1002
1015
1016
+ class PersistentContextPtr : AtomicContextPtr {
1017
+ Persistent<Object> per;
1018
+
1019
+ void BindLifecycleTo (Isolate* isolate, Local<Object>& obj) {
1020
+ // Register a callback to delete this object when the object is GCed
1021
+ per.Reset (isolate, obj);
1022
+ per.SetWeak (
1023
+ this ,
1024
+ [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1025
+ auto & per = data.GetParameter ()->per ;
1026
+ if (!per.IsEmpty ()) {
1027
+ per.ClearWeak ();
1028
+ per.Reset ();
1029
+ }
1030
+ // Using SetSecondPassCallback as shared_ptr can trigger ~Global and
1031
+ // any V8 API use needs to be in the second pass
1032
+ data.SetSecondPassCallback (
1033
+ [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1034
+ delete data.GetParameter ();
1035
+ });
1036
+ },
1037
+ WeakCallbackType::kParameter );
1038
+ }
1039
+
1040
+ friend class WallProfiler ;
1041
+ };
1042
+
1003
1043
void WallProfiler::SetContext (Isolate* isolate, Local<Value> value) {
1004
- // Need to be careful here, because we might be interrupted by a
1005
- // signal handler that will make use of curContext_.
1006
- // Update of shared_ptr is not atomic, so instead we use a pointer
1007
- // (curContext_) that points on two shared_ptr (context1_ and context2_),
1008
- // update the shared_ptr that is not currently in use and then atomically
1009
- // update curContext_.
1010
- auto newCurContext = curContext_.load (std::memory_order_relaxed) == &context1_
1011
- ? &context2_
1012
- : &context1_;
1013
- if (!value->IsNullOrUndefined ()) {
1014
- *newCurContext = std::make_shared<Global<Value>>(isolate, value);
1044
+ if (!useCPED_) {
1045
+ curContext_.Set (isolate, value);
1046
+ return ;
1047
+ }
1048
+
1049
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1050
+ // No Node AsyncContextFrame in this continuation yet
1051
+ if (!cped->IsObject ()) return ;
1052
+
1053
+ auto cpedObj = cped.As <Object>();
1054
+ auto localSymbol = cpedSymbol_.Get (isolate);
1055
+ auto v8Ctx = isolate->GetCurrentContext ();
1056
+ auto maybeProfData = cpedObj->Get (v8Ctx, localSymbol);
1057
+ if (maybeProfData.IsEmpty ()) return ;
1058
+ auto profData = maybeProfData.ToLocalChecked ();
1059
+
1060
+ PersistentContextPtr* contextPtr = nullptr ;
1061
+ if (profData->IsUndefined ()) {
1062
+ contextPtr = new PersistentContextPtr ();
1063
+
1064
+ auto maybeSetResult =
1065
+ cpedObj->Set (v8Ctx, localSymbol, External::New (isolate, contextPtr));
1066
+ if (maybeSetResult.IsNothing ()) {
1067
+ delete contextPtr;
1068
+ return ;
1069
+ }
1070
+ contextPtr->BindLifecycleTo (isolate, cpedObj);
1015
1071
} else {
1016
- newCurContext->reset ();
1072
+ contextPtr =
1073
+ static_cast <PersistentContextPtr*>(profData.As <External>()->Value ());
1017
1074
}
1018
- std::atomic_signal_fence (std::memory_order_release);
1019
- curContext_.store (newCurContext, std::memory_order_relaxed);
1075
+
1076
+ contextPtr->Set (isolate, value);
1077
+ }
1078
+
1079
+ ContextPtr WallProfiler::GetContextPtrSignalSafe (Isolate* isolate) {
1080
+ if (!useCPED_) {
1081
+ // Not strictly necessary but we can avoid HandleScope creation for this
1082
+ // case.
1083
+ return curContext_.Get ();
1084
+ }
1085
+
1086
+ if (gcCount > 0 ) {
1087
+ return gcContext;
1088
+ } else if (isolate->InContext ()) {
1089
+ auto handleScope = HandleScope (isolate);
1090
+ return GetContextPtr (isolate);
1091
+ }
1092
+ // not in a V8 Context
1093
+ return std::shared_ptr<Global<Value>>();
1094
+ }
1095
+
1096
+ ContextPtr WallProfiler::GetContextPtr (Isolate* isolate) {
1097
+ if (!useCPED_) {
1098
+ return curContext_.Get ();
1099
+ }
1100
+
1101
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1102
+ if (!cped->IsObject ()) return std::shared_ptr<Global<Value>>();
1103
+
1104
+ auto cpedObj = cped.As <Object>();
1105
+ auto localSymbol = cpedSymbol_.Get (isolate);
1106
+ auto maybeProfData = cpedObj->Get (isolate->GetCurrentContext (), localSymbol);
1107
+ if (maybeProfData.IsEmpty ()) return std::shared_ptr<Global<Value>>();
1108
+ auto profData = maybeProfData.ToLocalChecked ();
1109
+
1110
+ if (profData->IsUndefined ()) return std::shared_ptr<Global<Value>>();
1111
+
1112
+ return static_cast <PersistentContextPtr*>(profData.As <External>()->Value ())
1113
+ ->Get ();
1020
1114
}
1021
1115
1022
1116
NAN_GETTER (WallProfiler::GetContext) {
@@ -1047,14 +1141,16 @@ NAN_METHOD(WallProfiler::Dispose) {
1047
1141
void WallProfiler::PushContext (int64_t time_from,
1048
1142
int64_t time_to,
1049
1143
int64_t cpu_time,
1050
- int64_t async_id ) {
1144
+ Isolate* isolate ) {
1051
1145
// Be careful this is called in a signal handler context therefore all
1052
1146
// operations must be async signal safe (in particular no allocations).
1053
1147
// Our ring buffer avoids allocations.
1054
- auto context = curContext_.load (std::memory_order_relaxed);
1055
- std::atomic_signal_fence (std::memory_order_acquire);
1056
1148
if (contexts_.size () < contexts_.capacity ()) {
1057
- contexts_.push_back ({*context, time_from, time_to, cpu_time, async_id});
1149
+ contexts_.push_back ({GetContextPtrSignalSafe (isolate),
1150
+ time_from,
1151
+ time_to,
1152
+ cpu_time,
1153
+ GetAsyncId (isolate)});
1058
1154
std::atomic_fetch_add_explicit (
1059
1155
reinterpret_cast <std::atomic<uint32_t >*>(&fields_[kSampleCount ]),
1060
1156
1U ,
0 commit comments