From 8787869d10fede1d4cecbf17a708b49141f97be2 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 15:38:45 -0700 Subject: [PATCH] feat: single-tick non-tracking event loop runner --- builtins/web/fetch/request-response.cpp | 4 --- .../wasi-0.2.0-rc-2023-10-18/host_api.cpp | 17 ++++++++++ .../wasi-0.2.0-rc-2023-12-05/host_api.cpp | 17 ++++++++++ host-apis/wasi-0.2.0/host_api.cpp | 12 +++++++ include/extension-api.h | 5 +++ runtime/event_loop.cpp | 31 +++++++++++++------ 6 files changed, 73 insertions(+), 13 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 8bc34ac..3c84a3d 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -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); } diff --git a/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp b/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp index 4c35cb1..9b5e2f0 100644 --- a/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp +++ b/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp @@ -112,6 +112,23 @@ size_t api::AsyncTask::select(std::vector &tasks) { return ready_index; } +std::optional api::AsyncTask::ready(std::vector &tasks) { + auto count = tasks.size(); + vector> handles; + for (const auto task : tasks) { + handles.emplace_back(task->id()); + } + auto list = list_borrow_pollable_t{ + reinterpret_cast::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) { diff --git a/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp b/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp index 126ce52..e2a799b 100644 --- a/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp +++ b/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp @@ -116,6 +116,23 @@ size_t api::AsyncTask::select(std::vector &tasks) { return ready_index; } +std::optional api::AsyncTask::ready(std::vector &tasks) { + auto count = tasks.size(); + vector> handles; + for (const auto task : tasks) { + handles.emplace_back(task->id()); + } + auto list = list_borrow_pollable_t{ + reinterpret_cast::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) { diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 4e6410b..b9ef664 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -263,6 +263,18 @@ size_t api::AsyncTask::select(std::vector &tasks) { return ready_index; } +std::optional api::AsyncTask::ready(std::vector &tasks) { + auto count = tasks.size(); + for (size_t idx = 0; idx < count; ++idx) { + auto task = tasks.at(idx); + WASIHandle::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) { diff --git a/include/extension-api.h b/include/extension-api.h index 4f5360f..6a339dc 100644 --- a/include/extension-api.h +++ b/include/extension-api.h @@ -148,6 +148,11 @@ class AsyncTask { * Select for the next available ready task, providing the oldest ready first. */ static size_t select(std::vector &handles); + + /** + * Non-blocking check for a ready task, providing the oldest ready first, if any. + */ + static std::optional ready(std::vector &handles); }; } // namespace api diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index 6e95cbb..370e833 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -62,7 +62,7 @@ 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); @@ -70,24 +70,34 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { 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 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); @@ -96,7 +106,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); }