Skip to content

Commit a71cbc4

Browse files
committed
fs: fix build and snapshot issues with fs channels
Define the inline channel helpers in node_file-inl.h so that including node_file.h alone builds under -Werror=undefined-inline. Hold the cached channels weakly, as the permission code does, and skip the instrumentation while building a snapshot: linking a native channel creates JS channel objects that cannot be serialized. Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 6e1aa61 commit a71cbc4

3 files changed

Lines changed: 92 additions & 46 deletions

File tree

src/node_file-inl.h

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,21 @@ FSReqPromise<AliasedBufferT>::~FSReqPromise() {
204204
CHECK_IMPLIES(!finished_, !env()->can_call_into_js());
205205
}
206206

207+
inline bool FSOperationChannelHasSubscribers(FSOperationChannels& channels,
208+
FSOperationChannel channel) {
209+
diagnostics_channel::Channel* ch =
210+
channels[static_cast<size_t>(channel)].get();
211+
return ch != nullptr && ch->HasSubscribers();
212+
}
213+
214+
inline bool AnyFSOperationChannelHasSubscribers(FSOperationChannels& channels) {
215+
for (size_t i = 0; i < kNumFSOperationChannels; i++) {
216+
diagnostics_channel::Channel* ch = channels[i].get();
217+
if (ch != nullptr && ch->HasSubscribers()) return true;
218+
}
219+
return false;
220+
}
221+
207222
template <typename AliasedBufferT>
208223
FSReqPromise<AliasedBufferT>::FSReqPromise(BindingData* binding_data,
209224
v8::Local<v8::Object> obj,
@@ -233,8 +248,8 @@ void FSReqPromise<AliasedBufferT>::Reject(v8::Local<v8::Value> reject) {
233248
template <typename AliasedBufferT>
234249
void FSReqPromise<AliasedBufferT>::Resolve(v8::Local<v8::Value> value) {
235250
finished_ = true;
236-
PublishFSOpCompletionEvent(this, FSOperationChannel::kAsyncEnd, "result",
237-
value);
251+
PublishFSOpCompletionEvent(
252+
this, FSOperationChannel::kAsyncEnd, "result", value);
238253
v8::HandleScope scope(env()->isolate());
239254
InternalCallbackScope callback_scope(this);
240255
v8::Local<v8::Value> val;
@@ -338,12 +353,18 @@ FSReqBase* AsyncDestCall(Environment* env, FSReqBase* req_wrap,
338353
// See SyncCallAndThrowIf: instrumentation is unsafe with a pending
339354
// exception.
340355
if (binding != nullptr && !env->isolate()->HasPendingException()) {
341-
channels = &GetFSOperationChannels(binding, env, syscall);
356+
channels = GetFSOperationChannels(binding, env, syscall);
342357
req_wrap->set_op_channels(channels);
343-
if (FSOperationChannelHasSubscribers(*channels,
344-
FSOperationChannel::kStart)) {
345-
PublishFSOperationEvent(env, *channels, FSOperationChannel::kStart,
346-
api, nullptr, req_wrap->data(), -1, nullptr,
358+
if (channels != nullptr && FSOperationChannelHasSubscribers(
359+
*channels, FSOperationChannel::kStart)) {
360+
PublishFSOperationEvent(env,
361+
*channels,
362+
FSOperationChannel::kStart,
363+
api,
364+
nullptr,
365+
req_wrap->data(),
366+
-1,
367+
nullptr,
347368
v8::Local<v8::Value>());
348369
}
349370
}
@@ -363,8 +384,14 @@ FSReqBase* AsyncDestCall(Environment* env, FSReqBase* req_wrap,
363384
// The path is captured for the completion events; it requires a copy
364385
// since the uv request is cleaned up before they fire.
365386
req_wrap->set_op_path(path == nullptr ? std::string() : path);
366-
PublishFSOperationEvent(env, *channels, FSOperationChannel::kEnd, api,
367-
path, req_wrap->data(), fd, nullptr,
387+
PublishFSOperationEvent(env,
388+
*channels,
389+
FSOperationChannel::kEnd,
390+
api,
391+
path,
392+
req_wrap->data(),
393+
fd,
394+
nullptr,
368395
v8::Local<v8::Value>());
369396
}
370397
return req_wrap;
@@ -426,12 +453,18 @@ int SyncCallAndThrowIf(Predicate should_throw,
426453
// of which is safe with a pending exception (a multi-step operation keeps
427454
// going after a failed step to clean up, e.g. write + close).
428455
if (binding != nullptr && !env->isolate()->HasPendingException()) {
429-
channels = &GetFSOperationChannels(binding, env, req_wrap->syscall_p);
430-
if (FSOperationChannelHasSubscribers(*channels,
431-
FSOperationChannel::kStart)) {
432-
PublishFSOperationEvent(env, *channels, FSOperationChannel::kStart,
433-
"sync", req_wrap->path_p, req_wrap->dest_p, -1,
434-
nullptr, v8::Local<v8::Value>());
456+
channels = GetFSOperationChannels(binding, env, req_wrap->syscall_p);
457+
if (channels != nullptr && FSOperationChannelHasSubscribers(
458+
*channels, FSOperationChannel::kStart)) {
459+
PublishFSOperationEvent(env,
460+
*channels,
461+
FSOperationChannel::kStart,
462+
"sync",
463+
req_wrap->path_p,
464+
req_wrap->dest_p,
465+
-1,
466+
nullptr,
467+
v8::Local<v8::Value>());
435468
}
436469
}
437470
int result = fn(nullptr, &(req_wrap->req), args..., nullptr);
@@ -449,9 +482,15 @@ int SyncCallAndThrowIf(Predicate should_throw,
449482
nullptr,
450483
req_wrap->path_p,
451484
req_wrap->dest_p);
452-
PublishFSOperationEvent(env, *channels, FSOperationChannel::kError,
453-
"sync", req_wrap->path_p, req_wrap->dest_p,
454-
fd, "error", error);
485+
PublishFSOperationEvent(env,
486+
*channels,
487+
FSOperationChannel::kError,
488+
"sync",
489+
req_wrap->path_p,
490+
req_wrap->dest_p,
491+
fd,
492+
"error",
493+
error);
455494
}
456495
} else if (FSOperationChannelHasSubscribers(*channels,
457496
FSOperationChannel::kEnd)) {

src/node_file.cc

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,24 +104,35 @@ const char* const kFSOperationEventNames[kNumFSOperationChannels] = {
104104
"error",
105105
};
106106

107-
FSOperationChannels& GetFSOperationChannels(BindingData* binding,
107+
FSOperationChannels* GetFSOperationChannels(BindingData* binding,
108108
Environment* env,
109109
const char* operation) {
110+
if (env->isolate_data()->is_building_snapshot()) return nullptr;
110111
auto& names = binding->fs_op_channel_names_;
112+
FSOperationChannels* set = nullptr;
111113
for (size_t i = 0; i < names.size(); i++) {
112114
if (names[i] == operation) {
113-
return *binding->fs_op_channel_sets_[i];
115+
set = binding->fs_op_channel_sets_[i].get();
116+
break;
114117
}
115118
}
116-
auto set = std::make_unique<FSOperationChannels>();
119+
if (set == nullptr) {
120+
names.push_back(operation);
121+
binding->fs_op_channel_sets_.push_back(
122+
std::make_unique<FSOperationChannels>());
123+
set = binding->fs_op_channel_sets_.back().get();
124+
}
125+
// Entries are weak: fill in any that were never created or whose owner
126+
// (the diagnostics_channel BindingData) has released them since.
117127
for (size_t i = 0; i < kNumFSOperationChannels; i++) {
128+
if ((*set)[i]) continue;
118129
std::string name = std::string("tracing:fs.") + operation + ":" +
119130
kFSOperationEventNames[i];
120-
(*set)[i] = diagnostics_channel::Channel::Get(env, name);
131+
if (auto ch = diagnostics_channel::Channel::Get(env, name)) {
132+
(*set)[i] = BaseObjectWeakPtr<diagnostics_channel::Channel>(ch.get());
133+
}
121134
}
122-
names.push_back(operation);
123-
binding->fs_op_channel_sets_.push_back(std::move(set));
124-
return *binding->fs_op_channel_sets_.back();
135+
return set;
125136
}
126137

127138
void PublishFSOperationEvent(Environment* env,
@@ -867,8 +878,8 @@ void FSReqCallback::ResolveStatFs(const uv_statfs_t* stat) {
867878
}
868879

869880
void FSReqCallback::Resolve(Local<Value> value) {
870-
PublishFSOpCompletionEvent(this, FSOperationChannel::kAsyncEnd, "result",
871-
value);
881+
PublishFSOpCompletionEvent(
882+
this, FSOperationChannel::kAsyncEnd, "result", value);
872883
Local<Value> argv[2]{Null(env()->isolate()), value};
873884
MakeCallback(env()->oncomplete_string(),
874885
value->IsUndefined() ? 1 : arraysize(argv),
@@ -899,8 +910,8 @@ FSReqAfterScope::FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req)
899910
CHECK_EQ(wrap_->req(), req);
900911
// The async work for the operation has completed; the continuation window
901912
// begins here.
902-
PublishFSOpCompletionEvent(wrap, FSOperationChannel::kAsyncStart, nullptr,
903-
Local<Value>());
913+
PublishFSOpCompletionEvent(
914+
wrap, FSOperationChannel::kAsyncStart, nullptr, Local<Value>());
904915
}
905916

906917
FSReqAfterScope::~FSReqAfterScope() {

src/node_file.h

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,37 +81,33 @@ class FSReqBase;
8181
class BindingData;
8282

8383
// The event channels of one operation's channel family, all created when the
84-
// operation is first seen.
84+
// operation is first seen. The diagnostics_channel BindingData is the sole
85+
// owner of Channel objects; weak references keep this cache from holding them
86+
// alive past Realm teardown or into a snapshot, where the owner releases them
87+
// before serialization. A released entry is fetched again on the next use.
8588
using FSOperationChannels =
86-
std::array<BaseObjectPtr<diagnostics_channel::Channel>,
89+
std::array<BaseObjectWeakPtr<diagnostics_channel::Channel>,
8790
kNumFSOperationChannels>;
8891

8992
// Returns the channel set for `operation`, fetching it once per call site so
9093
// that publishing several events for one operation costs a single lookup.
9194
// `operation` must be a string literal: the cache is keyed on its identity.
92-
// The returned reference stays valid for the lifetime of the BindingData.
93-
FSOperationChannels& GetFSOperationChannels(BindingData* binding,
95+
// The returned pointer stays valid for the lifetime of the BindingData.
96+
// Returns nullptr while a snapshot is being built: creating a channel links a
97+
// JS channel object, and those cannot be serialized.
98+
FSOperationChannels* GetFSOperationChannels(BindingData* binding,
9499
Environment* env,
95100
const char* operation);
96101

97102
// Returns true if the given event channel has subscribers. Check before
98103
// calling PublishFSOperationEvent so the no-subscriber fast path stays free
99-
// of out-of-line calls and payload preparation.
104+
// of out-of-line calls and payload preparation. Defined in node_file-inl.h.
100105
inline bool FSOperationChannelHasSubscribers(FSOperationChannels& channels,
101-
FSOperationChannel channel) {
102-
diagnostics_channel::Channel* ch =
103-
channels[static_cast<size_t>(channel)].get();
104-
return ch != nullptr && ch->HasSubscribers();
105-
}
106+
FSOperationChannel channel);
106107

107108
// Returns true if any of the operation's event channels has subscribers.
108-
inline bool AnyFSOperationChannelHasSubscribers(FSOperationChannels& channels) {
109-
for (size_t i = 0; i < kNumFSOperationChannels; i++) {
110-
diagnostics_channel::Channel* ch = channels[i].get();
111-
if (ch != nullptr && ch->HasSubscribers()) return true;
112-
}
113-
return false;
114-
}
109+
// Defined in node_file-inl.h.
110+
inline bool AnyFSOperationChannelHasSubscribers(FSOperationChannels& channels);
115111

116112
// Publishes an event on one of the operation's channels. Payload fields are
117113
// set only when applicable: `path`/`dest` (which may be null or empty) for

0 commit comments

Comments
 (0)