Skip to content

Commit

Permalink
feat: single-tick non-tracking event loop runner
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Jun 21, 2024
1 parent 4cd78b6 commit 75d3c87
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 14 deletions.
4 changes: 0 additions & 4 deletions builtins/web/fetch/request-response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -996,10 +996,6 @@ bool reader_for_outgoing_body_catch_handler(JSContext *cx, JS::HandleObject body
// `responseDone`. (Note that even though we encountered an error,
// `responseDone` is the right state: `respondedWithError` is for when sending
// a response at all failed.)
// TODO(TS): investigate why this is disabled.
// if (Response::is_instance(body_owner)) {
// FetchEvent::set_state(FetchEvent::instance(), FetchEvent::State::responseDone);
// }
return finish_outgoing_body_streaming(cx, body_owner);
}

Expand Down
17 changes: 17 additions & 0 deletions host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ size_t api::AsyncTask::select(std::vector<api::AsyncTask *> &tasks) {
return ready_index;
}

std::optional<size_t> api::AsyncTask::ready(std::vector<api::AsyncTask *> &tasks) {
auto count = tasks.size();
vector<Borrow<Pollable>> handles;
for (const auto task : tasks) {
handles.emplace_back(task->id());
}
auto list = list_borrow_pollable_t{
reinterpret_cast<HandleOps<Pollable>::borrow *>(handles.data()), count};
bindings_list_u32_t result{nullptr, 0};
wasi_io_0_2_0_rc_2023_10_18_poll_poll_list(&list, &result);
MOZ_ASSERT(result.len > 0);
const auto ready_index = result.ptr[0];
free(result.ptr);

return ready_index;
}

namespace host_api {

HostString::HostString(const char *c_str) {
Expand Down
17 changes: 17 additions & 0 deletions host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ size_t api::AsyncTask::select(std::vector<api::AsyncTask *> &tasks) {
return ready_index;
}

std::optional<size_t> api::AsyncTask::ready(std::vector<api::AsyncTask *> &tasks) {
auto count = tasks.size();
vector<Borrow<Pollable>> handles;
for (const auto task : tasks) {
handles.emplace_back(task->id());
}
auto list = list_borrow_pollable_t{
reinterpret_cast<HandleOps<Pollable>::borrow *>(handles.data()), count};
bindings_list_u32_t result{nullptr, 0};
wasi_io_0_2_0_rc_2023_11_10_poll_poll(&list, &result);
MOZ_ASSERT(result.len > 0);
const auto ready_index = result.ptr[0];
free(result.ptr);

return ready_index;
}

namespace host_api {

HostString::HostString(const char *c_str) {
Expand Down
12 changes: 12 additions & 0 deletions host-apis/wasi-0.2.0/host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ size_t api::AsyncTask::select(std::vector<AsyncTask *> &tasks) {
return ready_index;
}

std::optional<size_t> api::AsyncTask::ready(std::vector<api::AsyncTask *> &tasks) {
auto count = tasks.size();
for (size_t idx = 0; idx < count; ++idx) {
auto task = tasks.at(idx);
WASIHandle<host_api::Pollable>::Borrowed poll = { task->id() };
if (wasi_io_0_2_0_poll_method_pollable_ready(poll)) {
return idx;
}
}
return std::nullopt;
}

namespace host_api {

HostString::HostString(const char *c_str) {
Expand Down
5 changes: 5 additions & 0 deletions include/extension-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ class AsyncTask {
* Select for the next available ready task, providing the oldest ready first.
*/
static size_t select(std::vector<AsyncTask *> &handles);

/**
* Non-blocking check for a ready task, providing the oldest ready first, if any.
*/
static std::optional<size_t> ready(std::vector<AsyncTask *> &handles);
};

} // namespace api
Expand Down
33 changes: 23 additions & 10 deletions runtime/event_loop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,41 +62,54 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) {
queue.get().event_loop_running = true;
JSContext *cx = engine->cx();

while (true) {
do {
// Run a microtask checkpoint
js::RunJobs(cx);

if (JS_IsExceptionPending(cx)) {
exit_event_loop();
return false;
}
// if there is no interest in the event loop at all, just run one tick
if (interest_complete()) {
exit_event_loop();
return true;
}

const auto tasks = &queue.get().tasks;
size_t tasks_size = tasks->size();

if (tasks_size == 0) {
if (interest_complete()) {
break;
}
exit_event_loop();
MOZ_ASSERT(!interest_complete());
fprintf(stderr, "event loop error - both task and job queues are empty, but expected "
"operations did not resolve");
return false;
}

size_t task_idx;

// Select the next task to run according to event-loop semantics of oldest-first.
size_t task_idx = api::AsyncTask::select(*tasks);
if (interest_complete()) {
// Perform a non-blocking select in the case of there being no event loop interest
// (we are thus only performing a "single tick", but must still progress work that is ready)
std::optional<size_t> maybe_task_idx = api::AsyncTask::ready(*tasks);
if (!maybe_task_idx.has_value()) {
break;
}
task_idx = maybe_task_idx.value();
} else {
task_idx = api::AsyncTask::select(*tasks);
}

auto task = tasks->at(task_idx);
tasks->erase(tasks->begin() + task_idx);
bool success = task->run(engine);
tasks->erase(tasks->begin() + task_idx);
if (!success) {
exit_event_loop();
return false;
}
}
} while (!interest_complete());

exit_event_loop();
return true;
}

void EventLoop::init(JSContext *cx) { queue.init(cx); }
Expand Down

0 comments on commit 75d3c87

Please sign in to comment.