Skip to content

Commit 9d04047

Browse files
committed
src: allow generic C++ callables in SetImmediate()
Modify the native `SetImmediate()` functions to take generic C++ callables as arguments. This makes passing arguments to the callback easier, and in particular, it allows passing `std::unique_ptr`s directly, which in turn makes sure that the data they point to is deleted if the `Environment` is torn down before the callback can run.
1 parent 31d9b2f commit 9d04047

File tree

12 files changed

+206
-163
lines changed

12 files changed

+206
-163
lines changed

src/async_wrap.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ struct AsyncWrapObject : public AsyncWrap {
8787
SET_SELF_SIZE(AsyncWrapObject)
8888
};
8989

90-
void AsyncWrap::DestroyAsyncIdsCallback(Environment* env, void* data) {
90+
void AsyncWrap::DestroyAsyncIdsCallback(Environment* env) {
9191
Local<Function> fn = env->async_hooks_destroy_function();
9292

9393
TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
@@ -642,7 +642,7 @@ void AsyncWrap::EmitDestroy(Environment* env, double async_id) {
642642
}
643643

644644
if (env->destroy_async_id_list()->empty()) {
645-
env->SetUnrefImmediate(DestroyAsyncIdsCallback, nullptr);
645+
env->SetUnrefImmediate(&DestroyAsyncIdsCallback);
646646
}
647647

648648
env->destroy_async_id_list()->push_back(async_id);

src/async_wrap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class AsyncWrap : public BaseObject {
154154
static void EmitTraceEventAfter(ProviderType type, double async_id);
155155
void EmitTraceEventDestroy();
156156

157-
static void DestroyAsyncIdsCallback(Environment* env, void* data);
157+
static void DestroyAsyncIdsCallback(Environment* env);
158158

159159
inline ProviderType provider_type() const;
160160
inline ProviderType set_provider_type(ProviderType provider);

src/cares_wrap.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -690,9 +690,9 @@ class QueryWrap : public AsyncWrap {
690690
}
691691

692692
void QueueResponseCallback(int status) {
693-
env()->SetImmediate([](Environment*, void* data) {
694-
static_cast<QueryWrap*>(data)->AfterResponse();
695-
}, this, object());
693+
env()->SetImmediate([this](Environment*) {
694+
AfterResponse();
695+
}, object());
696696

697697
channel_->set_query_last_ok(status != ARES_ECONNREFUSED);
698698
channel_->ModifyActivityQueryCount(-1);

src/env-inl.h

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -743,33 +743,66 @@ inline void IsolateData::set_options(
743743
options_ = std::move(options);
744744
}
745745

746-
void Environment::CreateImmediate(native_immediate_callback cb,
747-
void* data,
748-
v8::Local<v8::Object> obj,
746+
template <typename Fn>
747+
void Environment::CreateImmediate(Fn&& cb,
748+
v8::Local<v8::Object> keep_alive,
749749
bool ref) {
750-
native_immediate_callbacks_.push_back({
751-
cb,
752-
data,
753-
v8::Global<v8::Object>(isolate_, obj),
754-
ref
755-
});
750+
auto callback = std::make_unique<NativeImmediateCallbackImpl<Fn>>(
751+
std::move(cb),
752+
v8::Global<v8::Object>(isolate(), keep_alive),
753+
ref);
754+
NativeImmediateCallback* prev_tail = native_immediate_callbacks_tail_;
755+
756+
native_immediate_callbacks_tail_ = callback.get();
757+
if (prev_tail != nullptr)
758+
prev_tail->set_next(std::move(callback));
759+
else
760+
native_immediate_callbacks_head_ = std::move(callback);
761+
756762
immediate_info()->count_inc(1);
757763
}
758764

759-
void Environment::SetImmediate(native_immediate_callback cb,
760-
void* data,
761-
v8::Local<v8::Object> obj) {
762-
CreateImmediate(cb, data, obj, true);
765+
template <typename Fn>
766+
void Environment::SetImmediate(Fn&& cb, v8::Local<v8::Object> keep_alive) {
767+
CreateImmediate(std::move(cb), keep_alive, true);
763768

764769
if (immediate_info()->ref_count() == 0)
765770
ToggleImmediateRef(true);
766771
immediate_info()->ref_count_inc(1);
767772
}
768773

769-
void Environment::SetUnrefImmediate(native_immediate_callback cb,
770-
void* data,
771-
v8::Local<v8::Object> obj) {
772-
CreateImmediate(cb, data, obj, false);
774+
template <typename Fn>
775+
void Environment::SetUnrefImmediate(Fn&& cb, v8::Local<v8::Object> keep_alive) {
776+
CreateImmediate(std::move(cb), keep_alive, false);
777+
}
778+
779+
Environment::NativeImmediateCallback::NativeImmediateCallback(bool refed)
780+
: refed_(refed) {}
781+
782+
bool Environment::NativeImmediateCallback::is_refed() const {
783+
return refed_;
784+
}
785+
786+
std::unique_ptr<Environment::NativeImmediateCallback>
787+
Environment::NativeImmediateCallback::get_next() {
788+
return std::move(next_);
789+
}
790+
791+
void Environment::NativeImmediateCallback::set_next(
792+
std::unique_ptr<NativeImmediateCallback> next) {
793+
next_ = std::move(next);
794+
}
795+
796+
template <typename Fn>
797+
Environment::NativeImmediateCallbackImpl<Fn>::NativeImmediateCallbackImpl(
798+
Fn&& callback, v8::Global<v8::Object>&& keep_alive, bool refed)
799+
: NativeImmediateCallback(refed),
800+
callback_(std::move(callback)),
801+
keep_alive_(std::move(keep_alive)) {}
802+
803+
template <typename Fn>
804+
void Environment::NativeImmediateCallbackImpl<Fn>::Call(Environment* env) {
805+
callback_(env);
773806
}
774807

775808
inline bool Environment::can_call_into_js() const {

src/env.cc

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ Environment::Environment(IsolateData* isolate_data,
339339
[](void* arg) {
340340
Environment* env = static_cast<Environment*>(arg);
341341
if (!env->destroy_async_id_list()->empty())
342-
AsyncWrap::DestroyAsyncIdsCallback(env, nullptr);
342+
AsyncWrap::DestroyAsyncIdsCallback(env);
343343
},
344344
this);
345345

@@ -641,42 +641,38 @@ void Environment::AtExit(void (*cb)(void* arg), void* arg) {
641641
void Environment::RunAndClearNativeImmediates() {
642642
TraceEventScope trace_scope(TRACING_CATEGORY_NODE1(environment),
643643
"RunAndClearNativeImmediates", this);
644-
size_t count = native_immediate_callbacks_.size();
645-
if (count > 0) {
646-
size_t ref_count = 0;
647-
std::vector<NativeImmediateCallback> list;
648-
native_immediate_callbacks_.swap(list);
649-
auto drain_list = [&]() {
650-
TryCatchScope try_catch(this);
651-
for (auto it = list.begin(); it != list.end(); ++it) {
652-
DebugSealHandleScope seal_handle_scope(isolate());
653-
it->cb_(this, it->data_);
654-
if (it->refed_)
655-
ref_count++;
656-
if (UNLIKELY(try_catch.HasCaught())) {
657-
if (!try_catch.HasTerminated())
658-
errors::TriggerUncaughtException(isolate(), try_catch);
659-
660-
// We are done with the current callback. Increase the counter so that
661-
// the steps below make everything *after* the current item part of
662-
// the new list.
663-
it++;
664-
665-
// Bail out, remove the already executed callbacks from list
666-
// and set up a new TryCatch for the other pending callbacks.
667-
std::move_backward(it, list.end(), list.begin() + (list.end() - it));
668-
list.resize(list.end() - it);
669-
return true;
670-
}
644+
size_t ref_count = 0;
645+
size_t count = 0;
646+
std::unique_ptr<NativeImmediateCallback> head;
647+
head.swap(native_immediate_callbacks_head_);
648+
native_immediate_callbacks_tail_ = nullptr;
649+
650+
auto drain_list = [&]() {
651+
TryCatchScope try_catch(this);
652+
for (; head; head = head->get_next()) {
653+
DebugSealHandleScope seal_handle_scope(isolate());
654+
count++;
655+
if (head->is_refed())
656+
ref_count++;
657+
658+
head->Call(this);
659+
if (UNLIKELY(try_catch.HasCaught())) {
660+
if (!try_catch.HasTerminated())
661+
errors::TriggerUncaughtException(isolate(), try_catch);
662+
663+
// We are done with the current callback. Move one iteration along,
664+
// as if we had completed successfully.
665+
head = head->get_next();
666+
return true;
671667
}
672-
return false;
673-
};
674-
while (drain_list()) {}
668+
}
669+
return false;
670+
};
671+
while (head && drain_list()) {}
675672

676-
DCHECK_GE(immediate_info()->count(), count);
677-
immediate_info()->count_dec(count);
678-
immediate_info()->ref_count_dec(ref_count);
679-
}
673+
DCHECK_GE(immediate_info()->count(), count);
674+
immediate_info()->count_dec(count);
675+
immediate_info()->ref_count_dec(ref_count);
680676
}
681677

682678

src/env.h

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,15 +1172,15 @@ class Environment : public MemoryRetainer {
11721172
return current_value;
11731173
}
11741174

1175-
typedef void (*native_immediate_callback)(Environment* env, void* data);
1176-
// cb will be called as cb(env, data) on the next event loop iteration.
1177-
// obj will be kept alive between now and after the callback has run.
1178-
inline void SetImmediate(native_immediate_callback cb,
1179-
void* data,
1180-
v8::Local<v8::Object> obj = v8::Local<v8::Object>());
1181-
inline void SetUnrefImmediate(native_immediate_callback cb,
1182-
void* data,
1183-
v8::Local<v8::Object> obj =
1175+
// cb will be called as cb(env) on the next event loop iteration.
1176+
// keep_alive will be kept alive between now and after the callback has run.
1177+
template <typename Fn>
1178+
inline void SetImmediate(Fn&& cb,
1179+
v8::Local<v8::Object> keep_alive =
1180+
v8::Local<v8::Object>());
1181+
template <typename Fn>
1182+
inline void SetUnrefImmediate(Fn&& cb,
1183+
v8::Local<v8::Object> keep_alive =
11841184
v8::Local<v8::Object>());
11851185
// This needs to be available for the JS-land setImmediate().
11861186
void ToggleImmediateRef(bool ref);
@@ -1245,9 +1245,9 @@ class Environment : public MemoryRetainer {
12451245
#endif // HAVE_INSPECTOR
12461246

12471247
private:
1248-
inline void CreateImmediate(native_immediate_callback cb,
1249-
void* data,
1250-
v8::Local<v8::Object> obj,
1248+
template <typename Fn>
1249+
inline void CreateImmediate(Fn&& cb,
1250+
v8::Local<v8::Object> keep_alive,
12511251
bool ref);
12521252

12531253
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
@@ -1370,13 +1370,38 @@ class Environment : public MemoryRetainer {
13701370

13711371
std::list<ExitCallback> at_exit_functions_;
13721372

1373-
struct NativeImmediateCallback {
1374-
native_immediate_callback cb_;
1375-
void* data_;
1376-
v8::Global<v8::Object> keep_alive_;
1373+
class NativeImmediateCallback {
1374+
public:
1375+
explicit inline NativeImmediateCallback(bool refed);
1376+
1377+
virtual ~NativeImmediateCallback() = default;
1378+
virtual void Call(Environment* env) = 0;
1379+
1380+
inline bool is_refed() const;
1381+
inline std::unique_ptr<NativeImmediateCallback> get_next();
1382+
inline void set_next(std::unique_ptr<NativeImmediateCallback> next);
1383+
1384+
private:
13771385
bool refed_;
1386+
std::unique_ptr<NativeImmediateCallback> next_;
1387+
};
1388+
1389+
template <typename Fn>
1390+
class NativeImmediateCallbackImpl final : public NativeImmediateCallback {
1391+
public:
1392+
NativeImmediateCallbackImpl(Fn&& callback,
1393+
v8::Global<v8::Object>&& keep_alive,
1394+
bool refed);
1395+
void Call(Environment* env) override;
1396+
1397+
private:
1398+
Fn callback_;
1399+
v8::Global<v8::Object> keep_alive_;
13781400
};
1379-
std::vector<NativeImmediateCallback> native_immediate_callbacks_;
1401+
1402+
std::unique_ptr<NativeImmediateCallback> native_immediate_callbacks_head_;
1403+
NativeImmediateCallback* native_immediate_callbacks_tail_ = nullptr;
1404+
13801405
void RunAndClearNativeImmediates();
13811406
static void CheckImmediate(uv_check_t* handle);
13821407

src/node_api.cc

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,33 @@ class BufferFinalizer : private Finalizer {
3636
public:
3737
// node::Buffer::FreeCallback
3838
static void FinalizeBufferCallback(char* data, void* hint) {
39-
BufferFinalizer* finalizer = static_cast<BufferFinalizer*>(hint);
39+
std::unique_ptr<BufferFinalizer, Deleter> finalizer{
40+
static_cast<BufferFinalizer*>(hint)};
4041
finalizer->_finalize_data = data;
41-
static_cast<node_napi_env>(finalizer->_env)->node_env()
42-
->SetImmediate([](node::Environment* env, void* hint) {
43-
BufferFinalizer* finalizer = static_cast<BufferFinalizer*>(hint);
44-
45-
if (finalizer->_finalize_callback != nullptr) {
46-
v8::HandleScope handle_scope(finalizer->_env->isolate);
47-
v8::Context::Scope context_scope(finalizer->_env->context());
48-
49-
finalizer->_env->CallIntoModuleThrow([&](napi_env env) {
50-
finalizer->_finalize_callback(
51-
env,
52-
finalizer->_finalize_data,
53-
finalizer->_finalize_hint);
54-
});
55-
}
5642

57-
Delete(finalizer);
58-
}, hint);
43+
node::Environment* node_env =
44+
static_cast<node_napi_env>(finalizer->_env)->node_env();
45+
node_env->SetImmediate(
46+
[finalizer = std::move(finalizer)](node::Environment* env) {
47+
if (finalizer->_finalize_callback == nullptr) return;
48+
49+
v8::HandleScope handle_scope(finalizer->_env->isolate);
50+
v8::Context::Scope context_scope(finalizer->_env->context());
51+
52+
finalizer->_env->CallIntoModuleThrow([&](napi_env env) {
53+
finalizer->_finalize_callback(
54+
env,
55+
finalizer->_finalize_data,
56+
finalizer->_finalize_hint);
57+
});
58+
});
5959
}
60+
61+
struct Deleter {
62+
void operator()(BufferFinalizer* finalizer) {
63+
Finalizer::Delete(finalizer);
64+
}
65+
};
6066
};
6167

6268
static inline napi_env NewEnv(v8::Local<v8::Context> context) {

src/node_file.cc

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,35 +170,33 @@ inline void FileHandle::Close() {
170170

171171
struct err_detail { int ret; int fd; };
172172

173-
err_detail* detail = new err_detail { ret, fd_ };
173+
err_detail detail { ret, fd_ };
174174

175175
if (ret < 0) {
176176
// Do not unref this
177-
env()->SetImmediate([](Environment* env, void* data) {
177+
env()->SetImmediate([detail](Environment* env) {
178178
char msg[70];
179-
std::unique_ptr<err_detail> detail(static_cast<err_detail*>(data));
180179
snprintf(msg, arraysize(msg),
181180
"Closing file descriptor %d on garbage collection failed",
182-
detail->fd);
181+
detail.fd);
183182
// This exception will end up being fatal for the process because
184183
// it is being thrown from within the SetImmediate handler and
185184
// there is no JS stack to bubble it to. In other words, tearing
186185
// down the process is the only reasonable thing we can do here.
187186
HandleScope handle_scope(env->isolate());
188-
env->ThrowUVException(detail->ret, "close", msg);
189-
}, detail);
187+
env->ThrowUVException(detail.ret, "close", msg);
188+
});
190189
return;
191190
}
192191

193192
// If the close was successful, we still want to emit a process warning
194193
// to notify that the file descriptor was gc'd. We want to be noisy about
195194
// this because not explicitly closing the FileHandle is a bug.
196-
env()->SetUnrefImmediate([](Environment* env, void* data) {
197-
std::unique_ptr<err_detail> detail(static_cast<err_detail*>(data));
195+
env()->SetUnrefImmediate([detail](Environment* env) {
198196
ProcessEmitWarning(env,
199197
"Closing file descriptor %d on garbage collection",
200-
detail->fd);
201-
}, detail);
198+
detail.fd);
199+
});
202200
}
203201

204202
void FileHandle::CloseReq::Resolve() {

0 commit comments

Comments
 (0)