Skip to content

Commit b11616b

Browse files
joyeecheungdanielleadams
authored andcommitted
src: support WeakReference in snapshot
Move util::WeakReference to a separate header and implement {de}serialization for it to be snapshotable. PR-URL: #44193 Refs: #44014 Refs: #37476 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 1ca5755 commit b11616b

9 files changed

+246
-38
lines changed

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@
643643
'src/node_stat_watcher.h',
644644
'src/node_union_bytes.h',
645645
'src/node_url.h',
646+
'src/node_util.h',
646647
'src/node_version.h',
647648
'src/node_v8.h',
648649
'src/node_v8_platform-inl.h',

src/node_snapshotable.cc

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "node_metadata.h"
1717
#include "node_process.h"
1818
#include "node_snapshot_builder.h"
19+
#include "node_util.h"
1920
#include "node_v8.h"
2021
#include "node_v8_platform-inl.h"
2122

src/node_snapshotable.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class ExternalReferenceRegistry;
1818
V(fs_binding_data, fs::BindingData) \
1919
V(v8_binding_data, v8_utils::BindingData) \
2020
V(blob_binding_data, BlobBindingData) \
21-
V(process_binding_data, process::BindingData)
21+
V(process_binding_data, process::BindingData) \
22+
V(util_weak_reference, util::WeakReference)
2223

