Skip to content
File

Blob: src/workerd/jsg/async-context.c++

7.0 KB
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 
12namespace workerd::jsg {
13 
14namespace {
15inline 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 
27AsyncContextFrame::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 
47AsyncContextFrame::StorageEntry::StorageEntry(kj::Own<StorageKey> key, Value value)
48 : key(kj::mv(key)),
49 value(kj::mv(value)) {}
50 
51AsyncContextFrame::StorageEntry AsyncContextFrame::StorageEntry::clone(Lock& js) {
52 return StorageEntry(kj::addRef(*key), value.addRef(js));
53}
54 
55kj::Maybe<AsyncContextFrame&> AsyncContextFrame::current(Lock& js) {
56 return current(js.v8Isolate);
57}
58 
59kj::Maybe<Ref<AsyncContextFrame>> AsyncContextFrame::currentRef(Lock& js) {
60 return jsg::AsyncContextFrame::current(js).map(
61 [](jsg::AsyncContextFrame& frame) { return frame.addRef(); });
62}
63 
64kj::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 
74Ref<AsyncContextFrame> AsyncContextFrame::create(Lock& js, StorageEntry storageEntry) {
75 return js.alloc<AsyncContextFrame>(js, kj::mv(storageEntry));
76}
77 
78v8::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 
85v8::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 
103v8::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 
129v8::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 
150kj::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 
156AsyncContextFrame::Scope::Scope(Lock& js, kj::Maybe<AsyncContextFrame&> resource)
157 : Scope(js.v8Isolate, resource) {}
158 
159AsyncContextFrame::Scope::Scope(v8::Isolate* ptr, kj::Maybe<AsyncContextFrame&> maybeFrame)
160 : isolate(ptr),
161 prior(AsyncContextFrame::current(ptr)) {
162 maybeSetV8ContinuationContext(isolate, maybeFrame);
163}
164 
165AsyncContextFrame::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 
170AsyncContextFrame::Scope::~Scope() noexcept(false) {
171 maybeSetV8ContinuationContext(isolate, prior);
172}
173 
174AsyncContextFrame::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 
178v8::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 
185v8::Local<v8::Object> AsyncContextFrame::getJSWrapper(Lock& js) {
186 return getJSWrapper(js.v8Isolate);
187}
188 
189void 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