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"
2425#include < cinttypes>
2526#include < cstdio>
2627#include < iostream>
28+ #include < limits>
2729#include < memory>
2830
2931namespace node {
@@ -479,6 +481,11 @@ Environment::~Environment() {
479481 // FreeEnvironment() should have set this.
480482 CHECK (is_stopping ());
481483
484+ if (options_->heap_snapshot_near_heap_limit > heap_limit_snapshot_taken_) {
485+ isolate_->RemoveNearHeapLimitCallback (Environment::NearHeapLimitCallback,
486+ 0 );
487+ }
488+
482489 isolate ()->GetHeapProfiler ()->RemoveBuildEmbedderGraphCallback (
483490 BuildEmbedderGraph, this );
484491
@@ -1402,6 +1409,25 @@ void Environment::DeserializeProperties(const EnvSerializeInfo* info) {
14021409 CHECK_EQ (ctx_from_snapshot, ctx);
14031410}
14041411
1412+ uint64_t GuessMemoryAvailableToTheProcess () {
1413+ uint64_t free_in_system = uv_get_free_memory ();
1414+ size_t allowed = uv_get_constrained_memory ();
1415+ if (allowed == 0 ) {
1416+ return free_in_system;
1417+ }
1418+ size_t rss;
1419+ int err = uv_resident_set_memory (&rss);
1420+ if (err) {
1421+ return free_in_system;
1422+ }
1423+ if (allowed < rss) {
1424+ // Something is probably wrong. Fallback to the free memory.
1425+ return free_in_system;
1426+ }
1427+ // There may still be room for swap, but we will just leave it here.
1428+ return allowed - rss;
1429+ }
1430+
14051431void Environment::BuildEmbedderGraph (Isolate* isolate,
14061432 EmbedderGraph* graph,
14071433 void * data) {
@@ -1414,6 +1440,126 @@ void Environment::BuildEmbedderGraph(Isolate* isolate,
14141440 });
14151441}
14161442
1443+ size_t Environment::NearHeapLimitCallback (void * data,
1444+ size_t current_heap_limit,
1445+ size_t initial_heap_limit) {
1446+ Environment* env = static_cast <Environment*>(data);
1447+
1448+ Debug (env,
1449+ DebugCategory::DIAGNOSTICS,
1450+ " Invoked NearHeapLimitCallback, processing=%d, "
1451+ " current_limit=%" PRIu64 " , "
1452+ " initial_limit=%" PRIu64 " \n " ,
1453+ env->is_processing_heap_limit_callback_ ,
1454+ static_cast <uint64_t >(current_heap_limit),
1455+ static_cast <uint64_t >(initial_heap_limit));
1456+
1457+ size_t max_young_gen_size = env->isolate_data ()->max_young_gen_size ;
1458+ size_t young_gen_size = 0 ;
1459+ size_t old_gen_size = 0 ;
1460+
1461+ v8::HeapSpaceStatistics stats;
1462+ size_t num_heap_spaces = env->isolate ()->NumberOfHeapSpaces ();
1463+ for (size_t i = 0 ; i < num_heap_spaces; ++i) {
1464+ env->isolate ()->GetHeapSpaceStatistics (&stats, i);
1465+ if (strcmp (stats.space_name (), " new_space" ) == 0 ||
1466+ strcmp (stats.space_name (), " new_large_object_space" ) == 0 ) {
1467+ young_gen_size += stats.space_used_size ();
1468+ } else {
1469+ old_gen_size += stats.space_used_size ();
1470+ }
1471+ }
1472+
1473+ Debug (env,
1474+ DebugCategory::DIAGNOSTICS,
1475+ " max_young_gen_size=%" PRIu64 " , "
1476+ " young_gen_size=%" PRIu64 " , "
1477+ " old_gen_size=%" PRIu64 " , "
1478+ " total_size=%" PRIu64 " \n " ,
1479+ static_cast <uint64_t >(max_young_gen_size),
1480+ static_cast <uint64_t >(young_gen_size),
1481+ static_cast <uint64_t >(old_gen_size),
1482+ static_cast <uint64_t >(young_gen_size + old_gen_size));
1483+
1484+ uint64_t available = GuessMemoryAvailableToTheProcess ();
1485+ // TODO(joyeecheung): get a better estimate about the native memory
1486+ // usage into the overhead, e.g. based on the count of objects.
1487+ uint64_t estimated_overhead = max_young_gen_size;
1488+ Debug (env,
1489+ DebugCategory::DIAGNOSTICS,
1490+ " Estimated available memory=%" PRIu64 " , "
1491+ " estimated overhead=%" PRIu64 " \n " ,
1492+ static_cast <uint64_t >(available),
1493+ static_cast <uint64_t >(estimated_overhead));
1494+
1495+ // This might be hit when the snapshot is being taken in another
1496+ // NearHeapLimitCallback invocation.
1497+ // When taking the snapshot, objects in the young generation may be
1498+ // promoted to the old generation, result in increased heap usage,
1499+ // but it should be no more than the young generation size.
1500+ // Ideally, this should be as small as possible - the heap limit
1501+ // can only be restored when the heap usage falls down below the
1502+ // new limit, so in a heap with unbounded growth the isolate
1503+ // may eventually crash with this new limit - effectively raising
1504+ // the heap limit to the new one.
1505+ if (env->is_processing_heap_limit_callback_ ) {
1506+ size_t new_limit = initial_heap_limit + max_young_gen_size;
1507+ Debug (env,
1508+ DebugCategory::DIAGNOSTICS,
1509+ " Not generating snapshots in nested callback. "
1510+ " new_limit=%" PRIu64 " \n " ,
1511+ static_cast <uint64_t >(new_limit));
1512+ return new_limit;
1513+ }
1514+
1515+ // Estimate whether the snapshot is going to use up all the memory
1516+ // available to the process. If so, just give up to prevent the system
1517+ // from killing the process for a system OOM.
1518+ if (estimated_overhead > available) {
1519+ Debug (env,
1520+ DebugCategory::DIAGNOSTICS,
1521+ " Not generating snapshots because it's too risky.\n " );
1522+ env->isolate ()->RemoveNearHeapLimitCallback (NearHeapLimitCallback,
1523+ initial_heap_limit);
1524+ return current_heap_limit;
1525+ }
1526+
1527+ // Take the snapshot synchronously.
1528+ env->is_processing_heap_limit_callback_ = true ;
1529+
1530+ std::string dir = env->options ()->diagnostic_dir ;
1531+ if (dir.empty ()) {
1532+ dir = env->GetCwd ();
1533+ }
1534+ DiagnosticFilename name (env, " Heap" , " heapsnapshot" );
1535+ std::string filename = dir + kPathSeparator + (*name);
1536+
1537+ Debug (env, DebugCategory::DIAGNOSTICS, " Start generating %s...\n " , *name);
1538+
1539+ // Remove the callback first in case it's triggered when generating
1540+ // the snapshot.
1541+ env->isolate ()->RemoveNearHeapLimitCallback (NearHeapLimitCallback,
1542+ initial_heap_limit);
1543+
1544+ heap::WriteSnapshot (env->isolate (), filename.c_str ());
1545+ env->heap_limit_snapshot_taken_ += 1 ;
1546+
1547+ // Don't take more snapshots than the number specified by
1548+ // --heapsnapshot-near-heap-limit.
1549+ if (env->heap_limit_snapshot_taken_ <
1550+ env->options_ ->heap_snapshot_near_heap_limit ) {
1551+ env->isolate ()->AddNearHeapLimitCallback (NearHeapLimitCallback, env);
1552+ }
1553+
1554+ FPrintF (stderr, " Wrote snapshot to %s\n " , filename.c_str ());
1555+ // Tell V8 to reset the heap limit once the heap usage falls down to
1556+ // 95% of the initial limit.
1557+ env->isolate ()->AutomaticallyRestoreInitialHeapLimit (0.95 );
1558+
1559+ env->is_processing_heap_limit_callback_ = false ;
1560+ return initial_heap_limit;
1561+ }
1562+
14171563inline size_t Environment::SelfSize () const {
14181564 size_t size = sizeof (*this );
14191565 // Remove non pointer fields that will be tracked in MemoryInfo()
0 commit comments