Skip to content

Commit 6970520

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: nodejs#32984 Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
1 parent feb1753 commit 6970520

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
@@ -858,7 +858,9 @@ static void ResetContextSettingsBeforeSnapshot(Local<Context> context) {
858858

859859
const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() {
860860
static auto registry = std::make_unique<ExternalReferenceRegistry>();
861-
return registry->external_references();
861+
static const std::vector<intptr_t>& references =
862+
registry->external_references();
863+
return references;
862864
}
863865

864866
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;
@@ -367,6 +369,27 @@ TEST_F(EnvironmentTest, WorkerInEnvironmentWithoutSnapshot) {
367369
EXPECT_EQ(node::SpinEventLoop(*env).FromJust(), 0);
368370
}
369371

372+
TEST_F(EnvironmentTest, CollectExternalReferencesFromSeveralThreads) {
373+
constexpr int kThreads = 8;
374+
const intptr_t* data[kThreads];
375+
size_t sizes[kThreads];
376+
std::vector<std::thread> threads;
377+
for (int i = 0; i < kThreads; i++) {
378+
threads.emplace_back([&, i]() {
379+
const std::vector<intptr_t>& references =
380+
node::SnapshotBuilder::CollectExternalReferences();
381+
data[i] = references.data();
382+
sizes[i] = references.size();
383+
});
384+
}
385+
for (std::thread& thread : threads) thread.join();
386+
for (int i = 1; i < kThreads; i++) {
387+
EXPECT_EQ(data[i], data[0]);
388+
EXPECT_EQ(sizes[i], sizes[0]);
389+
}
390+
EXPECT_EQ(node::SnapshotBuilder::CollectExternalReferences().back(), 0);
391+
}
392+
370393
TEST_F(EnvironmentTest, NoEnvironmentSanity) {
371394
const v8::HandleScope handle_scope(isolate_);
372395
v8::Local<v8::Context> context = v8::Context::New(isolate_);

0 commit comments

Comments
 (0)