Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch support - merge pending upstream fetch and async reworks and add temporary workarounds #3

Merged
merged 52 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
34cf1ef
Use SpiderMonkey's encoding_c instead of our own crate
tschneidereit May 29, 2024
0d02283
Change WPT support to use a CMake option instead of an env var
tschneidereit Apr 18, 2024
bfe3158
WIP: rework headers
tschneidereit Mar 13, 2024
86db17e
WIP: rework headers
tschneidereit Mar 22, 2024
d4e43db
Immediately lock reified body stream when the underlying body has alr…
tschneidereit Apr 6, 2024
dae6953
More headers rework!
tschneidereit Apr 10, 2024
aa5a32c
Use `string_view` instead of `HostString in core::decode`
tschneidereit Apr 29, 2024
d1984e9
Adjust namespace of wpt support functionality
tschneidereit Apr 29, 2024
ca335a7
And yet more headers rework—along with an overhaul of lots of the hos…
tschneidereit Apr 29, 2024
3bb6b5d
Support creating a component without evaluating a top-level script du…
tschneidereit May 28, 2024
c8cf151
Slight improvements to the WPT harness
tschneidereit May 28, 2024
c44b816
Substantially more reworking of the fetch related host API surface
tschneidereit May 28, 2024
2d68405
fix: release build on fetch rework (#54)
guybedford May 29, 2024
3027305
feat: single-tick non-tracking event loop runner
guybedford May 29, 2024
2e23c54
fixup
guybedford May 29, 2024
5bdb946
fixup
guybedford May 29, 2024
55566e9
fix event loop interest handling
guybedford May 29, 2024
4b3f9f5
simplify single tick
guybedford May 29, 2024
1d17ace
simpler loop
guybedford May 30, 2024
6a8ae9e
fixup return paths
guybedford May 30, 2024
2bad18a
Use a templated WASIHandle class to represent handles for WASI 0.2.0
tschneidereit Jun 1, 2024
ec28998
Fix bugs in runtime-evaluation
tschneidereit Jun 2, 2024
429daec
Fix bugs in headers handling
tschneidereit Jun 2, 2024
a0781f0
Make the `extract body` spec operation work for outgoing requests and…
tschneidereit Jun 2, 2024
7f4ba5a
Lock request/response body when consuming it for `.text()`, `.arrayBu…
tschneidereit Jun 2, 2024
e2f3b37
Fix handling of various value types when extracting request/response …
tschneidereit Jun 2, 2024
4820fdf
Remove some outdated comments and TODOs
tschneidereit Jun 3, 2024
f992afe
Ensure that the `ResponseFutureTask` is enqueued for streaming requests
tschneidereit Jun 3, 2024
f40bafb
Fix streaming of outgoing bodies and setting their content-length header
tschneidereit Jun 3, 2024
ce62077
Merge remote-tracking branch 'upstream/fetch-rework' into fetch
noise64 Jun 5, 2024
4e5a07d
Ensure that a streaming outgoing response body keeps the event loop a…
tschneidereit Jun 5, 2024
7221924
Fix construction of URLs for incoming requests/responses
tschneidereit Jun 5, 2024
5033ce2
Improve handling of already-closed outgoing bodies
tschneidereit Jun 5, 2024
27598cf
Merge remote-tracking branch 'refs/remotes/upstream/fetch-rework' int…
noise64 Jun 6, 2024
259fdb2
Remove some code duplication
tschneidereit Jun 6, 2024
066d573
Handle forbidden headers
tschneidereit Jun 7, 2024
1c41c36
checkpoint: smaller fetch gets working
noise64 Jun 10, 2024
38e7ed4
add fetch tests to test runner
noise64 Jun 10, 2024
bc3e5ee
always do at least 1 event loop in run_event_loop_until_interest (so …
noise64 Jun 10, 2024
41a4083
fetch js test notes / debug
noise64 Jun 10, 2024
a50422a
expose runEventLoop directly
noise64 Jun 11, 2024
c03dbed
fetch.js cleanups / guard for infinite event loop
noise64 Jun 11, 2024
c35f466
rename fetch suite to fetchsync
noise64 Jun 12, 2024
10047a3
also wait for pending jobs in run_event_loop_until_interest + cleanups
noise64 Jun 12, 2024
dc480ea
add debug log to inc / dec interest
noise64 Jun 12, 2024
78c5e2d
disable inc in maybe_stream_body
noise64 Jun 12, 2024
1731f10
Merge remote-tracking branch 'upstream/fetch-rework' into fetch
noise64 Jun 13, 2024
63ffbae
extract logging to common place
noise64 Jun 13, 2024
d44b0e5
updated test based on ComponentizeJS tests
noise64 Jun 13, 2024
b6b03ea
remove exposed runEventLoop
noise64 Jun 13, 2024
2af1dea
disabled "fetch sync" tests for now
noise64 Jun 13, 2024
ef86be3
disable "fetch sync" tests for now
noise64 Jun 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,22 @@ include("openssl")
include("host_api")
include("build-crates")

add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h)
add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h runtime/decode.h)
target_link_libraries(extension_api INTERFACE rust-url spidermonkey)
target_include_directories(extension_api INTERFACE include deps/include runtime)

include("builtins")

if (DEFINED ENV{WPT})
option(ENABLE_WPT "Enable WPT harness support" OFF)
if (ENABLE_WPT)
include("tests/wpt-harness/wpt.cmake")
endif()

add_executable(starling.wasm
runtime/js.cpp
runtime/allocator.cpp
runtime/encode.cpp
runtime/decode.cpp
runtime/engine.cpp
runtime/event_loop.cpp
runtime/builtin.cpp
Expand Down Expand Up @@ -94,13 +96,14 @@ function(componentize OUTPUT)
add_custom_command(
OUTPUT ${OUTPUT}.wasm
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${CMAKE_COMMAND} -E env "PATH=${WASM_TOOLS_DIR};${WIZER_DIR};$ENV{PATH}" ${RUNTIME_DIR}/componentize.sh ${SOURCES} ${OUTPUT}.wasm
COMMAND ${CMAKE_COMMAND} -E env "PATH=${WASM_TOOLS_DIR};${WIZER_DIR};$ENV{PATH}" ${RUNTIME_DIR}/componentize.sh ${SOURCES} -o ${OUTPUT}.wasm
DEPENDS ${ARG_SOURCES} ${RUNTIME_DIR}/componentize.sh starling.wasm
VERBATIM
)
add_custom_target(${OUTPUT} DEPENDS ${OUTPUT}.wasm)
endfunction()

componentize(smoke-test SOURCES tests/cases/smoke/smoke.js)
componentize(runtime-eval)

include("tests/tests.cmake")
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ StarlingMonkey includes a test runner for the [Web Platform Tests](https://web-p

### Requirements

The WPT runner requires `Node.js` to be installed, and during build configuration the environment variable `WPT` must be defined.
The WPT runner requires `Node.js` to be installed, and during build configuration the option `ENABLE_WPT:BOOL=ON` must be set.

When running the test, `WPT_ROOT` must be set to the path of a checkout of the WPT suite at revision `1014eae5e66f8f334610d5a1521756f7a2fb769f`:

```bash
WPT=1 WPT_ROOT=[path to your WPT checkout] cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug
WPT_ROOT=[path to your WPT checkout] cmake -S . -B cmake-build-debug -DENABLE_WPT:BOOL=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debug --parallel 8 --target wpt-runtime
cd cmake-build-debug
ctest --verbose # Note: some of the tests run fairly slowly in debug builds, so be patient
Expand Down
135 changes: 64 additions & 71 deletions builtins/web/fetch/fetch-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,13 @@
#include "headers.h"
#include "request-response.h"

namespace builtins::web::fetch {

static api::Engine *ENGINE;

class ResponseFutureTask final : public api::AsyncTask {
Heap<JSObject *> request_;
host_api::FutureHttpIncomingResponse *future_;

public:
explicit ResponseFutureTask(const HandleObject request,
host_api::FutureHttpIncomingResponse *future)
: request_(request), future_(future) {
auto res = future->subscribe();
MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail");
handle_ = res.unwrap();
}

[[nodiscard]] bool run(api::Engine *engine) override {
// MOZ_ASSERT(ready());
JSContext *cx = engine->cx();
#include <encode.h>

const RootedObject request(cx, request_);
RootedObject response_promise(cx, Request::response_promise(request));

auto res = future_->maybe_response();
if (res.is_err()) {
JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource.");
return RejectPromiseWithPendingError(cx, response_promise);
}

auto maybe_response = res.unwrap();
MOZ_ASSERT(maybe_response.has_value());
auto response = maybe_response.value();
RootedObject response_obj(
cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj));
if (!response_obj) {
return false;
}

response_obj = Response::create(cx, response_obj, response);
if (!response_obj) {
return false;
}
#include <memory>

RequestOrResponse::set_url(response_obj, RequestOrResponse::url(request));
RootedValue response_val(cx, ObjectValue(*response_obj));
if (!ResolvePromise(cx, response_promise, response_val)) {
return false;
}

return cancel(engine);
}

[[nodiscard]] bool cancel(api::Engine *engine) override {
// TODO(TS): implement
handle_ = -1;
return true;
}
namespace builtins::web::fetch {

void trace(JSTracer *trc) override { TraceEdge(trc, &request_, "Request for response future"); }
};
static api::Engine *ENGINE;

// TODO: throw in all Request methods/getters that rely on host calls once a
// request has been sent. The host won't let us act on them anymore anyway.
Expand All @@ -80,30 +26,60 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

RootedObject requestInstance(
RootedObject request_obj(
cx, JS_NewObjectWithGivenProto(cx, &Request::class_, Request::proto_obj));
if (!requestInstance)
return false;
if (!request_obj) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

RootedObject request(cx, Request::create(cx, requestInstance, args[0], args.get(1)));
if (!request) {
if (!Request::create(cx, request_obj, args[0], args.get(1))) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

RootedString method_str(cx, Request::method(cx, request_obj));
if (!method_str) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

host_api::HostString method = core::encode(cx, method_str);
if (!method.ptr) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

RootedValue url_val(cx, RequestOrResponse::url(request_obj));
host_api::HostString url = core::encode(cx, url_val);
if (!url.ptr) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

unique_ptr<host_api::HttpHeaders> headers = RequestOrResponse::headers_handle_clone(cx,
request_obj, host_api::HttpHeadersGuard::Request);
if (!headers) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

auto request = host_api::HttpOutgoingRequest::make(method, std::move(url),
std::move(headers));
MOZ_RELEASE_ASSERT(request);
JS_SetReservedSlot(request_obj, static_cast<uint32_t>(Request::Slots::Request),
PrivateValue(request));

RootedObject response_promise(cx, JS::NewPromiseObject(cx, nullptr));
if (!response_promise)
return ReturnPromiseRejectedWithPendingError(cx, args);

bool streaming = false;
if (!RequestOrResponse::maybe_stream_body(cx, request, &streaming)) {
if (!RequestOrResponse::maybe_stream_body(cx, request_obj, &streaming)) {
return false;
}
if (streaming) {
// Ensure that the body handle is stored before making the request handle invalid by sending it.
request->body();
}

host_api::FutureHttpIncomingResponse *pending_handle;
{
auto request_handle = Request::outgoing_handle(request);
auto res = request_handle->send();

auto res = request->send();
if (auto *err = res.to_err()) {
HANDLE_ERROR(cx, *err);
return ReturnPromiseRejectedWithPendingError(cx, args);
Expand All @@ -115,17 +91,34 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) {
// If the request body is streamed, we need to wait for streaming to complete
// before marking the request as pending.
if (!streaming) {
ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle));
ENGINE->incr_event_loop_interest("fetch: not streaming");
ENGINE->queue_async_task(new ResponseFutureTask(request_obj, pending_handle));
}

JS::SetReservedSlot(request, static_cast<uint32_t>(Request::Slots::ResponsePromise),
JS::ObjectValue(*response_promise));
SetReservedSlot(request_obj, static_cast<uint32_t>(Request::Slots::ResponsePromise),
ObjectValue(*response_promise));
SetReservedSlot(request_obj, static_cast<uint32_t>(Request::Slots::PendingResponseHandle),
PrivateValue(pending_handle));

args.rval().setObject(*response_promise);
return true;
}

const JSFunctionSpec methods[] = {JS_FN("fetch", fetch, 2, JSPROP_ENUMERATE), JS_FS_END};
bool runEventLoopUntilInterest(JSContext *cx, unsigned argc, Value *vp) {
ENGINE->run_event_loop_until_interest();
return true;
}

bool runEventLoop(JSContext *cx, unsigned argc, Value *vp) {
ENGINE->run_event_loop();
return true;
}

const JSFunctionSpec methods[] = {
JS_FN("fetch", fetch, 2, JSPROP_ENUMERATE),
JS_FN("runEventLoopUntilInterest", runEventLoopUntilInterest, 0, JSPROP_ENUMERATE),
JS_FS_END,
};

bool install(api::Engine *engine) {
ENGINE = engine;
Expand Down
Loading
Loading