Skip to content

Commit 2cd6a3b

Browse files
addaleaxtargos
authored andcommitted
src: track async resources via pointers to stack-allocated handles
This addresses an existing `TODO` to remove the need for a separate `LocalVector`. Since all relevant handles are already present on the stack, we can track their addresses instead of storing the objects in a separate container. PR-URL: #59704 Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 7f347fc commit 2cd6a3b

File tree

7 files changed

+47
-34
lines changed

7 files changed

+47
-34
lines changed

src/api/callback.cc

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,26 @@ InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
4848
flags,
4949
async_wrap->context_frame()) {}
5050

51-
InternalCallbackScope::InternalCallbackScope(Environment* env,
52-
Local<Object> object,
53-
const async_context& asyncContext,
54-
int flags,
55-
Local<Value> context_frame)
51+
InternalCallbackScope::InternalCallbackScope(
52+
Environment* env,
53+
std::variant<Local<Object>, Local<Object>*> object,
54+
const async_context& asyncContext,
55+
int flags,
56+
Local<Value> context_frame)
5657
: env_(env),
5758
async_context_(asyncContext),
58-
object_(object),
5959
skip_hooks_(flags & kSkipAsyncHooks),
6060
skip_task_queues_(flags & kSkipTaskQueues) {
6161
CHECK_NOT_NULL(env);
62+
63+
if (std::holds_alternative<Local<Object>>(object)) {
64+
object_storage_ = std::get<Local<Object>>(object);
65+
object_ = &object_storage_;
66+
} else {
67+
object_ = std::get<Local<Object>*>(object);
68+
CHECK_NOT_NULL(object_);
69+
}
70+
6271
env->PushAsyncCallbackScope();
6372

6473
if (!env->can_call_into_js()) {
@@ -85,7 +94,7 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
8594
isolate, async_context_frame::exchange(isolate, context_frame));
8695

8796
env->async_hooks()->push_async_context(
88-
async_context_.async_id, async_context_.trigger_async_id, object);
97+
async_context_.async_id, async_context_.trigger_async_id, object_);
8998

9099
pushed_ids_ = true;
91100

src/async_wrap.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ void AsyncWrap::PushAsyncContext(const FunctionCallbackInfo<Value>& args) {
278278
// then the checks in push_async_ids() and pop_async_id() will.
279279
double async_id = args[0]->NumberValue(env->context()).FromJust();
280280
double trigger_async_id = args[1]->NumberValue(env->context()).FromJust();
281-
env->async_hooks()->push_async_context(async_id, trigger_async_id, {});
281+
env->async_hooks()->push_async_context(async_id, trigger_async_id, nullptr);
282282
}
283283

284284

src/env-inl.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ v8::Local<v8::Array> AsyncHooks::js_execution_async_resources() {
106106
}
107107

108108
v8::Local<v8::Object> AsyncHooks::native_execution_async_resource(size_t i) {
109-
if (i >= native_execution_async_resources_.size()) return {};
110-
return native_execution_async_resources_[i];
109+
if (i >= native_execution_async_resources_.size() ||
110+
native_execution_async_resources_[i] == nullptr)
111+
return {};
112+
return *native_execution_async_resources_[i];
111113
}
112114

