File
Blob: src/workerd/jsg/async-context.c++
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | #include "async-context.h" |
| 5 | |
| 6 | #include "jsg.h" |
| 7 | |
| 8 | #include <workerd/jsg/memory.h> |
| 9 | |
| 10 | #include <v8.h> |
| 11 | |
| 12 | namespace workerd::jsg { |
| 13 | |
| 14 | namespace { |
| 15 | inline void maybeSetV8ContinuationContext( |
| 16 | v8::Isolate* isolate, kj::Maybe<AsyncContextFrame&> maybeFrame) { |
| 17 | v8::Local<v8::Value> value; |
| 18 | KJ_IF_SOME(frame, maybeFrame) { |
| 19 | value = frame.getJSWrapper(isolate); |
| 20 | } else { |
| 21 | value = v8::Undefined(isolate); |
| 22 | } |
| 23 | isolate->SetContinuationPreservedEmbedderDataV2(value); |
| 24 | } |
| 25 | } // namespace |
| 26 | |
| 27 | AsyncContextFrame::AsyncContextFrame(Lock& js, StorageEntry storageEntry) { |
| 28 | KJ_IF_SOME(frame, current(js)) { |
| 29 | // Propagate the storage context of the current frame (if any). |
| 30 | // If current(js) returns nullptr, we assume we're in the root |
| 31 | // frame and there is no storage to propagate. |
| 32 | frame.storage.eraseAll([](const auto& entry) { return entry.key->isDead(); }); |
| 33 | for (auto& entry: frame.storage) { |
| 34 | storage.insert(entry.clone(js)); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | // This case is extremely unlikely to happen but let's handle it anyway |
| 39 | // just out of an excess of caution. |
| 40 | if (storageEntry.key->isDead()) return; |
| 41 | |
| 42 | storage.upsert(kj::mv(storageEntry), [](StorageEntry& existing, StorageEntry&& row) mutable { |
| 43 | existing.value = kj::mv(row.value); |
| 44 | }); |
| 45 | } |
| 46 | |
| 47 | AsyncContextFrame::StorageEntry::StorageEntry(kj::Own<StorageKey> key, Value value) |
| 48 | : key(kj::mv(key)), |
| 49 | value(kj::mv(value)) {} |
| 50 | |
| 51 | AsyncContextFrame::StorageEntry AsyncContextFrame::StorageEntry::clone(Lock& js) { |
| 52 | return StorageEntry(kj::addRef(*key), value.addRef(js)); |
| 53 | } |
| 54 | |
| 55 | kj::Maybe<AsyncContextFrame&> AsyncContextFrame::current(Lock& js) { |
| 56 | return current(js.v8Isolate); |
| 57 | } |
| 58 | |
| 59 | kj::Maybe<Ref<AsyncContextFrame>> AsyncContextFrame::currentRef(Lock& js) { |
| 60 | return jsg::AsyncContextFrame::current(js).map( |
| 61 | [](jsg::AsyncContextFrame& frame) { return frame.addRef(); }); |
| 62 | } |
| 63 | |
| 64 | kj::Maybe<AsyncContextFrame&> AsyncContextFrame::current(v8::Isolate* isolate) { |
| 65 | auto value = isolate->GetContinuationPreservedEmbedderDataV2(); |
| 66 | KJ_IF_SOME(wrappable, Wrappable::tryUnwrapOpaque(isolate, value.As<v8::Value>())) { |
| 67 | AsyncContextFrame* frame = dynamic_cast<AsyncContextFrame*>(&wrappable); |
| 68 | KJ_ASSERT(frame != nullptr); |
| 69 | return *frame; |
| 70 | } |
| 71 | return kj::none; |
| 72 | } |
| 73 | |
| 74 | Ref<AsyncContextFrame> AsyncContextFrame::create(Lock& js, StorageEntry storageEntry) { |
| 75 | return js.alloc<AsyncContextFrame>(js, kj::mv(storageEntry)); |
| 76 | } |
| 77 | |
| 78 | v8::Local<v8::Function> AsyncContextFrame::wrap(Lock& js, |
| 79 | V8Ref<v8::Function>& fn, |
| 80 | jsg::Function<void()> validate, |
| 81 | kj::Maybe<v8::Local<v8::Value>> thisArg) { |
| 82 | return wrap(js, fn.getHandle(js), kj::mv(validate), thisArg); |
| 83 | } |
| 84 | |
| 85 | v8::Local<v8::Function> AsyncContextFrame::wrapSnapshot(Lock& js, jsg::Function<void()> validate) { |
| 86 | return js.wrapReturningFunction(js.v8Context(), |
| 87 | JSG_VISITABLE_LAMBDA((frame = AsyncContextFrame::currentRef(js), validate = kj::mv(validate)), |
| 88 | (frame, validate), (Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) { |
| 89 | validate(js); |
| 90 | auto context = js.v8Context(); |
| 91 | JSG_REQUIRE(args[0]->IsFunction(), TypeError, "The first argument must be a function"); |
| 92 | auto fn = args[0].As<v8::Function>(); |
| 93 | v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length() - 1); |
| 94 | for (int n = 1; n < args.Length(); n++) { |
| 95 | argv[n - 1] = args[n]; |
| 96 | } |
| 97 | |
| 98 | AsyncContextFrame::Scope scope(js, frame); |
| 99 | return check(fn->Call(context, context->Global(), argv.size(), argv.data())); |
| 100 | })); |
| 101 | } |
| 102 | |
| 103 | v8::Local<v8::Function> AsyncContextFrame::wrap(Lock& js, |
| 104 | v8::Local<v8::Function> fn, |
| 105 | jsg::Function<void()> validate, |
| 106 | kj::Maybe<v8::Local<v8::Value>> thisArg) { |
| 107 | auto context = js.v8Context(); |
| 108 | |
| 109 | return js.wrapReturningFunction(context, |
| 110 | JSG_VISITABLE_LAMBDA( |
| 111 | (frame = JSG_THIS, validate = kj::mv(validate), |
| 112 | thisArg = js.v8Ref(thisArg.orDefault(context->Global())), fn = js.v8Ref(fn)), |
| 113 | (frame, validate, thisArg, fn), |
| 114 | (Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) { |
| 115 | validate(js); |
| 116 | auto function = fn.getHandle(js); |
| 117 | |
| 118 | v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length()); |
| 119 | for (int n = 0; n < args.Length(); n++) { |
| 120 | argv[n] = args[n]; |
| 121 | } |
| 122 | |
| 123 | AsyncContextFrame::Scope scope(js, *frame.get()); |
| 124 | return check( |
| 125 | function->Call(js.v8Context(), thisArg.getHandle(js), argv.size(), argv.data())); |
| 126 | })); |
| 127 | } |
| 128 | |
| 129 | v8::Local<v8::Function> AsyncContextFrame::wrapRoot( |
| 130 | Lock& js, v8::Local<v8::Function> fn, kj::Maybe<v8::Local<v8::Value>> thisArg) { |
| 131 | auto context = js.v8Context(); |
| 132 | |
| 133 | return js.wrapReturningFunction(context, |
| 134 | JSG_VISITABLE_LAMBDA( |
| 135 | (thisArg = js.v8Ref(thisArg.orDefault(context->Global())), fn = js.v8Ref(fn)), |
| 136 | (thisArg, fn), (Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) { |
| 137 | auto function = fn.getHandle(js); |
| 138 | |
| 139 | v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length()); |
| 140 | for (int n = 0; n < args.Length(); n++) { |
| 141 | argv[n] = args[n]; |
| 142 | } |
| 143 | |
| 144 | AsyncContextFrame::Scope scope(js, kj::none); |
| 145 | return check( |
| 146 | function->Call(js.v8Context(), thisArg.getHandle(js), argv.size(), argv.data())); |
| 147 | })); |
| 148 | } |
| 149 | |
| 150 | kj::Maybe<Value&> AsyncContextFrame::get(StorageKey& key) { |
| 151 | KJ_ASSERT(!key.isDead()); |
| 152 | storage.eraseAll([](const auto& entry) { return entry.key->isDead(); }); |
| 153 | return storage.find(key).map([](auto& entry) -> Value& { return entry.value; }); |
| 154 | } |
| 155 | |
| 156 | AsyncContextFrame::Scope::Scope(Lock& js, kj::Maybe<AsyncContextFrame&> resource) |
| 157 | : Scope(js.v8Isolate, resource) {} |
| 158 | |
| 159 | AsyncContextFrame::Scope::Scope(v8::Isolate* ptr, kj::Maybe<AsyncContextFrame&> maybeFrame) |
| 160 | : isolate(ptr), |
| 161 | prior(AsyncContextFrame::current(ptr)) { |
| 162 | maybeSetV8ContinuationContext(isolate, maybeFrame); |
| 163 | } |
| 164 | |
| 165 | AsyncContextFrame::Scope::Scope(Lock& js, kj::Maybe<Ref<AsyncContextFrame>>& resource) |
| 166 | : Scope(js.v8Isolate, resource.map([](Ref<AsyncContextFrame>& frame) -> AsyncContextFrame& { |
| 167 | return *frame.get(); |
| 168 | })) {} |
| 169 | |
| 170 | AsyncContextFrame::Scope::~Scope() noexcept(false) { |
| 171 | maybeSetV8ContinuationContext(isolate, prior); |
| 172 | } |
| 173 | |
| 174 | AsyncContextFrame::StorageScope::StorageScope(Lock& js, StorageKey& key, Value store) |
| 175 | : frame(AsyncContextFrame::create(js, StorageEntry(kj::addRef(key), kj::mv(store)))), |
| 176 | scope(js, *frame) {} |
| 177 | |
| 178 | v8::Local<v8::Object> AsyncContextFrame::getJSWrapper(v8::Isolate* isolate) { |
| 179 | KJ_IF_SOME(handle, tryGetHandle(isolate)) { |
| 180 | return handle; |
| 181 | } |
| 182 | return attachOpaqueWrapper(isolate->GetCurrentContext(), true); |
| 183 | } |
| 184 | |
| 185 | v8::Local<v8::Object> AsyncContextFrame::getJSWrapper(Lock& js) { |
| 186 | return getJSWrapper(js.v8Isolate); |
| 187 | } |
| 188 | |
| 189 | void AsyncContextFrame::jsgVisitForGc(GcVisitor& visitor) { |
| 190 | // tracing will make the members weak and will allow |
| 191 | // them to be gc'd, which is not what we want. |
| 192 | } |
| 193 | } // namespace workerd::jsg |