@@ -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
@@ -321,8 +323,7 @@ void SignalHandler::HandleProfilerSignal(int sig,
321
323
auto time_from = Now ();
322
324
old_handler (sig, info, context);
323
325
auto time_to = Now ();
324
- double async_id = node::AsyncHooksGetExecutionAsyncId (isolate);
325
- prof->PushContext (time_from, time_to, cpu_time, async_id);
326
+ prof->PushContext (time_from, time_to, cpu_time, isolate);
326
327
}
327
328
#else
328
329
class SignalHandler {
@@ -484,14 +485,24 @@ ContextsByNode WallProfiler::GetContextsByNode(CpuProfile* profile,
484
485
return contextsByNode;
485
486
}
486
487
488
+ void GCPrologueCallback (Isolate* isolate, GCType type, GCCallbackFlags flags, void * data) {
489
+ static_cast <WallProfiler*>(data)->OnGCStart ();
490
+ }
491
+
492
+ void GCEpilogueCallback (Isolate* isolate, GCType type, GCCallbackFlags flags, void * data) {
493
+ static_cast <WallProfiler*>(data)->OnGCEnd ();
494
+ }
495
+
487
496
WallProfiler::WallProfiler (std::chrono::microseconds samplingPeriod,
488
497
std::chrono::microseconds duration,
489
498
bool includeLines,
490
499
bool withContexts,
491
500
bool workaroundV8Bug,
492
501
bool collectCpuTime,
493
- bool isMainThread)
502
+ bool isMainThread,
503
+ bool useCPED)
494
504
: samplingPeriod_(samplingPeriod),
505
+ useCPED_ (useCPED),
495
506
includeLines_(includeLines),
496
507
withContexts_(withContexts),
497
508
isMainThread_(isMainThread) {
@@ -506,7 +517,6 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
506
517
contexts_.reserve (duration * 2 / samplingPeriod);
507
518
}
508
519
509
- curContext_.store (&context1_, std::memory_order_relaxed);
510
520
collectionMode_.store (CollectionMode::kNoCollect , std::memory_order_relaxed);
511
521
512
522
auto isolate = v8::Isolate::GetCurrent ();
@@ -515,13 +525,16 @@ WallProfiler::WallProfiler(std::chrono::microseconds samplingPeriod,
515
525
516
526
v8::Local<v8::Uint32Array> jsArray =
517
527
v8::Uint32Array::New (buffer, 0 , kFieldCount );
518
- #if (V8_MAJOR_VERSION >= 8)
519
528
fields_ = static_cast <uint32_t *>(buffer->GetBackingStore ()->Data ());
520
- #else
521
- fields_ = static_cast <uint32_t *>(buffer->GetContents ().Data ());
522
- #endif
523
529
jsArray_ = v8::Global<v8::Uint32Array>(isolate, jsArray);
524
530
std::fill (fields_, fields_ + kFieldCount , 0 );
531
+
532
+ gcCount = 0 ;
533
+ if (useCPED_) {
534
+ cpedSymbol_ = v8::Global<v8::Symbol>(isolate, v8::Symbol::New (isolate));
535
+ isolate->AddGCPrologueCallback (&GCPrologueCallback, this );
536
+ isolate->AddGCEpilogueCallback (&GCEpilogueCallback, this );
537
+ }
525
538
}
526
539
527
540
WallProfiler::~WallProfiler () {
@@ -535,6 +548,10 @@ void WallProfiler::Dispose(Isolate* isolate) {
535
548
536
549
g_profilers.RemoveProfiler (isolate, this );
537
550
}
551
+ if (isolate != nullptr && useCPED_) {
552
+ isolate->RemoveGCPrologueCallback (&GCPrologueCallback, this );
553
+ isolate->RemoveGCEpilogueCallback (&GCEpilogueCallback, this );
554
+ }
538
555
}
539
556
540
557
NAN_METHOD (WallProfiler::New) {
@@ -647,13 +664,23 @@ NAN_METHOD(WallProfiler::New) {
647
664
" Include line option is not compatible with contexts." );
648
665
}
649
666
667
+ auto useCPEDValue =
668
+ Nan::Get (arg, Nan::New<v8::String>(" useCPED" ).ToLocalChecked ());
669
+ if (useCPEDValue.IsEmpty () ||
670
+ !useCPEDValue.ToLocalChecked ()->IsBoolean ()) {
671
+ return Nan::ThrowTypeError (" useCPED must be a boolean." );
672
+ }
673
+ bool useCPED =
674
+ useCPEDValue.ToLocalChecked ().As <v8::Boolean>()->Value ();
675
+
650
676
WallProfiler* obj = new WallProfiler (interval,
651
677
duration,
652
678
lineNumbers,
653
679
withContexts,
654
680
workaroundV8Bug,
655
681
collectCpuTime,
656
- isMainThread);
682
+ isMainThread,
683
+ useCPED);
657
684
obj->Wrap (info.This ());
658
685
info.GetReturnValue ().Set (info.This ());
659
686
} else {
@@ -966,28 +993,98 @@ v8::CpuProfiler* WallProfiler::CreateV8CpuProfiler() {
966
993
}
967
994
968
995
v8::Local<v8::Value> WallProfiler::GetContext (Isolate* isolate) {
969
- auto context = *curContext_. load (std::memory_order_relaxed );
996
+ auto context = GetContextPtr (isolate );
970
997
if (!context) return v8::Undefined (isolate);
971
998
return context->Get (isolate);
972
999
}
973
1000
1001
+ class PersistentContextPtr : AtomicContextPtr {
1002
+ Persistent<Object> per;
1003
+
1004
+ void BindLifecycleTo (Isolate* isolate, Local<Object>& obj) {
1005
+ // Register a callback to delete this object when the object is GCed
1006
+ per.Reset (isolate, obj);
1007
+ per.SetWeak (this , [](const WeakCallbackInfo<PersistentContextPtr>& data) {
1008
+ auto &per = data.GetParameter ()->per ;
1009
+ if (!per.IsEmpty ()) {
1010
+ per.ClearWeak ();
1011
+ per.Reset ();
1012
+ }
1013
+ // Using SetSecondPassCallback as shared_ptr can trigger ~Global and any V8 API use needs to be in the second pass
1014
+ data.SetSecondPassCallback ([](const WeakCallbackInfo<PersistentContextPtr>& data) {
1015
+ delete data.GetParameter ();
1016
+ });
1017
+ }, WeakCallbackType::kParameter );
1018
+ }
1019
+
1020
+ friend class WallProfiler ;
1021
+ };
1022
+
974
1023
void WallProfiler::SetContext (Isolate* isolate, Local<Value> value) {
975
- // Need to be careful here, because we might be interrupted by a
976
- // signal handler that will make use of curContext_.
977
- // Update of shared_ptr is not atomic, so instead we use a pointer
978
- // (curContext_) that points on two shared_ptr (context1_ and context2_),
979
- // update the shared_ptr that is not currently in use and then atomically
980
- // update curContext_.
981
- auto newCurContext = curContext_.load (std::memory_order_relaxed) == &context1_
982
- ? &context2_
983
- : &context1_;
984
- if (!value->IsNullOrUndefined ()) {
985
- *newCurContext = std::make_shared<Global<Value>>(isolate, value);
1024
+ if (!useCPED_) {
1025
+ curContext_.Set (isolate, value);
1026
+ return ;
1027
+ }
1028
+
1029
+ auto cped = isolate->GetContinuationPreservedEmbedderData ();
1030
+ // No Node AsyncContextFrame in this continuation yet
1031
+ if (!cped->IsObject ()) return ;
1032
+
1033
+ auto cpedObj = cped.As <Object>();
1034
+ auto localSymbol = cpedSymbol_.Get (isolate);
1035
+ auto v8Ctx = isolate->GetCurrentContext ();
1036
+ auto maybeProfData = cpedObj->Get (v8Ctx, localSymbol);
1037
+ if (maybeProfData.IsEmpty ()) return ;
1038
+ auto profData = maybeProfData.ToLocalChecked ();
1039
+
1040
+ PersistentContextPtr* contextPtr = nullptr ;
1041
+ if (profData->IsUndefined ()) {
1042
+ contextPtr = new PersistentContextPtr ();
1043
+
1044
+ auto maybeSetResult = cpedObj->Set (v8Ctx, localSymbol, External::New (isolate, contextPtr));
1045
+ if (maybeSetResult.IsNothing ()) {
1046
+ delete contextPtr;
1047
+ return ;
1048
+ }
1049
+ contextPtr->BindLifecycleTo (isolate, cpedObj);
986
1050
} else {
987
- newCurContext-> reset ( );
1051
+ contextPtr = static_cast <PersistentContextPtr*>(profData. As <External>()-> Value () );
988
1052
}
989
- std::atomic_signal_fence (std::memory_order_release);
990
- curContext_.store (newCurContext, std::memory_order_relaxed);
1053
+
1054
+ contextPtr->Set (isolate, value);
1055
+ }
1056
+
1057
+ ContextPtr WallProfiler::GetContextPtrSignalSafe (Isolate* isolate) {
1058
+ if (!useCPED_) {
1059
+ // Not strictly necessary but we can avoid HandleScope creation for this case.
1060
+ return curContext_.Get ();
1061
+ }
1062
+
1063
+ if (gcCount == 0 ) {
1064
+ auto handleScope = HandleScope (isolate);
1065
+ return GetContextPtr (isolate);
1066
+ }
1067
+
1068
+ return gcContext;
1069
+ }
1070
+
1071
+ ContextPtr WallProfiler::GetContextPtr (Isolate* isolate) {
1072
+ if (!useCPED_) {
1073
+ return curContext_.Get ();
1074
+ }
1075
+
1076
+ auto cped = isolate->GetContinuationPreservedEmbedderData (); // signal safe?
1077
+ if (!cped->IsObject ()) return std::shared_ptr<Global<Value>>();
1078
+
1079
+ auto cpedObj = cped.As <Object>();
1080
+ auto localSymbol = cpedSymbol_.Get (isolate); // signal safe?
1081
+ auto maybeProfData = cpedObj->Get (isolate->GetEnteredOrMicrotaskContext (), localSymbol); // signal safe?
1082
+ if (maybeProfData.IsEmpty ()) return std::shared_ptr<Global<Value>>();
1083
+ auto profData = maybeProfData.ToLocalChecked ();
1084
+
1085
+ if (profData->IsUndefined ()) return std::shared_ptr<Global<Value>>();
1086
+
1087
+ return static_cast <PersistentContextPtr*>(profData.As <External>()->Value ())->Get ();
991
1088
}
992
1089
993
1090
NAN_GETTER (WallProfiler::GetContext) {
@@ -1018,14 +1115,13 @@ NAN_METHOD(WallProfiler::Dispose) {
1018
1115
void WallProfiler::PushContext (int64_t time_from,
1019
1116
int64_t time_to,
1020
1117
int64_t cpu_time,
1021
- double async_id ) {
1118
+ Isolate* isolate ) {
1022
1119
// Be careful this is called in a signal handler context therefore all
1023
1120
// operations must be async signal safe (in particular no allocations).
1024
1121
// Our ring buffer avoids allocations.
1025
- auto context = curContext_.load (std::memory_order_relaxed);
1026
- std::atomic_signal_fence (std::memory_order_acquire);
1027
1122
if (contexts_.size () < contexts_.capacity ()) {
1028
- contexts_.push_back ({*context, time_from, time_to, cpu_time, async_id});
1123
+ double async_id = node::AsyncHooksGetExecutionAsyncId (isolate);
1124
+ contexts_.push_back ({GetContextPtrSignalSafe (isolate), time_from, time_to, cpu_time, async_id});
1029
1125
std::atomic_fetch_add_explicit (
1030
1126
reinterpret_cast <std::atomic<uint32_t >*>(&fields_[kSampleCount ]),
1031
1127
1U ,
0 commit comments