GeezerVison is showing github.com — src: track async resources via pointers to stack-allocated handles · nodejs/node@2cd6a3b
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: src/api/callback.cc +16-7Lines changed: 16 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -48,17 +48,26 @@ InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags) | 48 | 48 | flags, | 49 | 49 | async_wrap->context_frame()) {} | 50 | 50 |
| 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) | 56 | 57 | : env_(env), | 57 | 58 | async_context_(asyncContext), | 58 | | - object_(object), | 59 | 59 | skip_hooks_(flags & kSkipAsyncHooks), | 60 | 60 | skip_task_queues_(flags & kSkipTaskQueues) { | 61 | 61 | 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 | + | 62 | 71 | env->PushAsyncCallbackScope(); | 63 | 72 |
| 64 | 73 | if (!env->can_call_into_js()) { | @@ -85,7 +94,7 @@ InternalCallbackScope::InternalCallbackScope(Environment* env, | 85 | 94 | isolate, async_context_frame::exchange(isolate, context_frame)); | 86 | 95 |
| 87 | 96 | 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_); | 89 | 98 |
| 90 | 99 | pushed_ids_ = true; | 91 | 100 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/async_wrap.cc +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -278,7 +278,7 @@ void AsyncWrap::PushAsyncContext(const FunctionCallbackInfo<Value>& args) { | 278 | 278 | // then the checks in push_async_ids() and pop_async_id() will. | 279 | 279 | double async_id = args[0]->NumberValue(env->context()).FromJust(); | 280 | 280 | 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); | 282 | 282 | } | 283 | 283 |
| 284 | 284 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/env-inl.h +4-2Lines changed: 4 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -106,8 +106,10 @@ v8::Local<v8::Array> AsyncHooks::js_execution_async_resources() { | 106 | 106 | } | 107 | 107 |
| 108 | 108 | 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]; | 111 | 113 | } | 112 | 114 |
| 113 | 115 | inline v8::Local<v8::String> AsyncHooks::provider_string(int idx) { | |
Collapse file Copy file name to clipboardExpand all lines: src/env.cc +10-11Lines changed: 10 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -122,7 +122,8 @@ void Environment::ResetPromiseHooks(Local<Function> init, | 122 | 122 | // Remember to keep this code aligned with pushAsyncContext() in JS. | 123 | 123 | void AsyncHooks::push_async_context(double async_id, | 124 | 124 | double trigger_async_id, | 125 | | - Local<Object> resource) { | | 125 | + Local<Object>* resource) { | | 126 | + CHECK_IMPLIES(resource != nullptr, !resource->IsEmpty()); | 126 | 127 | // Since async_hooks is experimental, do only perform the check | 127 | 128 | // when async_hooks is enabled. | 128 | 129 | if (fields_[kCheck] > 0) { | @@ -140,14 +141,14 @@ void AsyncHooks::push_async_context(double async_id, | 140 | 141 |
| 141 | 142 | #ifdef DEBUG | 142 | 143 | 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]); | 144 | 145 | #endif | 145 | 146 |
| 146 | 147 | // When this call comes from JS (as a way of increasing the stack size), | 147 | 148 | // `resource` will be empty, because JS caches these values anyway. | 148 | | - if (!resource.IsEmpty()) { | | 149 | + if (resource != nullptr) { | 149 | 150 | 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<>! | 151 | 152 | native_execution_async_resources_[offset] = resource; | 152 | 153 | } | 153 | 154 | } | @@ -172,11 +173,11 @@ bool AsyncHooks::pop_async_context(double async_id) { | 172 | 173 | fields_[kStackLength] = offset; | 173 | 174 |
| 174 | 175 | if (offset < native_execution_async_resources_.size() && | 175 | | - !native_execution_async_resources_[offset].IsEmpty()) [[likely]] { | | 176 | + native_execution_async_resources_[offset] != nullptr) [[likely]] { | 176 | 177 | #ifdef DEBUG | 177 | 178 | for (uint32_t i = offset + 1; i < native_execution_async_resources_.size(); | 178 | 179 | i++) { | 179 | | - CHECK(native_execution_async_resources_[i].IsEmpty()); | | 180 | + CHECK_NULL(native_execution_async_resources_[i]); | 180 | 181 | } | 181 | 182 | #endif | 182 | 183 | native_execution_async_resources_.resize(offset); | @@ -1717,7 +1718,6 @@ AsyncHooks::AsyncHooks(Isolate* isolate, const SerializeInfo* info) | 1717 | 1718 | fields_(isolate, kFieldsCount, MAYBE_FIELD_PTR(info, fields)), | 1718 | 1719 | async_id_fields_( | 1719 | 1720 | isolate, kUidFieldsCount, MAYBE_FIELD_PTR(info, async_id_fields)), | 1720 | | - native_execution_async_resources_(isolate), | 1721 | 1721 | info_(info) { | 1722 | 1722 | HandleScope handle_scope(isolate); | 1723 | 1723 | if (info == nullptr) { | @@ -1806,10 +1806,9 @@ AsyncHooks::SerializeInfo AsyncHooks::Serialize(Local<Context> context, | 1806 | 1806 | native_execution_async_resources_.size()); | 1807 | 1807 | for (size_t i = 0; i < native_execution_async_resources_.size(); i++) { | 1808 | 1808 | 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]); | 1813 | 1812 | } | 1814 | 1813 |
| 1815 | 1814 | // At the moment, promise hooks are not supported in the startup snapshot. | |
Collapse file Copy file name to clipboardExpand all lines: src/env.h +5-10Lines changed: 5 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 59 | 59 | #include <array> | 60 | 60 | #include <atomic> | 61 | 61 | #include <cstdint> | | 62 | +#include <deque> | 62 | 63 | #include <functional> | 63 | 64 | #include <list> | 64 | 65 | #include <memory> | @@ -324,7 +325,7 @@ class AsyncHooks : public MemoryRetainer { | 324 | 325 | // `pop_async_context()` or `clear_async_id_stack()` are called. | 325 | 326 | void push_async_context(double async_id, | 326 | 327 | double trigger_async_id, | 327 | | - v8::Local<v8::Object> execution_async_resource); | | 328 | + v8::Local<v8::Object>* execution_async_resource); | 328 | 329 | bool pop_async_context(double async_id); | 329 | 330 | void clear_async_id_stack(); // Used in fatal exceptions. | 330 | 331 |
| @@ -386,15 +387,9 @@ class AsyncHooks : public MemoryRetainer { | 386 | 387 |
| 387 | 388 | v8::Global<v8::Array> js_execution_async_resources_; | 388 | 389 |
| 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_; | 398 | 393 |
| 399 | 394 | // Non-empty during deserialization | 400 | 395 | const SerializeInfo* info_ = nullptr; | |
Collapse file Copy file name to clipboardExpand all lines: src/node_internals.h +9-2Lines changed: 9 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 37 | 37 | #include <cstdlib> | 38 | 38 |
| 39 | 39 | #include <string> | | 40 | +#include <variant> | 40 | 41 | #include <vector> | 41 | 42 |
| 42 | 43 | struct sockaddr; | @@ -245,9 +246,14 @@ class InternalCallbackScope { | 245 | 246 | // compatibility issues, but it shouldn't.) | 246 | 247 | kSkipTaskQueues = 2 | 247 | 248 | }; | | 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). | 248 | 254 | InternalCallbackScope( | 249 | 255 | Environment* env, | 250 | | - v8::Local<v8::Object> object, | | 256 | + std::variant<v8::Local<v8::Object>, v8::Local<v8::Object>*> object, | 251 | 257 | const async_context& asyncContext, | 252 | 258 | int flags = kNoFlags, | 253 | 259 | v8::Local<v8::Value> context_frame = v8::Local<v8::Value>()); | @@ -263,7 +269,8 @@ class InternalCallbackScope { | 263 | 269 | private: | 264 | 270 | Environment* env_; | 265 | 271 | async_context async_context_; | 266 | | - v8::Local<v8::Object> object_; | | 272 | + v8::Local<v8::Object> object_storage_; | | 273 | + v8::Local<v8::Object>* object_; | 267 | 274 | bool skip_hooks_; | 268 | 275 | bool skip_task_queues_; | 269 | 276 | bool failed_ = false; | |
Collapse file Copy file name to clipboardExpand all lines: src/node_task_queue.cc +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -100,10 +100,11 @@ void PromiseRejectCallback(PromiseRejectMessage message) { | 100 | 100 | if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol()) | 101 | 101 | .To(&trigger_async_id)) return; | 102 | 102 |
| | 103 | + Local<Object> promise_as_obj = promise; | 103 | 104 | if (async_id != AsyncWrap::kInvalidAsyncId && | 104 | 105 | trigger_async_id != AsyncWrap::kInvalidAsyncId) { | 105 | 106 | env->async_hooks()->push_async_context( | 106 | | - async_id, trigger_async_id, promise); | | 107 | + async_id, trigger_async_id, &promise_as_obj); | 107 | 108 | } | 108 | 109 |
| 109 | 110 | USE(callback->Call( | |
|