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 28, 2024
1 parent d7c6348 commit eb85b8f
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 13 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 @@ -990,10 +990,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
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 @@ -149,6 +149,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
31 changes: 22 additions & 9 deletions runtime/event_loop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,42 @@ 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);
Expand All @@ -95,7 +105,10 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) {
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 eb85b8f

Please sign in to comment.