Skip to content

Commit 0cda701

Browse files
committed
src: fix external reference list race between concurrent isolates
Two threads creating their first isolate at the same time (two `CommonEnvironmentSetup`s on their own threads, or an embedder's setup racing a Worker) could corrupt or misread the external reference list handed to V8: `SnapshotBuilder::CollectExternalReferences()` creates its registry in a thread-safe function static, but then calls `external_references()` on every call, and that method appends the terminating nullptr and flips `is_finalized_` the first time through without any locking, so both threads can append, or one can read the vector while the other reallocates it. TSAN reports it for any two concurrent setups. Keep the finalized list in a second function static so finalization runs exactly once, under that static's initialization guard. Refs: #32984 Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
1 parent 654a6cf commit 0cda701

2 files changed

Lines changed: 28 additions & 3 deletions

File tree

‎src/node_snapshotable.cc‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,9 @@ static void ResetContextSettingsBeforeSnapshot(Local<Context> context) {
862862

863863
const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() {
864864
static auto registry = std::make_unique<ExternalReferenceRegistry>();
865-
return registry->external_references();
865+
static const std::vector<intptr_t>& references =
866+
registry->external_references();
867+
return references;
866868
}
867869

868870
void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data,

‎test/cctest/test_environment.cc‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
#include "node_buffer.h"
33
#include "node_internals.h"
44
#include "node_realm-inl.h"
5+
#include "node_snapshot_builder.h"
56
#include "node_url.h"
67
#include "util.h"
78

9+
#include <stdio.h>
10+
#include <cstdio>
811
#include <string>
12+
#include <thread> // NOLINT(build/c++11)
913
#include "gtest/gtest.h"
1014
#include "node_test_fixture.h"
11-
#include <stdio.h>
12-
#include <cstdio>
1315

1416
using node::AtExit;
1517
using node::RunAtExit;
@@ -328,6 +330,27 @@ TEST_F(EnvironmentTest, MultipleEnvironmentsPerIsolate) {
328330
EXPECT_TRUE(called_cb_2);
329331
}
330332

333+
TEST_F(EnvironmentTest, CollectExternalReferencesFromSeveralThreads) {
334+
constexpr int kThreads = 8;
335+
const intptr_t* data[kThreads];
336+
size_t sizes[kThreads];
337+
std::vector<std::thread> threads;
338+
for (int i = 0; i < kThreads; i++) {
339+
threads.emplace_back([&, i]() {
340+
const std::vector<intptr_t>& references =
341+
node::SnapshotBuilder::CollectExternalReferences();
342+
data[i] = references.data();
343+
sizes[i] = references.size();
344+
});
345+
}
346+
for (std::thread& thread : threads) thread.join();
347+
for (int i = 1; i < kThreads; i++) {
348+
EXPECT_EQ(data[i], data[0]);
349+
EXPECT_EQ(sizes[i], sizes[0]);
350+
}
351+
EXPECT_EQ(node::SnapshotBuilder::CollectExternalReferences().back(), 0);
352+
}
353+
331354
TEST_F(EnvironmentTest, NoEnvironmentSanity) {
332355
const v8::HandleScope handle_scope(isolate_);
333356
v8::Local<v8::Context> context = v8::Context::New(isolate_);

0 commit comments

Comments
 (0)