Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: support WeakReference, diagnostics_channel and net in the snapshot #44193

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/internal/main/mksnapshot.js
Expand Up @@ -48,7 +48,7 @@ const supportedModules = new SafeSet(new SafeArrayIterator([
'constants',
'crypto',
// 'dgram',
// 'diagnostics_channel',
'diagnostics_channel',
// 'dns',
// 'dns/promises',
// 'domain',
Expand All @@ -60,7 +60,7 @@ const supportedModules = new SafeSet(new SafeArrayIterator([
// 'https',
// 'inspector',
// 'module',
// 'net',
'net',
'os',
'path',
'path/posix',
Expand Down
20 changes: 4 additions & 16 deletions lib/net.js
Expand Up @@ -131,20 +131,9 @@ const noop = () => {};

const kPerfHooksNetConnectContext = Symbol('kPerfHooksNetConnectContext');

let netClientSocketChannel;
let netServerSocketChannel;
function lazyChannels() {
// TODO(joyeecheung): support diagnostics channels in the snapshot.
// For now it is fine to create them lazily when there isn't a snapshot to
// build. If users need the channels they would have to create them first
// before invoking any built-ins that would publish to these channels
// anyway.
if (netClientSocketChannel === undefined) {
const dc = require('diagnostics_channel');
netClientSocketChannel = dc.channel('net.client.socket');
netServerSocketChannel = dc.channel('net.server.socket');
}
}
const dc = require('diagnostics_channel');
const netClientSocketChannel = dc.channel('net.client.socket');
const netServerSocketChannel = dc.channel('net.server.socket');

const {
hasObserver,
Expand Down Expand Up @@ -217,7 +206,7 @@ function connect(...args) {
const options = normalized[0];
debug('createConnection', normalized);
const socket = new Socket(options);
lazyChannels();

if (netClientSocketChannel.hasSubscribers) {
netClientSocketChannel.publish({
socket,
Expand Down Expand Up @@ -1761,7 +1750,6 @@ function onconnection(err, clientHandle) {
socket.server = self;
socket._server = self;
self.emit('connection', socket);
lazyChannels();
if (netServerSocketChannel.hasSubscribers) {
netServerSocketChannel.publish({
socket,
Expand Down
1 change: 1 addition & 0 deletions node.gyp
Expand Up @@ -628,6 +628,7 @@
'src/node_stat_watcher.h',
'src/node_union_bytes.h',
'src/node_url.h',
'src/node_util.h',
'src/node_version.h',
'src/node_v8.h',
'src/node_v8_platform-inl.h',
Expand Down
1 change: 1 addition & 0 deletions src/node_snapshotable.cc
Expand Up @@ -15,6 +15,7 @@
#include "node_metadata.h"
#include "node_process.h"
#include "node_snapshot_builder.h"
#include "node_util.h"
#include "node_v8.h"
#include "node_v8_platform-inl.h"

Expand Down
3 changes: 2 additions & 1 deletion src/node_snapshotable.h
Expand Up @@ -18,7 +18,8 @@ class ExternalReferenceRegistry;
V(fs_binding_data, fs::BindingData) \
V(v8_binding_data, v8_utils::BindingData) \
V(blob_binding_data, BlobBindingData) \
V(process_binding_data, process::BindingData)
V(process_binding_data, process::BindingData) \
V(util_weak_reference, util::WeakReference)

enum class EmbedderObjectType : uint8_t {
#define V(PropertyName, NativeType) k_##PropertyName,
Expand Down
129 changes: 92 additions & 37 deletions src/node_util.cc
@@ -1,3 +1,4 @@
#include "node_util.h"
#include "base_object-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
Expand All @@ -15,7 +16,7 @@ using v8::Context;
using v8::External;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::HandleScope;
using v8::IndexFilter;
using v8::Integer;
using v8::Isolate;
Expand Down Expand Up @@ -207,52 +208,106 @@ void ArrayBufferViewHasBuffer(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(args[0].As<ArrayBufferView>()->HasBuffer());
}

class WeakReference : public BaseObject {
public:
WeakReference(Environment* env, Local<Object> object, Local<Object> target)
: BaseObject(env, object) {
MakeWeak();
WeakReference::WeakReference(Environment* env,
Local<Object> object,
Local<Object> target)
: WeakReference(env, object, target, 0) {}

WeakReference::WeakReference(Environment* env,
Local<Object> object,
Local<Object> target,
uint64_t reference_count)
: SnapshotableObject(env, object, type_int),
reference_count_(reference_count) {
MakeWeak();
if (!target.IsEmpty()) {
target_.Reset(env->isolate(), target);
target_.SetWeak();
if (reference_count_ == 0) {
target_.SetWeak();
}
}
}

static void New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CHECK(args[0]->IsObject());
new WeakReference(env, args.This(), args[0].As<Object>());
bool WeakReference::PrepareForSerialization(Local<Context> context,
v8::SnapshotCreator* creator) {
if (target_.IsEmpty()) {
target_index_ = 0;
return true;
}

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

static void IncRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
weak_ref->reference_count_++;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak();
}
InternalFieldInfoBase* WeakReference::Serialize(int index) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
InternalFieldInfo* info =
InternalFieldInfoBase::New<InternalFieldInfo>(type());
info->target = target_index_;
info->reference_count = reference_count_;
return info;
}

static void DecRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
CHECK_GE(weak_ref->reference_count_, 1);
weak_ref->reference_count_--;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak();
void WeakReference::Deserialize(Local<Context> context,
Local<Object> holder,
int index,
InternalFieldInfoBase* info) {
DCHECK_EQ(index, BaseObject::kEmbedderType);
HandleScope scope(context->GetIsolate());

InternalFieldInfo* weak_info = reinterpret_cast<InternalFieldInfo*>(info);
Local<Object> target;
if (weak_info->target != 0) {
target = context->GetDataFromSnapshotOnce<Object>(weak_info->target)
.ToLocalChecked();
}
new WeakReference(Environment::GetCurrent(context),
holder,
target,
weak_info->reference_count);
}

void WeakReference::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CHECK(args[0]->IsObject());
new WeakReference(env, args.This(), args[0].As<Object>());
}

void WeakReference::Get(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
Isolate* isolate = args.GetIsolate();
if (!weak_ref->target_.IsEmpty())
args.GetReturnValue().Set(weak_ref->target_.Get(isolate));
}

SET_MEMORY_INFO_NAME(WeakReference)
SET_SELF_SIZE(WeakReference)
SET_NO_MEMORY_INFO()
void WeakReference::IncRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
weak_ref->reference_count_++;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak();
}

private:
Global<Object> target_;
uint64_t reference_count_ = 0;
};
void WeakReference::DecRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
CHECK_GE(weak_ref->reference_count_, 1);
weak_ref->reference_count_--;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak();
}

static void GuessHandleType(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Expand Down
54 changes: 54 additions & 0 deletions src/node_util.h
@@ -0,0 +1,54 @@

#ifndef SRC_NODE_UTIL_H_
#define SRC_NODE_UTIL_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "node_snapshotable.h"
#include "v8.h"

namespace node {
namespace util {

class WeakReference : public SnapshotableObject {
public:
SERIALIZABLE_OBJECT_METHODS();

static constexpr FastStringKey type_name{"node::util::WeakReference"};
static constexpr EmbedderObjectType type_int =
EmbedderObjectType::k_util_weak_reference;

WeakReference(Environment* env,
v8::Local<v8::Object> object,
v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IncRef(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DecRef(const v8::FunctionCallbackInfo<v8::Value>& args);

SET_MEMORY_INFO_NAME(WeakReference)
SET_SELF_SIZE(WeakReference)
SET_NO_MEMORY_INFO()

struct InternalFieldInfo : public node::InternalFieldInfoBase {
SnapshotIndex target;
uint64_t reference_count;
};

private:
WeakReference(Environment* env,
v8::Local<v8::Object> object,
v8::Local<v8::Object> target,
uint64_t reference_count);
v8::Global<v8::Object> target_;
uint64_t reference_count_ = 0;

SnapshotIndex target_index_ = 0; // 0 means target_ is not snapshotted
};

} // namespace util
} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_UTIL_H_
1 change: 1 addition & 0 deletions src/util.cc
Expand Up @@ -27,6 +27,7 @@
#include "node_buffer.h"
#include "node_errors.h"
#include "node_internals.h"
#include "node_util.h"
#include "string_bytes.h"
#include "uv.h"

Expand Down
60 changes: 60 additions & 0 deletions test/fixtures/snapshot/server.js
@@ -0,0 +1,60 @@
'use strict';

const net = require('net');
const {
setDeserializeMainFunction
} = require('v8').startupSnapshot;
const dc = require('diagnostics_channel');

const echoServer = net.Server(function(connection) {
connection.on('data', function(chunk) {
connection.write(chunk);
});
connection.on('end', function() {
connection.end();
});
});

const kNumChars = 256;
const buffer = new Uint8Array(kNumChars);
for (let i = 0; i < kNumChars; ++i) {
buffer[i] = i;
}

let recv = '';

echoServer.on('listening', function() {
const port = this.address().port;
console.log(`server port`, port);
const c = net.createConnection({ host: '127.0.0.1', port });

c.on('data', function(chunk) {
recv += chunk.toString('latin1');

if (recv.length === buffer.length) {
c.end();
}
});

c.on('connect', function() {
c.write(buffer);
});

c.on('close', function() {
console.log(`recv.length: ${recv.length}`);
echoServer.close();
});

});

dc.subscribe('net.server.socket', (({ socket }) => {
console.log(`From server diagnostics channel:`, socket.localPort);
}));

dc.subscribe('net.client.socket', (({ socket }) => {
console.log(`From client diagnostics channel`);
}));

setDeserializeMainFunction(() => {
echoServer.listen(0);
});
20 changes: 20 additions & 0 deletions test/fixtures/snapshot/weak-reference-gc.js
@@ -0,0 +1,20 @@
'use strict';

const { internalBinding } = require('internal/test/binding');
const { WeakReference } = internalBinding('util');
const {
setDeserializeMainFunction
} = require('v8').startupSnapshot
const assert = require('assert');

let obj = { hello: 'world' };
const ref = new WeakReference(obj);

setDeserializeMainFunction(() => {
obj = null;
globalThis.gc();

setImmediate(() => {
assert.strictEqual(ref.get(), undefined);
});
});