Skip to content

Commit

Permalink
Merge pull request #3 from golemcloud/fetch
Browse files Browse the repository at this point in the history
Fetch support - merge pending upstream fetch and async reworks and add temporary workarounds
  • Loading branch information
noise64 authored Jun 14, 2024
2 parents aa9ff14 + ef86be3 commit 9fb33d5
Show file tree
Hide file tree
Showing 34 changed files with 2,244 additions and 1,231 deletions.
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

0 comments on commit 9fb33d5

Please sign in to comment.