Skip to content

Commit d2fe21b

Browse files
authored
Abstract over async handles (#536)
Introduce an `AsyncHandle` type to the host api, and refactor the event loop to use them instead. I reworked the interface to the underlying `fastly_async_io_select` host call, so that it's now a bit more clear when the timeout has expired. I've also added comments to `process_pending_async_tasks` to better explain the interactions between the two action queues.
1 parent 2c8a9cc commit d2fe21b

File tree

7 files changed

+227
-48
lines changed

7 files changed

+227
-48
lines changed

runtime/js-compute-runtime/builtins/request-response.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,14 +1063,16 @@ HttpReq Request::request_handle(JSObject *obj) {
10631063
JS::GetReservedSlot(obj, static_cast<uint32_t>(Request::Slots::Request)).toInt32());
10641064
}
10651065

1066-
fastly_pending_request_handle_t Request::pending_handle(JSObject *obj) {
1066+
HttpPendingReq Request::pending_handle(JSObject *obj) {
1067+
HttpPendingReq res;
1068+
10671069
JS::Value handle_val =
10681070
JS::GetReservedSlot(obj, static_cast<uint32_t>(Request::Slots::PendingRequest));
10691071
if (handle_val.isInt32()) {
1070-
return handle_val.toInt32();
1072+
res = HttpPendingReq(handle_val.toInt32());
10711073
}
10721074

1073-
return INVALID_HANDLE;
1075+
return res;
10741076
}
10751077

10761078
bool Request::is_downstream(JSObject *obj) {

runtime/js-compute-runtime/builtins/request-response.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Request final : public BuiltinImpl<Request> {
141141
static bool apply_cache_override(JSContext *cx, JS::HandleObject self);
142142

143143
static HttpReq request_handle(JSObject *obj);
144-
static fastly_pending_request_handle_t pending_handle(JSObject *obj);
144+
static HttpPendingReq pending_handle(JSObject *obj);
145145
static bool is_downstream(JSObject *obj);
146146
static JSString *backend(JSObject *obj);
147147
static const JSFunctionSpec static_methods[];

runtime/js-compute-runtime/fastly-world/fastly_world_adapter.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,10 @@ bool fastly_async_io_select(fastly_list_async_handle_t *hs, uint32_t timeout_ms,
685685
}
686686
return false;
687687
}
688-
ret->is_some = true;
688+
689+
// The result is only valid if the timeout didn't expire.
690+
ret->is_some = ret->val != UINT32_MAX;
691+
689692
return true;
690693
}
691694
bool fastly_async_io_is_ready(fastly_async_handle_t handle, bool *ret, fastly_error_t *err) {

runtime/js-compute-runtime/fastly.wit

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ default world fastly-world {
462462
///
463463
/// The timeout is specified in milliseconds, or 0 if no timeout is desired.
464464
///
465-
/// Returns the _index_ (not handle!) of the first object that is ready, or u32::MAX if the
466-
/// timeout expires before any objects are ready for I/O.
465+
/// Returns the _index_ (not handle!) of the first object that is ready, or
466+
/// none if the timeout expires before any objects are ready for I/O.
467467
async-io-select: func(hs: list<async-handle>, timeout-ms: u32) -> result<option<u32>, error>
468468

469469
/// Returns 1 if the given async item is "ready" for its associated I/O action, 0 otherwise.

runtime/js-compute-runtime/host_interface/host_api.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,41 @@ fastly_world_string_t string_view_to_world_string(std::string_view str) {
1515

1616
} // namespace
1717

18+
Result<bool> AsyncHandle::is_ready() const {
19+
Result<bool> res;
20+
21+
fastly_error_t err;
22+
bool ret;
23+
if (!fastly_async_io_is_ready(this->handle, &ret, &err)) {
24+
res.emplace_err(err);
25+
} else {
26+
res.emplace(ret);
27+
}
28+
29+
return res;
30+
}
31+
32+
Result<std::optional<uint32_t>> AsyncHandle::select(const std::vector<AsyncHandle> &handles,
33+
uint32_t timeout_ms) {
34+
Result<std::optional<uint32_t>> res;
35+
36+
static_assert(sizeof(AsyncHandle) == sizeof(fastly_async_handle_t));
37+
fastly_list_async_handle_t hs{
38+
.ptr = reinterpret_cast<fastly_async_handle_t *>(const_cast<AsyncHandle *>(handles.data())),
39+
.len = handles.size()};
40+
fastly_option_u32_t ret;
41+
fastly_error_t err;
42+
if (!fastly_async_io_select(&hs, timeout_ms, &ret, &err)) {
43+
res.emplace_err(err);
44+
} else if (ret.is_some) {
45+
res.emplace(ret.val);
46+
} else {
47+
res.emplace(std::nullopt);
48+
}
49+
50+
return res;
51+
}
52+
1853
Result<HttpBody> HttpBody::make() {
1954
Result<HttpBody> res;
2055

@@ -101,6 +136,8 @@ Result<Void> HttpBody::close() {
101136
return res;
102137
}
103138

139+
AsyncHandle HttpBody::async_handle() const { return AsyncHandle{this->handle}; }
140+
104141
namespace {
105142

106143
template <auto header_names_get>
@@ -187,6 +224,38 @@ Result<Void> generic_header_remove(auto handle, std::string_view name) {
187224

188225
} // namespace
189226

227+
Result<std::optional<Response>> HttpPendingReq::poll() {
228+
Result<std::optional<Response>> res;
229+
230+
fastly_error_t err;
231+
fastly_option_response_t ret;
232+
if (!fastly_http_req_pending_req_poll(this->handle, &ret, &err)) {
233+
res.emplace_err(err);
234+
} else if (ret.is_some) {
235+
res.emplace(ret.val);
236+
} else {
237+
res.emplace(std::nullopt);
238+
}
239+
240+
return res;
241+
}
242+
243+
Result<Response> HttpPendingReq::wait() {
244+
Result<Response> res;
245+
246+
fastly_error_t err;
247+
fastly_response_t ret;
248+
if (!fastly_http_req_pending_req_wait(this->handle, &ret, &err)) {
249+
res.emplace_err(err);
250+
} else {
251+
res.emplace(ret);
252+
}
253+
254+
return res;
255+
}
256+
257+
AsyncHandle HttpPendingReq::async_handle() const { return AsyncHandle{this->handle}; }
258+
190259
Result<HttpReq> HttpReq::make() {
191260
Result<HttpReq> res;
192261

@@ -201,6 +270,51 @@ Result<HttpReq> HttpReq::make() {
201270
return res;
202271
}
203272

273+
Result<Response> HttpReq::send(HttpBody body, std::string_view backend) {
274+
Result<Response> res;
275+
276+
fastly_error_t err;
277+
fastly_response_t ret;
278+
fastly_world_string_t backend_str = string_view_to_world_string(backend);
279+
if (!fastly_http_req_send(this->handle, body.handle, &backend_str, &ret, &err)) {
280+
res.emplace_err(err);
281+
} else {
282+
res.emplace(ret);
283+
}
284+
285+
return res;
286+
}
287+
288+
Result<HttpPendingReq> HttpReq::send_async(HttpBody body, std::string_view backend) {
289+
Result<HttpPendingReq> res;
290+
291+
fastly_error_t err;
292+
fastly_pending_request_handle_t ret;
293+
fastly_world_string_t backend_str = string_view_to_world_string(backend);
294+
if (!fastly_http_req_send_async(this->handle, body.handle, &backend_str, &ret, &err)) {
295+
res.emplace_err(err);
296+
} else {
297+
res.emplace(ret);
298+
}
299+
300+
return res;
301+
}
302+
303+
Result<HttpPendingReq> HttpReq::send_async_streaming(HttpBody body, std::string_view backend) {
304+
Result<HttpPendingReq> res;
305+
306+
fastly_error_t err;
307+
fastly_pending_request_handle_t ret;
308+
fastly_world_string_t backend_str = string_view_to_world_string(backend);
309+
if (!fastly_http_req_send_async_streaming(this->handle, body.handle, &backend_str, &ret, &err)) {
310+
res.emplace_err(err);
311+
} else {
312+
res.emplace(ret);
313+
}
314+
315+
return res;
316+
}
317+
204318
Result<Void> HttpReq::set_method(std::string_view method) {
205319
Result<Void> res;
206320

runtime/js-compute-runtime/host_interface/host_api.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,33 @@ struct HostString final {
9090
operator std::string_view() { return std::string_view(this->ptr.get(), this->len); }
9191
};
9292

93+
/// Common methods for async handles.
94+
class AsyncHandle {
95+
public:
96+
static constexpr fastly_async_handle_t invalid = UINT32_MAX - 1;
97+
98+
fastly_async_handle_t handle;
99+
100+
AsyncHandle() = default;
101+
explicit AsyncHandle(fastly_async_handle_t handle) : handle{handle} {}
102+
103+
/// Check to see if this handle is ready.
104+
Result<bool> is_ready() const;
105+
106+
/// Return the index in handles of the `AsyncHandle` that's ready. If the select call finishes
107+
/// successfully and returns `std::nullopt`, the timeout has expired.
108+
///
109+
/// If the timeout is `0`, two behaviors are possible
110+
/// * if handles is empty, an error will be returned immediately
111+
/// * otherwise, block until a handle is ready and return its index
112+
///
113+
/// If the timeout is non-zero, two behaviors are possible
114+
/// * no handle becomes ready within timeout, and the successful `std::nullopt` is returned
115+
/// * a handle becomes ready within the timeout, and its index is returned.
116+
static Result<std::optional<uint32_t>> select(const std::vector<AsyncHandle> &handles,
117+
uint32_t timeout_ms);
118+
};
119+
93120
/// A convenience wrapper for the host calls involving http bodies.
94121
class HttpBody final {
95122
public:
@@ -101,6 +128,7 @@ class HttpBody final {
101128

102129
HttpBody() = default;
103130
explicit HttpBody(fastly_body_handle_t handle) : handle{handle} {}
131+
explicit HttpBody(AsyncHandle async) : handle{async.handle} {}
104132

105133
/// Returns true when this body handle is valid.
106134
bool valid() const { return this->handle != invalid; }
@@ -125,6 +153,30 @@ class HttpBody final {
125153

126154
/// Close this handle, and reset internal state to invalid.
127155
Result<Void> close();
156+
157+
AsyncHandle async_handle() const;
158+
};
159+
160+
struct Response;
161+
162+
class HttpPendingReq final {
163+
public:
164+
static constexpr fastly_pending_request_handle_t invalid = UINT32_MAX - 1;
165+
166+
fastly_pending_request_handle_t handle = invalid;
167+
168+
HttpPendingReq() = default;
169+
explicit HttpPendingReq(fastly_pending_request_handle_t handle) : handle{handle} {}
170+
explicit HttpPendingReq(AsyncHandle async) : handle{async.handle} {}
171+
172+
/// Poll for the response to this request.
173+
Result<std::optional<Response>> poll();
174+
175+
/// Block until the response is ready.
176+
Result<Response> wait();
177+
178+
/// Fetch the AsyncHandle for this pending request.
179+
AsyncHandle async_handle() const;
128180
};
129181

130182
class HttpBase {
@@ -155,6 +207,15 @@ class HttpReq final : public HttpBase {
155207

156208
static Result<HttpReq> make();
157209

210+
/// Send this request synchronously, and wait for the response.
211+
Result<Response> send(HttpBody body, std::string_view backend);
212+
213+
/// Send this request asynchronously.
214+
Result<HttpPendingReq> send_async(HttpBody body, std::string_view backend);
215+
216+
/// Send this request asynchronously, and allow sending additional data through the body.
217+
Result<HttpPendingReq> send_async_streaming(HttpBody body, std::string_view backend);
218+
158219
/// Get the http version used for this request.
159220

160221
/// Set the request method.
@@ -196,4 +257,13 @@ class HttpResp final : public HttpBase {
196257
Result<Void> remove_header(std::string_view name) override;
197258
};
198259

260+
/// The pair of a response and its body.
261+
struct Response {
262+
HttpResp resp;
263+
HttpBody body;
264+
265+
Response() = default;
266+
explicit Response(fastly_response_t resp) : resp{HttpResp{resp.f0}}, body{HttpBody{resp.f1}} {}
267+
};
268+
199269
#endif

0 commit comments

Comments
 (0)