113115
inline v8::Local<v8::String> AsyncHooks::provider_string(int idx) {

src/env.cc

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ void Environment::ResetPromiseHooks(Local<Function> init,
122122
// Remember to keep this code aligned with pushAsyncContext() in JS.
123123
void AsyncHooks::push_async_context(double async_id,
124124
double trigger_async_id,
125-
Local<Object> resource) {
125+
Local<Object>* resource) {
126+
CHECK_IMPLIES(resource != nullptr, !resource->IsEmpty());
126127
// Since async_hooks is experimental, do only perform the check
127128
// when async_hooks is enabled.
128129
if (fields_[kCheck] > 0) {
@@ -140,14 +141,14 @@ void AsyncHooks::push_async_context(double async_id,
140141

141142
#ifdef DEBUG
142143
for (uint32_t i = offset; i < native_execution_async_resources_.size(); i++)
143-
CHECK(native_execution_async_resources_[i].IsEmpty());
144+
CHECK_NULL(native_execution_async_resources_[i]);
144145
#endif
145146

146147
// When this call comes from JS (as a way of increasing the stack size),
147148
// `resource` will be empty, because JS caches these values anyway.
148-
if (!resource.IsEmpty()) {
149+
if (resource != nullptr) {
149150
native_execution_async_resources_.resize(offset + 1);
150-
// Caveat: This is a v8::Local<> assignment, we do not keep a v8::Global<>!
151+
// Caveat: This is a v8::Local<>* assignment, we do not keep a v8::Global<>!
151152
native_execution_async_resources_[offset] = resource;
152153
}
153154
}
@@ -172,11 +173,11 @@ bool AsyncHooks::pop_async_context(double async_id) {
172173
fields_[kStackLength] = offset;
173174

174175
if (offset < native_execution_async_resources_.size() &&
175-
!native_execution_async_resources_[offset].IsEmpty()) [[likely]] {
176+
native_execution_async_resources_[offset] != nullptr) [[likely]] {
176177
#ifdef DEBUG
177178
for (uint32_t i = offset + 1; i < native_execution_async_resources_.size();
178179
i++) {
179-
CHECK(native_execution_async_resources_[i].IsEmpty());
180+
CHECK_NULL(native_execution_async_resources_[i]);
180181
}
181182
#endif
182183
native_execution_async_resources_.resize(offset);
@@ -1717,7 +1718,6 @@ AsyncHooks::AsyncHooks(Isolate* isolate, const SerializeInfo* info)
17171718
fields_(isolate, kFieldsCount, MAYBE_FIELD_PTR(info, fields)),
17181719
async_id_fields_(
17191720
isolate, kUidFieldsCount, MAYBE_FIELD_PTR(info, async_id_fields)),
1720-
native_execution_async_resources_(isolate),
17211721
info_(info) {
17221722
HandleScope handle_scope(isolate);
17231723
if (info == nullptr) {
@@ -1806,10 +1806,9 @@ AsyncHooks::SerializeInfo AsyncHooks::Serialize(Local<Context> context,
18061806
native_execution_async_resources_.size());
18071807
for (size_t i = 0; i < native_execution_async_resources_.size(); i++) {
18081808
info.native_execution_async_resources[i] =
1809-
native_execution_async_resources_[i].IsEmpty() ? SIZE_MAX :
1810-
creator->AddData(
1811-
context,
1812-
native_execution_async_resources_[i]);
1809+
native_execution_async_resources_[i] == nullptr
1810+
? SIZE_MAX
1811+
: creator->AddData(context, *native_execution_async_resources_[i]);
18131812
}
18141813

18151814
// At the moment, promise hooks are not supported in the startup snapshot.

src/env.h

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#include <array>
6060
#include <atomic>
6161
#include <cstdint>
62+
#include <deque>
6263
#include <functional>
6364
#include <list>
6465
#include <memory>
@@ -324,7 +325,7 @@ class AsyncHooks : public MemoryRetainer {
324325
// `pop_async_context()` or `clear_async_id_stack()` are called.
325326
void push_async_context(double async_id,
326327
double trigger_async_id,
327-
v8::Local<v8::Object> execution_async_resource);
328+
v8::Local<v8::Object>* execution_async_resource);
328329
bool pop_async_context(double async_id);
329330
void clear_async_id_stack(); // Used in fatal exceptions.
330331

@@ -386,15 +387,9 @@ class AsyncHooks : public MemoryRetainer {
386387

387388
v8::Global<v8::Array> js_execution_async_resources_;
388389

389-
// TODO(@jasnell): Note that this is technically illegal use of
390-
// v8::Locals which should be kept on the stack. Here, the entries
391-
// in this object grows and shrinks with the C stack, and entries
392-
// will be in the right handle scopes, but v8::Locals are supposed
393-
// to remain on the stack and not the heap. For general purposes
394-
// this *should* be ok but may need to be looked at further should
395-
// v8 become stricter in the future about v8::Locals being held in
396-
// the stack.
397-
v8::LocalVector<v8::Object> native_execution_async_resources_;
390+
// We avoid storing the handles directly here, because they are already
391+
// properly allocated on the stack, we just need access to them here.
392+
std::deque<v8::Local<v8::Object>*> native_execution_async_resources_;
398393

399394
// Non-empty during deserialization
400395
const SerializeInfo* info_ = nullptr;

src/node_internals.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <cstdlib>
3838

3939
#include <string>
40+
#include <variant>
4041
#include <vector>
4142

4243
struct sockaddr;
@@ -245,9 +246,14 @@ class InternalCallbackScope {
245246
// compatibility issues, but it shouldn't.)
246247
kSkipTaskQueues = 2
247248
};
249+
// You need to either guarantee that this `InternalCallbackScope` is
250+
// stack-allocated itself, OR that `object` is a pointer to a stack-allocated
251+
// `v8::Local<v8::Object>` which outlives this scope (e.g. for the
252+
// public `CallbackScope` which indirectly allocates an instance of
253+
// this class for ABI stability purposes).
248254
InternalCallbackScope(
249255
Environment* env,
250-
v8::Local<v8::Object> object,
256+
std::variant<v8::Local<v8::Object>, v8::Local<v8::Object>*> object,
251257
const async_context& asyncContext,
252258
int flags = kNoFlags,
253259
v8::Local<v8::Value> context_frame = v8::Local<v8::Value>());
@@ -263,7 +269,8 @@ class InternalCallbackScope {
263269
private:
264270
Environment* env_;
265271
async_context async_context_;
266-
v8::Local<v8::Object> object_;
272+
v8::Local<v8::Object> object_storage_;
273+
v8::Local<v8::Object>* object_;
267274
bool skip_hooks_;
268275
bool skip_task_queues_;
269276
bool failed_ = false;

src/node_task_queue.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,11 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
100100
if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol())
101101
.To(&trigger_async_id)) return;
102102

103+
Local<Object> promise_as_obj = promise;
103104
if (async_id != AsyncWrap::kInvalidAsyncId &&
104105
trigger_async_id != AsyncWrap::kInvalidAsyncId) {
105106
env->async_hooks()->push_async_context(
106-
async_id, trigger_async_id, promise);
107+
async_id, trigger_async_id, &promise_as_obj);
107108
}
108109

109110
USE(callback->Call(

0 commit comments

Comments
 (0)