@@ -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
gcCount.store (0 , std::memory_order_relaxed);
535
537
@@ -676,13 +678,21 @@ NAN_METHOD(WallProfiler::New) {
676
678
" Include line option is not compatible with contexts." );
677
679
}
678
680
681
+ auto useCPEDValue =
682
+ Nan::Get (arg, Nan::New<v8::String>(" useCPED" ).ToLocalChecked ());
683
+ if (useCPEDValue.IsEmpty () || !useCPEDValue.ToLocalChecked ()->IsBoolean ()) {
684
+ return Nan::ThrowTypeError (" useCPED must be a boolean." );
685
+ }
686
+ bool useCPED = useCPEDValue.ToLocalChecked ().As <v8::Boolean>()->Value ();
687
+
679
688
WallProfiler* obj = new WallProfiler (interval,
680
689
duration,
681
690
lineNumbers,
682
691
withContexts,
683
692
workaroundV8Bug,
684
693
collectCpuTime,
685
- isMainThread);
694
+ isMainThread,
695
+ useCPED);
686
696
obj->Wrap (info.This ());
687
697
info.GetReturnValue ().Set (info.This ());
688
698
} else {
@@ -995,28 +1005,109 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
995
1005
}
996
1006
997
1007
v8::Local<v8::Value> WallProfiler::GetContext (Isolate* isolate) {
998
- auto context = *curContext_. load (std::memory_order_relaxed );
1008
+ auto context = GetContextPtr (isolate );
999
1009
if (!context) return v8::Undefined (isolate);
1000
1010
return context->Get (isolate);
1001
1011
}
1002
1012
1013
+ class PersistentContextPtr : AtomicContextPtr {
1014
+ Persistent<Object> per;
1015
+
1016
+ void BindLifecycleTo (Isolate* isolate, Local<Object>& obj) {
1017
+ // Register a callback to delete this object when the object is GCed
1018
+ per.Reset (isolate, obj);
1019
+ per.SetWeak (
1020
+ this ,
1021
+ [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1022
+ auto & per = data.GetParameter ()->per ;
1023
+ if (!per.IsEmpty ()) {
1024
+ per.ClearWeak ();
1025
+ per.Reset ();
1026
+ }
1027
+ // Using SetSecondPassCallback as shared_ptr can trigger ~Global and
1028
+ // any V8 API use needs to be in the second pass
1029
+ data.SetSecondPassCallback (
1030
+ [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1031
+ delete data.GetParameter ();
1032
+ });
1033
+ },
1034
+ WeakCallbackType::kParameter );
1035
+ }
1036
+
1037
+ friend class WallProfiler ;
1038
+ };
1039
+
1003
1040
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);
1041
+ if (!useCPED_) {
1042
+ curContext_.Set (isolate, value);
1043
+ return ;
1044
+ }
1045
+
1046
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1047
+ // No Node AsyncContextFrame in this continuation yet
1048
+ if (!cped->IsObject ()) return ;
1049
+
1050
+ auto cpedObj = cped.As <Object>();
1051
+ auto localSymbol = cpedSymbol_.Get (isolate);
1052
+ auto v8Ctx = isolate->GetCurrentContext ();
1053
+ auto maybeProfData = cpedObj->Get (v8Ctx, localSymbol);
1054
+ if (maybeProfData.IsEmpty ()) return ;
1055
+ auto profData = maybeProfData.ToLocalChecked ();
1056
+
1057
+ PersistentContextPtr* contextPtr = nullptr ;
1058
+ if (profData->IsUndefined ()) {
1059
+ contextPtr = new PersistentContextPtr ();
1060
+
1061
+ auto maybeSetResult =
1062
+ cpedObj->Set (v8Ctx, localSymbol, External::New (isolate, contextPtr));
1063
+ if (maybeSetResult.IsNothing ()) {
1064
+ delete contextPtr;
1065
+ return ;
1066
+ }
1067
+ contextPtr->BindLifecycleTo (isolate, cpedObj);
1015
1068
} else {
1016
- newCurContext->reset ();
1069
+ contextPtr =
1070
+ static_cast <PersistentContextPtr*>(profData.As <External>()->Value ());
1017
1071
}
1018
- std::atomic_signal_fence (std::memory_order_release);
1019
- curContext_.store (newCurContext, std::memory_order_relaxed);
1072
+
1073
+ contextPtr->Set (isolate, value);
1074
+ }
1075
+
1076
+ ContextPtr WallProfiler::GetContextPtrSignalSafe (Isolate* isolate) {
1077
+ if (!useCPED_) {
1078
+ // Not strictly necessary but we can avoid HandleScope creation for this
1079
+ // case.
1080
+ return curContext_.Get ();
1081
+ }
1082
+
1083
+ if (gcCount > 0 ) {
1084
+ return gcContext;
1085
+ } else if (isolate->InContext ()) {
1086
+ auto handleScope = HandleScope (isolate);
1087
+ return GetContextPtr (isolate);
1088
+ }
1089
+ // not in a V8 Context
1090
+ return std::shared_ptr<Global<Value>>();
1091
+ }
1092
+
1093
+ ContextPtr WallProfiler::GetContextPtr (Isolate* isolate) {
1094
+ if (!useCPED_) {
1095
+ return curContext_.Get ();
1096
+ }
1097
+
1098
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1099
+ if (!cped->IsObject ()) return std::shared_ptr<Global<Value>>();
1100
+
1101
+ auto cpedObj = cped.As <Object>();
1102
+ auto localSymbol = cpedSymbol_.Get (isolate);
1103
+ auto maybeProfData = cpedObj->Get (isolate->GetCurrentContext (), localSymbol);
1104
+ if (maybeProfData.IsEmpty ()) return std::shared_ptr<Global<Value>>();
1105
+ auto profData = maybeProfData.ToLocalChecked ();
1106
+
1107
+ if (profData->IsUndefined ()) return std::shared_ptr<Global<Value>>();
1108
+
1109
+ return static_cast <PersistentContextPtr*>(profData.As <External>()->Value ())
1110
+ ->Get ();
1020
1111
}
1021
1112
1022
1113
NAN_GETTER (WallProfiler::GetContext) {
@@ -1065,7 +1156,10 @@ void WallProfiler::OnGCStart(v8::Isolate* isolate) {
1065
1156
std::atomic_signal_fence (std::memory_order_acquire);
1066
1157
if (curCount == 0 ) {
1067
1158
gcAsyncId = GetAsyncIdNoGC (isolate);
1068
- }
1159
+ if (useCPED_) {
1160
+ gcContext = GetContextPtrSignalSafe (isolate);
1161
+ }
1162
+ }
1069
1163
gcCount.store (curCount + 1 , std::memory_order_relaxed);
1070
1164
std::atomic_signal_fence (std::memory_order_release);
1071
1165
}
@@ -1077,20 +1171,25 @@ void WallProfiler::OnGCEnd() {
1077
1171
std::atomic_signal_fence (std::memory_order_release);
1078
1172
if (newCount == 0 ) {
1079
1173
gcAsyncId = -1 ;
1080
- }
1174
+ if (useCPED_) {
1175
+ gcContext.reset ();
1176
+ }
1177
+ }
1081
1178
}
1082
1179
1083
1180
void WallProfiler::PushContext (int64_t time_from,
1084
1181
int64_t time_to,
1085
1182
int64_t cpu_time,
1086
- int64_t async_id ) {
1183
+ Isolate* isolate ) {
1087
1184
// Be careful this is called in a signal handler context therefore all
1088
1185
// operations must be async signal safe (in particular no allocations).
1089
1186
// Our ring buffer avoids allocations.
1090
- auto context = curContext_.load (std::memory_order_relaxed);
1091
- std::atomic_signal_fence (std::memory_order_acquire);
1092
1187
if (contexts_.size () < contexts_.capacity ()) {
1093
- contexts_.push_back ({*context, time_from, time_to, cpu_time, async_id});
1188
+ contexts_.push_back ({GetContextPtrSignalSafe (isolate),
1189
+ time_from,
1190
+ time_to,
1191
+ cpu_time,
1192
+ GetAsyncId (isolate)});
1094
1193
std::atomic_fetch_add_explicit (
1095
1194
reinterpret_cast <std::atomic<uint32_t >*>(&fields_[kSampleCount ]),
1096
1195
1U ,
0 commit comments