2324
enum class EmbedderObjectType : uint8_t {
2425
#define V(PropertyName, NativeType) k_##PropertyName,

src/node_util.cc

+92-37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "node_util.h"
12
#include "base_object-inl.h"
23
#include "node_errors.h"
34
#include "node_external_reference.h"
@@ -15,7 +16,7 @@ using v8::Context;
1516
using v8::External;
1617
using v8::FunctionCallbackInfo;
1718
using v8::FunctionTemplate;
18-
using v8::Global;
19+
using v8::HandleScope;
1920
using v8::IndexFilter;
2021
using v8::Integer;
2122
using v8::Isolate;
@@ -207,52 +208,106 @@ void ArrayBufferViewHasBuffer(const FunctionCallbackInfo<Value>& args) {
207208
args.GetReturnValue().Set(args[0].As<ArrayBufferView>()->HasBuffer());
208209
}
209210

210-
class WeakReference : public BaseObject {
211-
public:
212-
WeakReference(Environment* env, Local<Object> object, Local<Object> target)
213-
: BaseObject(env, object) {
214-
MakeWeak();
211+
WeakReference::WeakReference(Environment* env,
212+
Local<Object> object,
213+
Local<Object> target)
214+
: WeakReference(env, object, target, 0) {}
215+
216+
WeakReference::WeakReference(Environment* env,
217+
Local<Object> object,
218+
Local<Object> target,
219+
uint64_t reference_count)
220+
: SnapshotableObject(env, object, type_int),
221+
reference_count_(reference_count) {
222+
MakeWeak();
223+
if (!target.IsEmpty()) {
215224
target_.Reset(env->isolate(), target);
216-
target_.SetWeak();
225+
if (reference_count_ == 0) {
226+
target_.SetWeak();
227+
}
217228
}
229+
}
218230

219-
static void New(const FunctionCallbackInfo<Value>& args) {
220-
Environment* env = Environment::GetCurrent(args);
221-
CHECK(args.IsConstructCall());
222-
CHECK(args[0]->IsObject());
223-
new WeakReference(env, args.This(), args[0].As<Object>());
231+
bool WeakReference::PrepareForSerialization(Local<Context> context,
232+
v8::SnapshotCreator* creator) {
233+
if (target_.IsEmpty()) {
234+
target_index_ = 0;
235+
return true;
224236
}
225237

226-
static void Get(const FunctionCallbackInfo<Value>& args) {
227-
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
228-
Isolate* isolate = args.GetIsolate();
229-
if (!weak_ref->target_.IsEmpty())
230-
args.GetReturnValue().Set(weak_ref->target_.Get(isolate));
231-
}
238+
// Users can still hold strong references to target in addition to the
239+
// reference that we manage here, and they could expect that the referenced
240+
// object remains the same as long as that external strong reference
241+
// is alive. Since we have no way to know if there is any other reference
242+
// keeping the target alive, the best we can do to maintain consistency is to
243+
// simply save a reference to the target in the snapshot (effectively making
244+
// it strong) during serialization, and restore it during deserialization.
245+
// If there's no known counted reference from our side, we'll make the
246+
// reference here weak upon deserialization so that it can be GC'ed if users
247+
// do not hold additional references to it.
248+
Local<Object> target = target_.Get(context->GetIsolate());
249+
target_index_ = creator->AddData(context, target);
250+
DCHECK_NE(target_index_, 0);
251+
target_.Reset();
252+
return true;
253+
}
232254

233-
static void IncRef(const FunctionCallbackInfo<Value>& args) {
234-
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
235-
weak_ref->reference_count_++;
236-
if (weak_ref->target_.IsEmpty()) return;
237-
if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak();
238-
}
255+
InternalFieldInfoBase* WeakReference::Serialize(int index) {
256+
DCHECK_EQ(index, BaseObject::kEmbedderType);
257+
InternalFieldInfo* info =
258+
InternalFieldInfoBase::New<InternalFieldInfo>(type());
259+
info->target = target_index_;
260+
info->reference_count = reference_count_;
261+
return info;
262+
}
239263

240-
static void DecRef(const FunctionCallbackInfo<Value>& args) {
241-
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
242-
CHECK_GE(weak_ref->reference_count_, 1);
243-
weak_ref->reference_count_--;
244-
if (weak_ref->target_.IsEmpty()) return;
245-
if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak();
264+
void WeakReference::Deserialize(Local<Context> context,
265+
Local<Object> holder,
266+
int index,
267+
InternalFieldInfoBase* info) {
268+
DCHECK_EQ(index, BaseObject::kEmbedderType);
269+
HandleScope scope(context->GetIsolate());
270+
271+
InternalFieldInfo* weak_info = reinterpret_cast<InternalFieldInfo*>(info);
272+
Local<Object> target;
273+
if (weak_info->target != 0) {
274+
target = context->GetDataFromSnapshotOnce<Object>(weak_info->target)
275+
.ToLocalChecked();
246276
}
277+
new WeakReference(Environment::GetCurrent(context),
278+
holder,
279+
target,
280+
weak_info->reference_count);
281+
}
282+
283+
void WeakReference::New(const FunctionCallbackInfo<Value>& args) {
284+
Environment* env = Environment::GetCurrent(args);
285+
CHECK(args.IsConstructCall());
286+
CHECK(args[0]->IsObject());
287+
new WeakReference(env, args.This(), args[0].As<Object>());
288+
}
289+
290+
void WeakReference::Get(const FunctionCallbackInfo<Value>& args) {
291+
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
292+
Isolate* isolate = args.GetIsolate();
293+
if (!weak_ref->target_.IsEmpty())
294+
args.GetReturnValue().Set(weak_ref->target_.Get(isolate));
295+
}
247296

248-
SET_MEMORY_INFO_NAME(WeakReference)
249-
SET_SELF_SIZE(WeakReference)
250-
SET_NO_MEMORY_INFO()
297+
void WeakReference::IncRef(const FunctionCallbackInfo<Value>& args) {
298+
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
299+
weak_ref->reference_count_++;
300+
if (weak_ref->target_.IsEmpty()) return;
301+
if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak();
302+
}
251303

252-
private:
253-
Global<Object> target_;
254-
uint64_t reference_count_ = 0;
255-
};
304+
void WeakReference::DecRef(const FunctionCallbackInfo<Value>& args) {
305+
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
306+
CHECK_GE(weak_ref->reference_count_, 1);
307+
weak_ref->reference_count_--;
308+
if (weak_ref->target_.IsEmpty()) return;
309+
if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak();
310+
}
256311

257312
static void GuessHandleType(const FunctionCallbackInfo<Value>& args) {
258313
Environment* env = Environment::GetCurrent(args);

src/node_util.h

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
#ifndef SRC_NODE_UTIL_H_
3+
#define SRC_NODE_UTIL_H_
4+
5+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
6+
#include "base_object.h"
7+
#include "node_snapshotable.h"
8+
#include "v8.h"
9+
10+
namespace node {
11+
namespace util {
12+
13+
class WeakReference : public SnapshotableObject {
14+
public:
15+
SERIALIZABLE_OBJECT_METHODS();
16+
17+
static constexpr FastStringKey type_name{"node::util::WeakReference"};
18+
static constexpr EmbedderObjectType type_int =
19+
EmbedderObjectType::k_util_weak_reference;
20+
21+
WeakReference(Environment* env,
22+
v8::Local<v8::Object> object,
23+
v8::Local<v8::Object> target);
24+
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
25+
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
26+
static void IncRef(const v8::FunctionCallbackInfo<v8::Value>& args);
27+
static void DecRef(const v8::FunctionCallbackInfo<v8::Value>& args);
28+
29+
SET_MEMORY_INFO_NAME(WeakReference)
30+
SET_SELF_SIZE(WeakReference)
31+
SET_NO_MEMORY_INFO()
32+
33+
struct InternalFieldInfo : public node::InternalFieldInfoBase {
34+
SnapshotIndex target;
35+
uint64_t reference_count;
36+
};
37+
38+
private:
39+
WeakReference(Environment* env,
40+
v8::Local<v8::Object> object,
41+
v8::Local<v8::Object> target,
42+
uint64_t reference_count);
43+
v8::Global<v8::Object> target_;
44+
uint64_t reference_count_ = 0;
45+
46+
SnapshotIndex target_index_ = 0; // 0 means target_ is not snapshotted
47+
};
48+
49+
} // namespace util
50+
} // namespace node
51+
52+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
53+
54+
#endif // SRC_NODE_UTIL_H_

src/util.cc

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "node_buffer.h"
2828
#include "node_errors.h"
2929
#include "node_internals.h"
30+
#include "node_util.h"
3031
#include "string_bytes.h"
3132
#include "uv.h"
3233

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const { internalBinding } = require('internal/test/binding');
4+
const { WeakReference } = internalBinding('util');
5+
const {
6+
setDeserializeMainFunction
7+
} = require('v8').startupSnapshot
8+
const assert = require('assert');
9+
10+
let obj = { hello: 'world' };
11+
const ref = new WeakReference(obj);
12+
13+
setDeserializeMainFunction(() => {
14+
obj = null;
15+
globalThis.gc();
16+
17+
setImmediate(() => {
18+
assert.strictEqual(ref.get(), undefined);
19+
});
20+
});
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const { internalBinding } = require('internal/test/binding');
4+
const { WeakReference } = internalBinding('util');
5+
const {
6+
setDeserializeMainFunction
7+
} = require('v8').startupSnapshot
8+
const assert = require('assert');
9+
10+
let obj = { hello: 'world' };
11+
const ref = new WeakReference(obj);
12+
13+
setDeserializeMainFunction(() => {
14+
assert.strictEqual(ref.get(), obj);
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
3+
// This tests that weak references work across serialization.
4+
5+
require('../common');
6+
const assert = require('assert');
7+
const { spawnSync } = require('child_process');
8+
const tmpdir = require('../common/tmpdir');
9+
const fixtures = require('../common/fixtures');
10+
const path = require('path');
11+
const fs = require('fs');
12+
13+
tmpdir.refresh();
14+
const blobPath = path.join(tmpdir.path, 'snapshot.blob');
15+
16+
function runTest(entry) {
17+
console.log('running test with', entry);
18+
{
19+
const child = spawnSync(process.execPath, [
20+
'--expose-internals',
21+
'--expose-gc',
22+
'--snapshot-blob',
23+
blobPath,
24+
'--build-snapshot',
25+
entry,
26+
], {
27+
cwd: tmpdir.path
28+
});
29+
if (child.status !== 0) {
30+
console.log(child.stderr.toString());
31+
console.log(child.stdout.toString());
32+
assert.strictEqual(child.status, 0);
33+
}
34+
const stats = fs.statSync(path.join(tmpdir.path, 'snapshot.blob'));
35+
assert(stats.isFile());
36+
}
37+
38+
{
39+
const child = spawnSync(process.execPath, [
40+
'--expose-internals',
41+
'--expose-gc',
42+
'--snapshot-blob',
43+
blobPath,
44+
], {
45+
cwd: tmpdir.path,
46+
env: {
47+
...process.env,
48+
}
49+
});
50+
51+
const stdout = child.stdout.toString().trim();
52+
const stderr = child.stderr.toString().trim();
53+
console.log(`[stdout]:\n${stdout}\n`);
54+
console.log(`[stderr]:\n${stderr}\n`);
55+
assert.strictEqual(child.status, 0);
56+
}
57+
}
58+
59+
runTest(fixtures.path('snapshot', 'weak-reference.js'));
60+
runTest(fixtures.path('snapshot', 'weak-reference-gc.js'));

0 commit comments

Comments
 (0)