Skip to content

Commit 5ea1835

Browse files
committed
Move DeferredProxy<T> to api/deferred-proxy.h
I switched io-context.h to include api/deferred-proxy.h instead of api/util.h, which necessitated some explicit inclusions of api/util.h in some .c++ files.
1 parent 5758fea commit 5ea1835

File tree

8 files changed

+196
-171
lines changed

8 files changed

+196
-171
lines changed

src/workerd/api/crypto-impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// Don't include this file unless your name is "crypto*.c++".
99

1010
#include "crypto.h"
11+
#include <workerd/api/util.h>
1112
#include <kj/encoding.h>
1213
#include <openssl/evp.h>
1314
#include <openssl/bio.h>

src/workerd/api/deferred-proxy-test.c++

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include "util.h"
1+
#include "deferred-proxy.h"
22
#include <kj/test.h>
33

44
namespace workerd::api {

src/workerd/api/deferred-proxy.h

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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+
5+
#pragma once
6+
7+
#include <kj/async.h>
8+
9+
namespace workerd::api {
10+
11+
// =======================================================================================
12+
13+
template <typename T>
14+
struct DeferredProxy {
15+
// Some API methods return Promise<DeferredProxy<T>> when the task can be separated into two
16+
// parts: some work that must be done with the IoContext still live, and some part that
17+
// can occur after the IoContext completes, but which should still be performed before
18+
// the overall task is "done".
19+
//
20+
// In particular, when an HTTP event ends up proxying the response body stream (or WebSocket
21+
// stream) directly to/from origin, then that streaming can take place without pinning the
22+
// isolate in memory, and without holding the IoContext open. So,
23+
// `ServiceWorkerGlobalScope::request()` returns `Promise<DeferredProxy<void>>`. The outer
24+
// Promise waits for the JavaScript work to be done, and the inner DeferredProxy<void> represents
25+
// the proxying step.
26+
//
27+
// Note that if you're performing a task that resolves to DeferredProxy but JavaScript is
28+
// actually waiting for the result of the task, then it's your responsibility to call
29+
// IoContext::current().registerPendingEvent() and attach it to `proxyTask`, otherwise
30+
// the request might be canceled as the proxy task won't be recognized as something that the
31+
// request is waiting on.
32+
//
33+
// TODO(cleanup): Now that we have jsg::Promise, it might make sense for deferred proxying to
34+
// be represented as `jsg::Promise<api::DeferredProxy<T>>`, since the outer promise is
35+
// intended to represent activity that happens in JavaScript while the inner one represents
36+
// pure I/O. This will require some refactoring, though.
37+
38+
kj::Promise<T> proxyTask;
39+
};
40+
41+
inline DeferredProxy<void> newNoopDeferredProxy() {
42+
return DeferredProxy<void> { kj::READY_NOW };
43+
}
44+
45+
template <typename T>
46+
inline DeferredProxy<T> newNoopDeferredProxy(T&& value) {
47+
return DeferredProxy<T> { kj::mv(value) };
48+
}
49+
50+
template <typename T>
51+
inline kj::Promise<DeferredProxy<T>> addNoopDeferredProxy(kj::Promise<T> promise) {
52+
// Helper method to use when you need to return `Promise<DeferredProxy<T>>` but no part of the
53+
// operation you are returning is eligible to be deferred past the IoContext lifetime.
54+
co_return newNoopDeferredProxy(co_await promise);
55+
}
56+
inline kj::Promise<DeferredProxy<void>> addNoopDeferredProxy(kj::Promise<void> promise) {
57+
co_await promise;
58+
co_return newNoopDeferredProxy();
59+
}
60+
61+
// ---------------------------------------------------------
62+
// Deferred proxy coroutine integration
63+
64+
class BeginDeferredProxyingConstant final {};
65+
constexpr BeginDeferredProxyingConstant BEGIN_DEFERRED_PROXYING {};
66+
// A magic constant which a DeferredProxyPromise<T> coroutine can `co_yield` to indicate that the
67+
// deferred proxying phase of its operation has begun.
68+
69+
template <typename T>
70+
class DeferredProxyPromise: public kj::Promise<DeferredProxy<T>> {
71+
// A "strong typedef" for a kj::Promise<DeferredProxy<T>>. DeferredProxyPromise<T> is intended to
72+
// be used as the return type for coroutines, in which case the coroutine implementation gains the
73+
// following features:
74+
//
75+
// - `co_yield BEGIN_DEFERRED_PROXYING` fulfills the outer kj::Promise<DeferredProxy<T>>. The
76+
// resulting DeferredProxy<T> object contains a `proxyTask` Promise which owns the coroutine.
77+
//
78+
// - `co_return` implicitly fulfills the outer Promise for the DeferredProxy<T> (if it has not
79+
// already been fulfilled by the magic `co_yield` described above), then fulfills the inner
80+
// `proxyTask`.
81+
//
82+
// - Unhandled exceptions reject the outer kj::Promise<DeferredProxy<T>> (if it has not already
83+
// been fulfilled by the magic `co_yield` described above), then reject the inner `proxyTask`.
84+
85+
public:
86+
DeferredProxyPromise(kj::Promise<DeferredProxy<T>> promise)
87+
: kj::Promise<DeferredProxy<T>>(kj::mv(promise)) {}
88+
// Allow conversion from a regular Promise. This allows our `promise_type::get_return_object()`
89+
// implementation to be implemented as a regular Promise-returning coroutine.
90+
91+
class Coroutine;
92+
using promise_type = Coroutine;
93+
// The coroutine adapter class, required for the compiler to know how to create coroutines
94+
// returning DeferredProxyPromise<T>. Since the whole point of DeferredProxyPromise<T> is to serve
95+
// as a coroutine return type, there's not really any point hiding promise_type inside of a
96+
// coroutine_traits specialization, like kj::Promise<T> does.
97+
};
98+
99+
template <typename T>
100+
class DeferredProxyPromise<T>::Coroutine:
101+
public kj::_::CoroutineMixin<DeferredProxyPromise<T>::Coroutine, T> {
102+
// The coroutine adapter type for DeferredProxyPromise<T>. Most of the work is forwarded to the
103+
// regular kj::Promise<T> coroutine adapter.
104+
105+
using InnerCoroutineAdapter =
106+
typename kj::_::stdcoro::coroutine_traits<kj::Promise<T>, Args...>::promise_type;
107+
108+
public:
109+
using Handle = kj::_::stdcoro::coroutine_handle<Coroutine>;
110+
111+
Coroutine(kj::SourceLocation location = {}): inner(Handle::from_promise(*this), location) {}
112+
113+
kj::Promise<DeferredProxy<T>> get_return_object() {
114+
// We need to return a RAII object which will destroy this (as in, `this`) coroutine adapter.
115+
// The logic which calls `coroutine_handle<>::destroy()` is tucked away in our inner coroutine
116+
// adapter, however, leading to the weird situation where the `inner.get_return_object()`
117+
// Promise owns `this`. Thus, we cannot store the inner promise in our own coroutine adapter
118+
// class, because that would cause a reference cycle. Fortunately, we can implement our own
119+
// `get_return_object()` as a regular Promise-returning coroutine and keep the inner Promise in
120+
// our coroutine frame, giving the caller transitive ownership of the DeferredProxyPromise<T>
121+
// coroutine by way of the kj::Promise<DeferredProxy<T>> coroutine. Later on, when the outer
122+
// Promise is fulfilled, the caller will gain direct ownership of the DeferredProxyPromise<T>
123+
// coroutine via the `proxyTask` promise.
124+
auto proxyTask = inner.get_return_object();
125+
co_await beginDeferredProxying.promise;
126+
co_return DeferredProxy<T> { kj::mv(proxyTask) };
127+
}
128+
129+
auto initial_suspend() { return inner.initial_suspend(); }
130+
auto final_suspend() noexcept { return inner.final_suspend(); }
131+
// Just trivially forward these.
132+
133+
void unhandled_exception() {
134+
// If the outer promise hasn't yet been fulfilled, it needs to be rejected now.
135+
if (beginDeferredProxying.fulfiller->isWaiting()) {
136+
beginDeferredProxying.fulfiller->reject(kj::getCaughtExceptionAsKj());
137+
}
138+
139+
inner.unhandled_exception();
140+
}
141+
142+
kj::_::stdcoro::suspend_never yield_value(decltype(BEGIN_DEFERRED_PROXYING)) {
143+
// This allows us to write `KJ_CO_MAGIC` within a DeferredProxyPromise<T> coroutine to fulfill
144+
// the coroutine's outer promise with a DeferredProxy<T>.
145+
// This could alternatively be an await_transform() with a magic parameter type.
146+
if (beginDeferredProxying.fulfiller->isWaiting()) {
147+
beginDeferredProxying.fulfiller->fulfill();
148+
}
149+
150+
return {};
151+
}
152+
153+
template <CoroutineYieldValue<InnerCoroutineAdapter> U>
154+
auto yield_value(U&& value) {
155+
// Forward all other `co_yield`s to the inner coroutine, if it has a `yield_value()`
156+
// implementation -- it might implement some magic, too.
157+
return inner.yield_value(kj::fwd<U>(value));
158+
}
159+
160+
void fulfill(kj::_::FixVoid<T>&& value) {
161+
// Required by CoroutineMixin implementation to implement `co_return`.
162+
163+
// Fulfill the outer promise if it hasn't already been fulfilled.
164+
if (beginDeferredProxying.fulfiller->isWaiting()) {
165+
beginDeferredProxying.fulfiller->fulfill();
166+
}
167+
168+
inner.fulfill(kj::mv(value));
169+
}
170+
171+
template <typename U>
172+
auto await_transform(U&& awaitable) {
173+
// Trivially forward everything, so we can await anything a kj::Promise<T> can.
174+
return inner.await_transform(kj::fwd<U>(awaitable));
175+
}
176+
177+
operator kj::_::CoroutineBase&() { return inner; }
178+
// Required by Awaiter<T>::await_suspend() to support awaiting Promises.
179+
180+
private:
181+
typename kj::_::stdcoro::coroutine_traits<kj::Promise<T>>::promise_type inner;
182+
// We defer the majority of the implementation to the regular kj::Promise<T> coroutine adapter.
183+
184+
kj::PromiseFulfillerPair<void> beginDeferredProxying = kj::newPromiseAndFulfiller<void>();
185+
// Our `get_return_object()` function returns a kj::Promise<DeferredProxy<T>>, waits on this
186+
// `beginDeferredProxying.promise`, then fulfills its Promise with the result of
187+
// `inner.get_return_object()`.
188+
};
189+
190+
} // namespace workerd::api

src/workerd/api/global-scope.c++

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <workerd/util/sentry.h>
2020
#include <workerd/util/thread-scopes.h>
2121
#include <workerd/api/hibernatable-web-socket.h>
22+
#include <workerd/api/util.h>
2223

2324
namespace workerd::api {
2425

src/workerd/api/streams/internal.c++

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "writable.h"
88
#include <workerd/jsg/jsg.h>
99
#include <kj/vector.h>
10+
#include <workerd/api/util.h>
1011

1112
namespace workerd::api {
1213

src/workerd/api/util.h

Lines changed: 0 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -104,175 +104,6 @@ double dateNow();
104104

105105
// =======================================================================================
106106

107-
template <typename T>
108-
struct DeferredProxy {
109-
// Some API methods return Promise<DeferredProxy<T>> when the task can be separated into two
110-
// parts: some work that must be done with the IoContext still live, and some part that
111-
// can occur after the IoContext completes, but which should still be performed before
112-
// the overall task is "done".
113-
//
114-
// In particular, when an HTTP event ends up proxying the response body stream (or WebSocket
115-
// stream) directly to/from origin, then that streaming can take place without pinning the
116-
// isolate in memory, and without holding the IoContext open. So,
117-
// `ServiceWorkerGlobalScope::request()` returns `Promise<DeferredProxy<void>>`. The outer
118-
// Promise waits for the JavaScript work to be done, and the inner DeferredProxy<void> represents
119-
// the proxying step.
120-
//
121-
// Note that if you're performing a task that resolves to DeferredProxy but JavaScript is
122-
// actually waiting for the result of the task, then it's your responsibility to call
123-
// IoContext::current().registerPendingEvent() and attach it to `proxyTask`, otherwise
124-
// the request might be canceled as the proxy task won't be recognized as something that the
125-
// request is waiting on.
126-
//
127-
// TODO(cleanup): Now that we have jsg::Promise, it might make sense for deferred proxying to
128-
// be represented as `jsg::Promise<api::DeferredProxy<T>>`, since the outer promise is
129-
// intended to represent activity that happens in JavaScript while the inner one represents
130-
// pure I/O. This will require some refactoring, though.
131-
132-
kj::Promise<T> proxyTask;
133-
};
134-
135-
inline DeferredProxy<void> newNoopDeferredProxy() {
136-
return DeferredProxy<void> { kj::READY_NOW };
137-
}
138-
139-
template <typename T>
140-
inline DeferredProxy<T> newNoopDeferredProxy(T&& value) {
141-
return DeferredProxy<T> { kj::mv(value) };
142-
}
143-
144-
template <typename T>
145-
inline kj::Promise<DeferredProxy<T>> addNoopDeferredProxy(kj::Promise<T> promise) {
146-
// Helper method to use when you need to return `Promise<DeferredProxy<T>>` but no part of the
147-
// operation you are returning is eligible to be deferred past the IoContext lifetime.
148-
co_return newNoopDeferredProxy(co_await promise);
149-
}
150-
inline kj::Promise<DeferredProxy<void>> addNoopDeferredProxy(kj::Promise<void> promise) {
151-
co_await promise;
152-
co_return newNoopDeferredProxy();
153-
}
154-
155-
// ---------------------------------------------------------
156-
// Deferred proxy coroutine integration
157-
158-
class BeginDeferredProxyingConstant final {};
159-
constexpr BeginDeferredProxyingConstant BEGIN_DEFERRED_PROXYING {};
160-
// A magic constant which a DeferredProxyPromise<T> coroutine can `co_yield` to indicate that the
161-
// deferred proxying phase of its operation has begun.
162-
163-
template <typename T>
164-
class DeferredProxyPromise: public kj::Promise<DeferredProxy<T>> {
165-
// A "strong typedef" for a kj::Promise<DeferredProxy<T>>. DeferredProxyPromise<T> is intended to
166-
// be used as the return type for coroutines, in which case the coroutine implementation gains the
167-
// following features:
168-
//
169-
// - `co_yield BEGIN_DEFERRED_PROXYING` fulfills the outer kj::Promise<DeferredProxy<T>>. The
170-
// resulting DeferredProxy<T> object contains a `proxyTask` Promise which owns the coroutine.
171-
//
172-
// - `co_return` implicitly fulfills the outer Promise for the DeferredProxy<T> (if it has not
173-
// already been fulfilled by the magic `co_yield` described above), then fulfills the inner
174-
// `proxyTask`.
175-
//
176-
// - Unhandled exceptions reject the outer kj::Promise<DeferredProxy<T>> (if it has not already
177-
// been fulfilled by the magic `co_yield` described above), then reject the inner `proxyTask`.
178-
179-
public:
180-
DeferredProxyPromise(kj::Promise<DeferredProxy<T>> promise)
181-
: kj::Promise<DeferredProxy<T>>(kj::mv(promise)) {}
182-
// Allow conversion from a regular Promise. This allows our `promise_type::get_return_object()`
183-
// implementation to be implemented as a regular Promise-returning coroutine.
184-
185-
class Coroutine;
186-
using promise_type = Coroutine;
187-
// The coroutine adapter class, required for the compiler to know how to create coroutines
188-
// returning DeferredProxyPromise<T>. Since the whole point of DeferredProxyPromise<T> is to serve
189-
// as a coroutine return type, there's not really any point hiding promise_type inside of a
190-
// coroutine_traits specialization, like kj::Promise<T> does.
191-
};
192-
193-
template <typename T>
194-
class DeferredProxyPromise<T>::Coroutine:
195-
public kj::_::CoroutineMixin<DeferredProxyPromise<T>::Coroutine, T> {
196-
// The coroutine adapter type for DeferredProxyPromise<T>. Most of the work is forwarded to the
197-
// regular kj::Promise<T> coroutine adapter.
198-
199-
public:
200-
using Handle = kj::_::stdcoro::coroutine_handle<Coroutine>;
201-
202-
Coroutine(kj::SourceLocation location = {}): inner(Handle::from_promise(*this), location) {}
203-
204-
kj::Promise<DeferredProxy<T>> get_return_object() {
205-
// We need to return a RAII object which will destroy this (as in, `this`) coroutine adapter.
206-
// The logic which calls `coroutine_handle<>::destroy()` is tucked away in our inner coroutine
207-
// adapter, however, leading to the weird situation where the `inner.get_return_object()`
208-
// Promise owns `this`. Thus, we cannot store the inner promise in our own coroutine adapter
209-
// class, because that would cause a reference cycle. Fortunately, we can implement our own
210-
// `get_return_object()` as a regular Promise-returning coroutine and keep the inner Promise in
211-
// our coroutine frame, giving the caller transitive ownership of the DeferredProxyPromise<T>
212-
// coroutine by way of the kj::Promise<DeferredProxy<T>> coroutine. Later on, when the outer
213-
// Promise is fulfilled, the caller will gain direct ownership of the DeferredProxyPromise<T>
214-
// coroutine via the `proxyTask` promise.
215-
auto proxyTask = inner.get_return_object();
216-
co_await beginDeferredProxying.promise;
217-
co_return DeferredProxy<T> { kj::mv(proxyTask) };
218-
}
219-
220-
auto initial_suspend() { return inner.initial_suspend(); }
221-
auto final_suspend() noexcept { return inner.final_suspend(); }
222-
// Just trivially forward these.
223-
224-
void unhandled_exception() {
225-
// If the outer promise hasn't yet been fulfilled, it needs to be rejected now.
226-
if (beginDeferredProxying.fulfiller->isWaiting()) {
227-
beginDeferredProxying.fulfiller->reject(kj::getCaughtExceptionAsKj());
228-
}
229-
230-
inner.unhandled_exception();
231-
}
232-
233-
kj::_::stdcoro::suspend_never yield_value(decltype(BEGIN_DEFERRED_PROXYING)) {
234-
// This allows us to write `co_yield` within a DeferredProxyPromise<T> coroutine to fulfill
235-
// the coroutine's outer promise with a DeferredProxy<T>.
236-
// This could alternatively be an await_transform() with a magic parameter type.
237-
if (beginDeferredProxying.fulfiller->isWaiting()) {
238-
beginDeferredProxying.fulfiller->fulfill();
239-
}
240-
241-
return {};
242-
}
243-
244-
void fulfill(kj::_::FixVoid<T>&& value) {
245-
// Required by CoroutineMixin implementation to implement `co_return`.
246-
247-
// Fulfill the outer promise if it hasn't already been fulfilled.
248-
if (beginDeferredProxying.fulfiller->isWaiting()) {
249-
beginDeferredProxying.fulfiller->fulfill();
250-
}
251-
252-
inner.fulfill(kj::mv(value));
253-
}
254-
255-
template <typename U>
256-
auto await_transform(U&& awaitable) {
257-
// Trivially forward everything, so we can await anything a kj::Promise<T> can.
258-
return inner.await_transform(kj::fwd<U>(awaitable));
259-
}
260-
261-
operator kj::_::CoroutineBase&() { return inner; }
262-
// Required by Awaiter<T>::await_suspend() to support awaiting Promises.
263-
264-
private:
265-
typename kj::_::stdcoro::coroutine_traits<kj::Promise<T>>::promise_type inner;
266-
// We defer the majority of the implementation to the regular kj::Promise<T> coroutine adapter.
267-
268-
kj::PromiseFulfillerPair<void> beginDeferredProxying = kj::newPromiseAndFulfiller<void>();
269-
// Our `get_return_object()` function returns a kj::Promise<DeferredProxy<T>>, waits on this
270-
// `beginDeferredProxying.promise`, then fulfills its Promise with the result of
271-
// `inner.get_return_object()`.
272-
};
273-
274-
// =======================================================================================
275-
276107
kj::Maybe<jsg::V8Ref<v8::Object>> cloneRequestCf(
277108
jsg::Lock& js, kj::Maybe<jsg::V8Ref<v8::Object>> maybeCf);
278109

0 commit comments

Comments
 (0)