33#include " async_wrap.h"
44#include " base_object-inl.h"
55#include " debug_utils-inl.h"
6+ #include " diagnosticfilename-inl.h"
67#include " memory_tracker-inl.h"
78#include " node_buffer.h"
89#include " node_context_data.h"
2223#include < algorithm>
2324#include < atomic>
2425#include < cstdio>
26+ #include < limits>
2527#include < memory>
2628
2729namespace node {
@@ -465,6 +467,11 @@ Environment::~Environment() {
465467 // FreeEnvironment() should have set this.
466468 CHECK (is_stopping ());
467469
470+ if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) {
471+ isolate_->RemoveNearHeapLimitCallback (Environment::NearHeapLimitCallback,
472+ 0 );
473+ }
474+
468475 isolate ()->GetHeapProfiler ()->RemoveBuildEmbedderGraphCallback (
469476 BuildEmbedderGraph, this );
470477
@@ -1097,6 +1104,25 @@ void Environment::VerifyNoStrongBaseObjects() {
10971104 });
10981105}
10991106
1107+ uint64_t GuessMemoryAvailableToTheProcess () {
1108+ uint64_t free_in_system = uv_get_free_memory ();
1109+ size_t allowed = uv_get_constrained_memory ();
1110+ if (allowed == 0 ) {
1111+ return free_in_system;
1112+ }
1113+ size_t rss;
1114+ int err = uv_resident_set_memory (&rss);
1115+ if (err) {
1116+ return free_in_system;
1117+ }
1118+ if (allowed < rss) {
1119+ // Something is probably wrong. Fallback to the free memory.
1120+ return free_in_system;
1121+ }
1122+ // There may still be room for swap, but we will just leave it here.
1123+ return allowed - rss;
1124+ }
1125+
11001126void Environment::BuildEmbedderGraph (Isolate* isolate,
11011127 EmbedderGraph* graph,
11021128 void * data) {
@@ -1109,6 +1135,126 @@ void Environment::BuildEmbedderGraph(Isolate* isolate,
11091135 });
11101136}
11111137
1138+ size_t Environment::NearHeapLimitCallback (void * data,
1139+ size_t current_heap_limit,
1140+ size_t initial_heap_limit) {
1141+ Environment* env = static_cast <Environment*>(data);
1142+
1143+ Debug (env,
1144+ DebugCategory::DIAGNOSTICS,
1145+ " Invoked NearHeapLimitCallback, processing=%d, "
1146+ " current_limit=%" PRIu64 " , "
1147+ " initial_limit=%" PRIu64 " \n " ,
1148+ env->is_processing_heap_limit_callback_ ,
1149+ static_cast <uint64_t >(current_heap_limit),
1150+ static_cast <uint64_t >(initial_heap_limit));
1151+
1152+ size_t max_young_gen_size = env->isolate_data ()->max_young_gen_size ;
1153+ size_t young_gen_size = 0 ;
1154+ size_t old_gen_size = 0 ;
1155+
1156+ v8::HeapSpaceStatistics stats;
1157+ size_t num_heap_spaces = env->isolate ()->NumberOfHeapSpaces ();
1158+ for (size_t i = 0 ; i < num_heap_spaces; ++i) {
1159+ env->isolate ()->GetHeapSpaceStatistics (&stats, i);
1160+ if (strcmp (stats.space_name (), " new_space" ) == 0 ||
1161+ strcmp (stats.space_name (), " new_large_object_space" ) == 0 ) {
1162+ young_gen_size += stats.space_used_size ();
1163+ } else {
1164+ old_gen_size += stats.space_used_size ();
1165+ }
1166+ }
1167+
1168+ Debug (env,
1169+ DebugCategory::DIAGNOSTICS,
1170+ " max_young_gen_size=%" PRIu64 " , "
1171+ " young_gen_size=%" PRIu64 " , "
1172+ " old_gen_size=%" PRIu64 " , "
1173+ " total_size=%" PRIu64 " \n " ,
1174+ static_cast <uint64_t >(max_young_gen_size),
1175+ static_cast <uint64_t >(young_gen_size),
1176+ static_cast <uint64_t >(old_gen_size),
1177+ static_cast <uint64_t >(young_gen_size + old_gen_size));
1178+
1179+ uint64_t available = GuessMemoryAvailableToTheProcess ();
1180+ // TODO(joyeecheung): get a better estimate about the native memory
1181+ // usage into the overhead, e.g. based on the count of objects.
1182+ uint64_t estimated_overhead = max_young_gen_size;
1183+ Debug (env,
1184+ DebugCategory::DIAGNOSTICS,
1185+ " Estimated available memory=%" PRIu64 " , "
1186+ " estimated overhead=%" PRIu64 " \n " ,
1187+ static_cast <uint64_t >(available),
1188+ static_cast <uint64_t >(estimated_overhead));
1189+
1190+ // This might be hit when the snapshot is being taken in another
1191+ // NearHeapLimitCallback invocation.
1192+ // When taking the snapshot, objects in the young generation may be
1193+ // promoted to the old generation, result in increased heap usage,
1194+ // but it should be no more than the young generation size.
1195+ // Ideally, this should be as small as possible - the heap limit
1196+ // can only be restored when the heap usage falls down below the
1197+ // new limit, so in a heap with unbounded growth the isolate
1198+ // may eventually crash with this new limit - effectively raising
1199+ // the heap limit to the new one.
1200+ if (env->is_processing_heap_limit_callback_ ) {
1201+ size_t new_limit = initial_heap_limit + max_young_gen_size;
1202+ Debug (env,
1203+ DebugCategory::DIAGNOSTICS,
1204+ " Not generating snapshots in nested callback. "
1205+ " new_limit=%" PRIu64 " \n " ,
1206+ static_cast <uint64_t >(new_limit));
1207+ return new_limit;
1208+ }
1209+
1210+ // Estimate whether the snapshot is going to use up all the memory
1211+ // available to the process. If so, just give up to prevent the system
1212+ // from killing the process for a system OOM.
1213+ if (estimated_overhead > available) {
1214+ Debug (env,
1215+ DebugCategory::DIAGNOSTICS,
1216+ " Not generating snapshots because it's too risky.\n " );
1217+ env->isolate ()->RemoveNearHeapLimitCallback (NearHeapLimitCallback,
1218+ initial_heap_limit);
1219+ return current_heap_limit;
1220+ }
1221+
1222+ // Take the snapshot synchronously.
1223+ env->is_processing_heap_limit_callback_ = true ;
1224+
1225+ std::string dir = env->options ()->diagnostic_dir ;
1226+ if (dir.empty ()) {
1227+ dir = env->GetCwd ();
1228+ }
1229+ DiagnosticFilename name (env, " Heap" , " heapsnapshot" );
1230+ std::string filename = dir + kPathSeparator + (*name);
1231+
1232+ Debug (env, DebugCategory::DIAGNOSTICS, " Start generating %s...\n " , *name);
1233+
1234+ // Remove the callback first in case it's triggered when generating
1235+ // the snapshot.
1236+ env->isolate ()->RemoveNearHeapLimitCallback (NearHeapLimitCallback,
1237+ initial_heap_limit);
1238+
1239+ heap::WriteSnapshot (env->isolate (), filename.c_str ());
1240+ env->heap_limit_snapshot_taken_ += 1 ;
1241+
1242+ // Don't take more snapshots than the number specified by
1243+ // --heapsnapshot-near-heap-limit.
1244+ if (env->heap_limit_snapshot_taken_ <
1245+ env->options_ ->heap_snapshot_near_heap_limit ) {
1246+ env->isolate ()->AddNearHeapLimitCallback (NearHeapLimitCallback, env);
1247+ }
1248+
1249+ FPrintF (stderr, " Wrote snapshot to %s\n " , filename.c_str ());
1250+ // Tell V8 to reset the heap limit once the heap usage falls down to
1251+ // 95% of the initial limit.
1252+ env->isolate ()->AutomaticallyRestoreInitialHeapLimit (0.95 );
1253+
1254+ env->is_processing_heap_limit_callback_ = false ;
1255+ return initial_heap_limit;
1256+ }
1257+
11121258inline size_t Environment::SelfSize () const {
11131259 size_t size = sizeof (*this );
11141260 // Remove non pointer fields that will be tracked in MemoryInfo()
0 commit comments