Skip to content

Commit ea2957f

Browse files
joyeecheungjuanarbol
authored andcommitted
src: make NearHeapLimitCallback() more robust
Instead of removing the callback before generating heap snapshot and then adding it back after the heap snapshot is generated, just remove it once the heap snapshot limit is reached. Otherwise if the worker callback kicks in and sets the heap limit to higher value during the heap snapshot generation, the current_heap_limit in the heap snapshot callback becomes invalid, and we might return a heap limit lower than the current one, resulting in OOM. In addition add more logs and checks in Worker::NearHeapLimit() to help us catch problems. PR-URL: #44581 Refs: nodejs/reliability#372 Reviewed-By: theanarkh <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent f05ce2b commit ea2957f

File tree

4 files changed

+38
-19
lines changed

4 files changed

+38
-19
lines changed

src/env-inl.h

+4
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,10 @@ inline void Environment::set_heap_snapshot_near_heap_limit(uint32_t limit) {
900900
heap_snapshot_near_heap_limit_ = limit;
901901
}
902902

903+
inline bool Environment::is_in_heapsnapshot_heap_limit_callback() const {
904+
return is_in_heapsnapshot_heap_limit_callback_;
905+
}
906+
903907
inline void Environment::AddHeapSnapshotNearHeapLimitCallback() {
904908
DCHECK(!heapsnapshot_near_heap_limit_callback_added_);
905909
heapsnapshot_near_heap_limit_callback_added_ = true;

src/env.cc

+20-16
Original file line numberDiff line numberDiff line change
@@ -1962,7 +1962,7 @@ size_t Environment::NearHeapLimitCallback(void* data,
19621962
"Invoked NearHeapLimitCallback, processing=%d, "
19631963
"current_limit=%" PRIu64 ", "
19641964
"initial_limit=%" PRIu64 "\n",
1965-
env->is_processing_heap_limit_callback_,
1965+
env->is_in_heapsnapshot_heap_limit_callback_,
19661966
static_cast<uint64_t>(current_heap_limit),
19671967
static_cast<uint64_t>(initial_heap_limit));
19681968

@@ -2014,8 +2014,8 @@ size_t Environment::NearHeapLimitCallback(void* data,
20142014
// new limit, so in a heap with unbounded growth the isolate
20152015
// may eventually crash with this new limit - effectively raising
20162016
// the heap limit to the new one.
2017-
if (env->is_processing_heap_limit_callback_) {
2018-
size_t new_limit = current_heap_limit + max_young_gen_size;
2017+
size_t new_limit = current_heap_limit + max_young_gen_size;
2018+
if (env->is_in_heapsnapshot_heap_limit_callback_) {
20192019
Debug(env,
20202020
DebugCategory::DIAGNOSTICS,
20212021
"Not generating snapshots in nested callback. "
@@ -2031,14 +2031,14 @@ size_t Environment::NearHeapLimitCallback(void* data,
20312031
Debug(env,
20322032
DebugCategory::DIAGNOSTICS,
20332033
"Not generating snapshots because it's too risky.\n");
2034-
env->RemoveHeapSnapshotNearHeapLimitCallback(initial_heap_limit);
2034+
env->RemoveHeapSnapshotNearHeapLimitCallback(0);
20352035
// The new limit must be higher than current_heap_limit or V8 might
20362036
// crash.
2037-
return current_heap_limit + 1;
2037+
return new_limit;
20382038
}
20392039

20402040
// Take the snapshot synchronously.
2041-
env->is_processing_heap_limit_callback_ = true;
2041+
env->is_in_heapsnapshot_heap_limit_callback_ = true;
20422042

20432043
std::string dir = env->options()->diagnostic_dir;
20442044
if (dir.empty()) {
@@ -2049,29 +2049,33 @@ size_t Environment::NearHeapLimitCallback(void* data,
20492049

20502050
Debug(env, DebugCategory::DIAGNOSTICS, "Start generating %s...\n", *name);
20512051

2052-
// Remove the callback first in case it's triggered when generating
2053-
// the snapshot.
2054-
env->RemoveHeapSnapshotNearHeapLimitCallback(initial_heap_limit);
2055-
20562052
heap::WriteSnapshot(env->isolate(), filename.c_str());
20572053
env->heap_limit_snapshot_taken_ += 1;
20582054

2059-
// Don't take more snapshots than the number specified by
2060-
// --heapsnapshot-near-heap-limit.
2061-
if (env->heap_limit_snapshot_taken_ < env->heap_snapshot_near_heap_limit_) {
2062-
env->AddHeapSnapshotNearHeapLimitCallback();
2055+
Debug(env,
2056+
DebugCategory::DIAGNOSTICS,
2057+
"%" PRIu32 "/%" PRIu32 " snapshots taken.\n",
2058+
env->heap_limit_snapshot_taken_,
2059+
env->heap_snapshot_near_heap_limit_);
2060+
2061+
// Don't take more snapshots than the limit specified.
2062+
if (env->heap_limit_snapshot_taken_ == env->heap_snapshot_near_heap_limit_) {
2063+
Debug(env,
2064+
DebugCategory::DIAGNOSTICS,
2065+
"Removing the near heap limit callback");
2066+
env->RemoveHeapSnapshotNearHeapLimitCallback(0);
20632067
}
20642068

20652069
FPrintF(stderr, "Wrote snapshot to %s\n", filename.c_str());
20662070
// Tell V8 to reset the heap limit once the heap usage falls down to
20672071
// 95% of the initial limit.
20682072
env->isolate()->AutomaticallyRestoreInitialHeapLimit(0.95);
20692073

2070-
env->is_processing_heap_limit_callback_ = false;
2074+
env->is_in_heapsnapshot_heap_limit_callback_ = false;
20712075

20722076
// The new limit must be higher than current_heap_limit or V8 might
20732077
// crash.
2074-
return current_heap_limit + 1;
2078+
return new_limit;
20752079
}
20762080

20772081
inline size_t Environment::SelfSize() const {

src/env.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,7 @@ class Environment : public MemoryRetainer {
14601460
void ForEachBindingData(T&& iterator);
14611461

14621462
inline void set_heap_snapshot_near_heap_limit(uint32_t limit);
1463+
inline bool is_in_heapsnapshot_heap_limit_callback() const;
14631464

14641465
inline void AddHeapSnapshotNearHeapLimitCallback();
14651466

@@ -1521,7 +1522,7 @@ class Environment : public MemoryRetainer {
15211522
std::vector<std::string> argv_;
15221523
std::string exec_path_;
15231524

1524-
bool is_processing_heap_limit_callback_ = false;
1525+
bool is_in_heapsnapshot_heap_limit_callback_ = false;
15251526
uint32_t heap_limit_snapshot_taken_ = 0;
15261527
uint32_t heap_snapshot_near_heap_limit_ = 0;
15271528
bool heapsnapshot_near_heap_limit_callback_added_ = false;

src/node_worker.cc

+12-2
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,21 @@ class WorkerThreadData {
253253
size_t Worker::NearHeapLimit(void* data, size_t current_heap_limit,
254254
size_t initial_heap_limit) {
255255
Worker* worker = static_cast<Worker*>(data);
256-
worker->Exit(1, "ERR_WORKER_OUT_OF_MEMORY", "JS heap out of memory");
257256
// Give the current GC some extra leeway to let it finish rather than
258257
// crash hard. We are not going to perform further allocations anyway.
259258
constexpr size_t kExtraHeapAllowance = 16 * 1024 * 1024;
260-
return current_heap_limit + kExtraHeapAllowance;
259+
size_t new_limit = current_heap_limit + kExtraHeapAllowance;
260+
Environment* env = worker->env();
261+
if (env != nullptr) {
262+
DCHECK(!env->is_in_heapsnapshot_heap_limit_callback());
263+
Debug(env,
264+
DebugCategory::DIAGNOSTICS,
265+
"Throwing ERR_WORKER_OUT_OF_MEMORY, "
266+
"new_limit=%" PRIu64 "\n",
267+
static_cast<uint64_t>(new_limit));
268+
}
269+
worker->Exit(1, "ERR_WORKER_OUT_OF_MEMORY", "JS heap out of memory");
270+
return new_limit;
261271
}
262272

263273
void Worker::Run() {

0 commit comments

Comments
 (0)