From f74f1f78a30f3f44e34a690b2639f48713289aa7 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sat, 17 May 2025 10:14:10 +0200 Subject: [PATCH 01/41] Add expect overload for std::optional --- src/io/result.hxx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/io/result.hxx b/src/io/result.hxx index 2964cef..77bed9f 100644 --- a/src/io/result.hxx +++ b/src/io/result.hxx @@ -20,7 +20,7 @@ #include #include -#include +#include #include namespace fastipc { @@ -68,7 +68,7 @@ template return std::move(expected.value()); } - std::cerr << message << ": " << expected.error().message() << "\n" << std::flush; + std::println(stderr, "{}: {}", message, expected.error().message()); std::abort(); } @@ -77,7 +77,17 @@ inline void expect(std::expected expected, std::string_vi return; } - std::cerr << message << ": " << expected.error().message() << "\n" << std::flush; + std::println(stderr, "{}: {}", message, expected.error().message()); + std::abort(); +} + +template +[[nodiscard]] T expect(std::optional expected, std::string_view message = "unexpected") noexcept { + if (expected.has_value()) { + return std::move(expected.value()); + } + + std::println(stderr, "{}", message); std::abort(); } From 01e99bef48091cde6026c86c33197ed0dca81db1 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sat, 17 May 2025 10:17:40 +0200 Subject: [PATCH 02/41] Make readClientRequest fallible + simple read/write wrappers --- src/fastipc.cxx | 3 +-- src/io/fd.hxx | 16 ++++++++++++++-- src/tower.cxx | 37 ++++++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/fastipc.cxx b/src/fastipc.cxx index 07ebf2f..59d145f 100644 --- a/src/fastipc.cxx +++ b/src/fastipc.cxx @@ -37,7 +37,6 @@ #include #include #include -#include #include "io/cursor.hxx" #include "io/fd.hxx" @@ -81,7 +80,7 @@ void writeClientRequest(std::span& buf, const ClientRequest& request) writeClientRequest(sndbuf, request); const auto bytes_written = - expect(io::sysVal(::write(sockfd.fd(), buf.data(), buf.size() - sndbuf.size())), "failed to write to tower"); + expect(io::write(sockfd, std::span{buf}.first(buf.size() - sndbuf.size())), "failed to write to tower"); static_cast(bytes_written); // seq packet std::size_t total_size{0U}; diff --git a/src/io/fd.hxx b/src/io/fd.hxx index 4cbea53..e4188fd 100644 --- a/src/io/fd.hxx +++ b/src/io/fd.hxx @@ -18,7 +18,7 @@ #pragma once -#include +#include #include #include @@ -57,8 +57,20 @@ class Fd final { int m_fd{-1}; }; -[[nodiscard]] constexpr io::expected adoptSysFd(int fd) noexcept { +[[nodiscard]] constexpr expected adoptSysFd(int fd) noexcept { return sysVal(fd).transform([](int fd) { return Fd{fd}; }); } +[[nodiscard]] inline expected write(const Fd& fd, std::span buf) noexcept { + return sysVal(::write(fd.fd(), buf.data(), buf.size())).transform([](int written) { + return static_cast(written); + }); +} + +[[nodiscard]] inline expected read(const Fd& fd, std::span buf) noexcept { + return sysVal(::read(fd.fd(), buf.data(), buf.size())).transform([](int read) { + return static_cast(read); + }); +} + } // namespace fastipc::io diff --git a/src/tower.cxx b/src/tower.cxx index b12c262..1023bdc 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -21,12 +21,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -49,19 +49,39 @@ namespace fastipc { namespace { -[[nodiscard]] ClientRequest readClientRequest(std::span& buf) noexcept { +[[nodiscard]] io::expected> readClientRequest(std::span& obuf) noexcept { + constexpr static auto kMinSize = 10u; + + auto buf = obuf; + + if (kMinSize > buf.size()) { + return {}; + } + const auto requester_type = io::getBuf>(buf); + + if (requester_type >= 2) { + return io::unexpected{std::make_error_code(std::errc::protocol_error)}; + } + const auto max_payload_size = io::getBuf(buf); - const auto topic_name_buf = io::takeBuf(buf, io::getBuf(buf)); + const auto topic_name_size = io::getBuf(buf); + + if (topic_name_size > buf.size()) { + return {}; + } - assert(requester_type < 2); + const auto topic_name_buf = io::takeBuf(buf, topic_name_size); - return { + obuf = buf; + + return ClientRequest{ .type = static_cast(requester_type), .max_payload_size = max_payload_size, .topic_name = {reinterpret_cast(topic_name_buf.data()), topic_name_buf.size()}, }; } + } // namespace [[nodiscard]] Tower Tower::create(std::string_view path) { @@ -107,11 +127,10 @@ void Tower::shutdown() { expect(io::sysCheck(::shutdown(m_sockfd.fd(), SHUT_RD)) void Tower::serve(io::Fd clientfd) { std::array buf{}; // NOLINT(*-magic-numbers) - const auto bytes_read = - expect(io::sysVal(::read(clientfd.fd(), buf.data(), buf.size())), "failed to read from client"); + const auto bytes_read = expect(io::read(clientfd, std::span{buf}), "failed to read from client"); - auto recvbuf = std::span{buf.data(), static_cast(bytes_read)}; - const auto request = readClientRequest(recvbuf); + auto recvbuf = std::span{buf}.first(bytes_read); + const auto request = expect(expect(readClientRequest(recvbuf), "invalid request"), "incomplete message"); std::println("{} request for topic '{}' with max payload size of {} bytes.", (request.type == RequesterType::Reader ? "reader" : "writer"), request.topic_name, From aa35e4aaba10a7e576fa2b860a9d03eef626d4a8 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Wed, 18 Jun 2025 22:43:29 +0200 Subject: [PATCH 03/41] wip: coroutines --- CMakeLists.txt | 9 ++ src/co/coroutine.hxx | 297 ++++++++++++++++++++++++++++++++++++++++++ src/co/received.hxx | 96 ++++++++++++++ src/co/scheduler.hxx | 47 +++++++ src/co/task.hxx | 160 +++++++++++++++++++++++ src/io/context.hxx | 29 +++++ src/io/fd.hxx | 31 ++++- src/io/io_env.hxx | 38 ++++++ src/io/polled_io.hxx | 139 ++++++++++++++++++++ src/io/reactor.cxx | 135 +++++++++++++++++++ src/io/reactor.hxx | 80 ++++++++++++ src/main.cxx | 33 ++++- src/tower.cxx | 14 +- src/tower.hxx | 8 +- src/visitor.hxx | 18 +++ test/intraprocess.cxx | 64 +++++---- 16 files changed, 1159 insertions(+), 39 deletions(-) create mode 100644 src/co/coroutine.hxx create mode 100644 src/co/received.hxx create mode 100644 src/co/scheduler.hxx create mode 100644 src/co/task.hxx create mode 100644 src/io/context.hxx create mode 100644 src/io/io_env.hxx create mode 100644 src/io/polled_io.hxx create mode 100644 src/io/reactor.cxx create mode 100644 src/io/reactor.hxx create mode 100644 src/visitor.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index ff90f7d..f549890 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,16 @@ target_sources ( src/io/addr.hxx src/io/addr.cxx src/io/endian.hxx + src/io/polled_io.hxx + src/io/reactor.hxx + src/io/reactor.cxx + src/io/io_env.hxx + src/io/context.hxx + src/co/scheduler.hxx + src/co/coroutine.hxx + src/co/task.hxx src/fastipc.cxx + src/visitor.hxx src/channel.hxx ) if (NOT DEFINED CMAKE_CXX_CLANG_TIDY OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx new file mode 100644 index 0000000..a0b23ec --- /dev/null +++ b/src/co/coroutine.hxx @@ -0,0 +1,297 @@ +/* + * coroutine.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "received.hxx" +#include "visitor.hxx" + +namespace fastipc::co { + +template +concept valueless = std::is_void_v; + +template +class Promise; + +template +class CoAwaiter; + +template +class Task; + +template +class EnvAwaiter final { + public: + bool await_ready() noexcept { return false; } + + template + std::coroutine_handle<> await_suspend(std::coroutine_handle> cont) { + m_env = &cont.promise().env(); + + return cont; + } + + const Env& await_resume() const noexcept { return *m_env; } + + private: + const Env* m_env = nullptr; +}; + +struct GetEnv final {}; + +constexpr GetEnv getEnv() noexcept { return {}; } + +template +class [[nodiscard]] Co final { + public: + using promise_type = Promise; + using handle_type = std::coroutine_handle; + + explicit Co(handle_type handle) : m_handle{std::move(handle)} {} + + Co(Co&) noexcept = delete; + Co& operator=(Co&) noexcept = delete; + + Co(Co&& it) noexcept : m_handle{std::exchange(it.m_handle, {})} {} + Co& operator=(Co&& rhs) noexcept { + auto other = Co{std::move(rhs)}; + + std::swap(m_handle, other.m_handle); + + return *this; + } + + ~Co() noexcept { + if (m_handle) { + m_handle.destroy(); + } + } + + void env(const Env* env) { m_handle.promise().m_env = env; } + + CoAwaiter operator co_await() && noexcept; + + private: + friend class CoAwaiter; + friend class Promise; + friend class Task; + + handle_type m_handle; +}; + +template +class Receiver { + public: + Receiver() = default; + + Receiver(Receiver&&) noexcept = default; + Receiver& operator=(Receiver&&) noexcept = default; + + Receiver(const Receiver&) noexcept = default; + Receiver& operator=(const Receiver&) noexcept = default; + + virtual ~Receiver() = default; + + virtual void set_value(T value) noexcept = 0; + virtual void set_exception(std::exception_ptr error) noexcept = 0; +}; + +template <> +class Receiver { + public: + Receiver() = default; + + Receiver(Receiver&&) noexcept = default; + Receiver& operator=(Receiver&&) noexcept = default; + + Receiver(const Receiver&) noexcept = default; + Receiver& operator=(const Receiver&) noexcept = default; + + virtual ~Receiver() = default; + + virtual void set_value() noexcept = 0; + virtual void set_exception(std::exception_ptr error) noexcept = 0; +}; + +template +class FinalAwaiter final { + public: + bool await_ready() noexcept { return false; } + + std::coroutine_handle<> await_suspend(std::coroutine_handle> completed) { + assert(completed.promise().m_received.has_value()); + auto& promise = completed.promise(); + + return match( + std::move(promise.m_cont), [](std::coroutine_handle<> cont) -> std::coroutine_handle<> { return cont; }, + [&promise](std::shared_ptr> receiver) -> std::coroutine_handle<> { + std::move(promise.m_received).forward(*receiver); + + return std::noop_coroutine(); + }); + } + + void await_resume() noexcept {} +}; + +template +class Promise final { + public: + Promise() = default; + + Promise(Promise&&) noexcept = delete; + Promise& operator=(Promise&&) noexcept = delete; + + Promise(const Promise&) noexcept = delete; + Promise& operator=(const Promise&) noexcept = delete; + + ~Promise() noexcept = default; + + Co get_return_object() { return Co{std::coroutine_handle::from_promise(*this)}; } + + std::suspend_always initial_suspend() noexcept { return {}; } + + FinalAwaiter final_suspend() noexcept { + assert(m_received.has_value()); + return {}; + } + + void return_value(T value) noexcept { m_received.set_value(std::move(value)); } + void unhandled_exception() noexcept { m_received.set_exception(std::current_exception()); } + + template + decltype(auto) await_transform(A&& awaitable) noexcept { + return std::forward(awaitable); + } + + EnvAwaiter await_transform(GetEnv) { return EnvAwaiter{}; } + + const Env& env() const { return *m_env; } + + private: + friend class Co; + friend class CoAwaiter; + friend class FinalAwaiter; + friend class Task; + + Received m_received; + + std::variant, std::shared_ptr>> m_cont = std::noop_coroutine(); + + const Env* m_env; +}; + +template +class Promise final { + public: + Promise() = default; + + Promise(Promise&&) noexcept = delete; + Promise& operator=(Promise&&) noexcept = delete; + + Promise(const Promise&) noexcept = delete; + Promise& operator=(const Promise&) noexcept = delete; + + ~Promise() noexcept = default; + + Co get_return_object() { return Co{std::coroutine_handle::from_promise(*this)}; } + + std::suspend_always initial_suspend() noexcept { return {}; } + + FinalAwaiter final_suspend() noexcept { + assert(m_received.has_value()); + return {}; + } + + void return_void() noexcept { m_received.set_value(); } + + void unhandled_exception() noexcept { m_received.set_exception(std::current_exception()); } + + template + decltype(auto) await_transform(A&& awaitable) noexcept { + return std::forward(awaitable); + } + + EnvAwaiter await_transform(GetEnv) { return EnvAwaiter{}; } + + const Env& env() const { return *m_env; } + + private: + friend class Co; + friend class CoAwaiter; + friend class FinalAwaiter; + friend class Task; + + Received m_received; + std::variant, std::shared_ptr>> m_cont = std::noop_coroutine(); + + const Env* m_env; +}; + +template +class [[nodiscard]] CoAwaiter final { + public: + explicit CoAwaiter(Co co) noexcept : m_co{std::move(co)} {} + + CoAwaiter(CoAwaiter&&) noexcept = delete; + CoAwaiter& operator=(CoAwaiter&&) noexcept = delete; + + CoAwaiter(const CoAwaiter&) noexcept = delete; + CoAwaiter& operator=(const CoAwaiter&) noexcept = delete; + + ~CoAwaiter() noexcept = default; + + bool await_ready() noexcept { return false; } + + template + std::coroutine_handle<> await_suspend(std::coroutine_handle> cont) { + m_co.m_handle.promise().m_env = &cont.promise().env(); + m_co.m_handle.promise().m_cont = cont; + + return m_co.m_handle; + } + + T await_resume() { + auto co = std::move(m_co); + + auto& promise = co.m_handle.promise(); + + assert(promise.m_received.has_value()); + return std::move(promise.m_received).consume(); + } + + private: + Co m_co; +}; + +template +CoAwaiter Co::operator co_await() && noexcept { + return CoAwaiter{std::move(*this)}; +} + +} // namespace fastipc::co diff --git a/src/co/received.hxx b/src/co/received.hxx new file mode 100644 index 0000000..86a1078 --- /dev/null +++ b/src/co/received.hxx @@ -0,0 +1,96 @@ +/* + * scheduler.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "visitor.hxx" + +namespace fastipc::co { + +template +struct Received final { + + void set_value(T value) { m_value = std::move(value); } + void set_exception(std::exception_ptr exc) { m_value = std::move(exc); } + + [[nodiscard]] bool has_value() const { return !std::holds_alternative(m_value); } + + [[nodiscard]] T consume() && { + return match( + std::move(m_value), [](std::exception_ptr exc) -> T { std::rethrow_exception(std::move(exc)); }, + [](T&& value) { return std::move(value); }, + [](std::monostate) -> T { + assert(false); + std::unreachable(); + }); + } + + template + void forward(R& receiver) && { + match( + std::move(m_value), [&receiver](std::exception_ptr exc) { receiver.set_exception(std::move(exc)); }, + [&receiver](T&& value) { receiver.set_value(std::move(value)); }, + [](std::monostate) { + assert(false); + std::unreachable(); + }); + } + + std::variant m_value; +}; + +template <> +struct Received final { + + void set_value() { m_value = has_value_tag{}; } + void set_exception(std::exception_ptr exc) { m_value = std::move(exc); } + + [[nodiscard]] bool has_value() const { return !std::holds_alternative(m_value); } + + void consume() && { + match( + std::move(m_value), [](std::exception_ptr exc) -> void { std::rethrow_exception(std::move(exc)); }, + [](has_value_tag) {}, + [](std::monostate) { + assert(false); + std::unreachable(); + }); + } + + template + void forward(R& receiver) && { + return match( + std::move(m_value), [&](std::exception_ptr exc) { receiver.set_exception(std::move(exc)); }, + [&](has_value_tag) { receiver.set_value(); }, + [](std::monostate) { + assert(false); + std::unreachable(); + }); + } + + struct has_value_tag final {}; + + std::variant m_value; +}; + +} // namespace fastipc::co \ No newline at end of file diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx new file mode 100644 index 0000000..ebfde3f --- /dev/null +++ b/src/co/scheduler.hxx @@ -0,0 +1,47 @@ +/* + * scheduler.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include + +namespace fastipc::co { + +class Scheduler final { + public: + // TODO: use std::function_ref + void schedule(std::function fn) { m_queue.push(std::move(fn)); } + + [[nodiscard]] bool can_run() const noexcept { return !m_queue.empty(); } + + void run() noexcept { + const auto queued = m_queue.size(); + + for (std::size_t i = 0; i < queued; ++i) { + std::move(m_queue.front())(); + m_queue.pop(); + } + } + + private: + std::queue> m_queue; +}; + +} // namespace fastipc::io \ No newline at end of file diff --git a/src/co/task.hxx b/src/co/task.hxx new file mode 100644 index 0000000..8b83104 --- /dev/null +++ b/src/co/task.hxx @@ -0,0 +1,160 @@ +/* + * task.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "coroutine.hxx" + +namespace fastipc::co { + +struct Unit final {}; + +template +class Task final : public Receiver, public std::enable_shared_from_this> { + public: + Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{&env} { std::println("Task created"); } + + Task(const Task&) = delete; + Task& operator=(const Task&) = delete; + + Task(Task&&) = default; + Task& operator=(Task&&) = default; + + [[nodiscard]] bool completed() const noexcept { return m_done; } + + ~Task() noexcept override = default; + + void start() { + auto& promise = m_co_run.m_handle.promise(); + + promise.m_env = m_env; + promise.m_cont = std::enable_shared_from_this>::shared_from_this(); + + m_env->scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); + } + + void set_value(T value) noexcept override { + m_value = std::move(value); + m_done = true; + + if (m_cont) { + m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); + } + } + + void set_exception(std::exception_ptr) noexcept override { std::terminate(); } + + private: + template + friend class JoinHandle; + + Co m_co_run; + const Env* m_env; + + bool m_done = false; + std::optional m_value{}; + + std::coroutine_handle<> m_cont; +}; + +template +class Task final : public Receiver, public std::enable_shared_from_this> { + public: + Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{&env} {} + + Task(const Task&) = delete; + Task& operator=(const Task&) = delete; + + Task(Task&&) = default; + Task& operator=(Task&&) = default; + + [[nodiscard]] bool completed() const noexcept { return m_done; } + + ~Task() noexcept override = default; + + void start() { + auto& promise = m_co_run.m_handle.promise(); + + promise.m_env = m_env; + promise.m_cont = std::enable_shared_from_this>::shared_from_this(); + + m_env->scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); + } + + void set_value() noexcept override { + m_done = true; + + if (m_cont) { + m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); + } + } + + void set_exception(std::exception_ptr exc) noexcept override { + m_done = true; + std::rethrow_exception(std::move(exc)); + } + + private: + template + friend class JoinHandle; + + Co m_co_run; + const Env* m_env; + + bool m_done = false; + + std::coroutine_handle<> m_cont; +}; + +template +class JoinHandle final { + + public: + explicit JoinHandle(std::shared_ptr> task) noexcept : m_task{std::move(task)} {} + + [[nodiscard]] bool completed() const noexcept { return m_task->completed(); } + + bool await_ready() noexcept { return completed(); } + + void await_suspend(std::coroutine_handle<> cont) { m_task->m_cont = cont; } + + T await_resume() { return std::move(m_task->m_value).value(); } + + private: + std::shared_ptr> m_task; +}; + +template +JoinHandle spawn(Co co, const Env& env) { + auto task = std::make_shared>(std::move(co), env); + task->start(); + + return JoinHandle{std::move(task)}; +} + +template +Co, Env> spawn(Co co) { + co_return spawn(std::move(co), co_await getEnv()); +} + +} // namespace fastipc::co \ No newline at end of file diff --git a/src/io/context.hxx b/src/io/context.hxx new file mode 100644 index 0000000..e2657a6 --- /dev/null +++ b/src/io/context.hxx @@ -0,0 +1,29 @@ +#pragma once +#include "co/scheduler.hxx" +#include "io_env.hxx" +#include "reactor.hxx" + +namespace fastipc::io { + +template +void context(F func) { + auto scheduler = co::Scheduler{}; + auto reactor = expect(Reactor::create()); + + Env env{.scheduler = &scheduler, .reactor = &reactor}; + + auto task = co::spawn(func(), env); + + while (scheduler.can_run()) { + while (scheduler.can_run()) { + scheduler.run(); + expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + } + + expect(reactor.react({}), "failed to react to io events"); + } + + static_cast(task); +} + +} // namespace fastipc::io \ No newline at end of file diff --git a/src/io/fd.hxx b/src/io/fd.hxx index e4188fd..4a09357 100644 --- a/src/io/fd.hxx +++ b/src/io/fd.hxx @@ -21,13 +21,19 @@ #include #include #include - +#include +#include #include #include "result.hxx" namespace fastipc::io { +template +concept AsFd = requires(const T& t) { + { t.fd() } -> std::same_as; +}; + class Fd final { public: constexpr explicit Fd() noexcept = default; @@ -61,16 +67,35 @@ class Fd final { return sysVal(fd).transform([](int fd) { return Fd{fd}; }); } -[[nodiscard]] inline expected write(const Fd& fd, std::span buf) noexcept { +[[nodiscard]] inline expected write(const AsFd auto& fd, std::span buf) noexcept { return sysVal(::write(fd.fd(), buf.data(), buf.size())).transform([](int written) { return static_cast(written); }); } -[[nodiscard]] inline expected read(const Fd& fd, std::span buf) noexcept { +[[nodiscard]] inline expected read(const AsFd auto& fd, std::span buf) noexcept { return sysVal(::read(fd.fd(), buf.data(), buf.size())).transform([](int read) { return static_cast(read); }); } +inline expected setBlocking(const AsFd auto& fd, bool blocking) noexcept { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + return sysVal(::fcntl(fd.fd(), F_GETFL, 0)).and_then([&](auto flags) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + return sysCheck(::fcntl(fd.fd(), F_SETFL, blocking ? flags & ~O_NONBLOCK : flags | O_NONBLOCK)); + }); +} + +[[nodiscard]] inline expected> makePipe() { + std::array raw_fds{}; + + return sysCheck(::pipe2(raw_fds.data(), SOCK_CLOEXEC)).transform([&]() { + auto read_fd = io::Fd{raw_fds[0]}; + auto write_fd = io::Fd{raw_fds[1]}; + + return std::pair{std::move(read_fd), std::move(write_fd)}; + }); +} + } // namespace fastipc::io diff --git a/src/io/io_env.hxx b/src/io/io_env.hxx new file mode 100644 index 0000000..0f699b6 --- /dev/null +++ b/src/io/io_env.hxx @@ -0,0 +1,38 @@ +/* + * io_env.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "co/coroutine.hxx" +#include "co/scheduler.hxx" +#include "reactor.hxx" + +namespace fastipc::io { + +struct Env final { + co::Scheduler* scheduler; + Reactor* reactor; +}; + +template +using Co = co::Co; + +template +using Promise = co::Promise; + +} // namespace fastipc::io diff --git a/src/io/polled_io.hxx b/src/io/polled_io.hxx new file mode 100644 index 0000000..f6e0cbe --- /dev/null +++ b/src/io/polled_io.hxx @@ -0,0 +1,139 @@ +/* + * polled_io.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include "co/coroutine.hxx" +#include "io/io_env.hxx" +#include "fd.hxx" +#include "reactor.hxx" +#include "result.hxx" + +namespace fastipc::io { + +class PolledFd final { + public: + PolledFd(const PolledFd&) noexcept = delete; + PolledFd& operator=(const PolledFd&) noexcept = delete; + + PolledFd(PolledFd&& it) noexcept = default; + PolledFd& operator=(PolledFd&& rhs) noexcept = default; + + ~PolledFd() noexcept { + if (m_fd.fd() != -1) { + const auto res = expected(m_reactor->unregister(m_registration)); + + static_cast(res); + } + } + + static Co> create(Fd fd) noexcept { + auto& reactor = *(co_await co::getEnv()).reactor; + + co_return setBlocking(fd, false) + .and_then([&]() { return reactor.registerFd(fd); }) + .transform([&](auto* registration) { return PolledFd{std::move(fd), registration, reactor}; }); + } + + [[nodiscard]] constexpr const int& fd() const noexcept { return m_fd.fd(); } + + private: + template + friend class TryIoAwaiter; + + PolledFd(Fd fd, Reactor::Registration* registration, Reactor& reactor) noexcept + : m_fd{std::move(fd)}, m_registration{registration}, m_reactor{&reactor} {} + + Fd m_fd; + Reactor::Registration* m_registration; + Reactor* m_reactor; +}; + +template +class TryIoAwaiter final { + public: + using value_type = std::invoke_result_t; + + explicit TryIoAwaiter(const io::PolledFd& fd, io::Direction direction, F io) + : m_fd{&fd}, m_direction{direction}, m_io{std::move(io)} {} + + bool await_ready() noexcept { return false; } + + template + void await_suspend(std::coroutine_handle> cont) { + m_cont = cont; + m_env = &cont.promise().env(); + + m_env->scheduler->schedule([this]() { poll(); }); + } + + value_type await_resume() noexcept { return std::move(m_value).value(); } + + private: + void poll() { + auto res = m_io(); + + if (!res.has_value()) { + if (res.error() == std::errc::operation_would_block || + res.error() == std::errc::resource_unavailable_try_again) { + + auto& registration = *m_fd->m_registration; + auto& cb = m_direction == io::Direction::Read ? registration.read_cb : registration.write_cb; + + cb = [this]() { m_env->scheduler->schedule([this]() { poll(); }); }; + + return; + } + } + + m_value = std::move(res); + + m_env->scheduler->schedule([&]() { m_cont.resume(); }); + } + + const io::PolledFd* m_fd; + io::Direction m_direction; + F m_io; + + const Env* m_env = nullptr; + std::optional m_value = {}; + std::coroutine_handle<> m_cont; +}; + +// [[nodiscard]] inline Co>> makePipeAsync() { +// auto make_res = makePipe(); + +// if (make_res.error()) { +// co_return unexpected{make_res.error()}; +// } + +// auto [r, w] = std::move(make_res).value(); + +// co_return std::pair{ +// co_await PolledFd::create(std::move(r)), +// co_await PolledFd::create(std::move(w)), +// }; +// } + +// auto [read_fd, write_fd] = expect(io::makePipe()); + +// auto aread_fd = expect(co_await io::PolledFd::create(std::move(read_fd))); +// auto awrite_fd = expect(co_await io::PolledFd::create(std::move(write_fd))); + +} // namespace fastipc::io diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx new file mode 100644 index 0000000..ae2f3e3 --- /dev/null +++ b/src/io/reactor.cxx @@ -0,0 +1,135 @@ +/* + * reactor.cxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fd.hxx" +#include "reactor.hxx" +#include "result.hxx" + +namespace fastipc::io { + +Reactor::Reactor(Fd event_fd, Fd epoll_fd) : m_event_fd_{std::move(event_fd)}, m_epoll_fd_{std::move(epoll_fd)} {} + +expected Reactor::create() noexcept { + return adoptSysFd(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) + .and_then([](Fd event_fd) { + return adoptSysFd(::epoll_create1(0)) + .and_then([&](Fd epoll_fd) { + ::epoll_event event{.events = EPOLLIN | EPOLLET, .data{.u64 = kEventFdData}}; + + return sysCheck(::epoll_ctl(epoll_fd.fd(), EPOLL_CTL_ADD, event_fd.fd(), &event)).transform([&]() { + return std::move(epoll_fd); + }); + }) + .transform([&](Fd epoll_fd) { return std::pair{std::move(event_fd), std::move(epoll_fd)}; }); + }) + .transform([](std::pair fds) { return Reactor{std::move(fds.first), std::move(fds.second)}; }); +} + +expected Reactor::react(std::optional timeout) noexcept { + return wait(timeout).transform([this](auto events) { process(events); }); +} + +expected> Reactor::wait(std::optional timeout) noexcept { + if (m_registered.empty()) { + return {}; + } + + const auto timeout_ms = timeout.transform([](auto ms) { return static_cast(ms.count()); }).value_or(-1); + + const auto wait_res = sysVal( + ::epoll_wait(m_epoll_fd_.fd(), m_events_buf_.data(), static_cast(m_events_buf_.size()), timeout_ms)); + + return wait_res.transform([this](int n) { return std::span{m_events_buf_}.first(static_cast(n)); }); +} + +void Reactor::process(std::span<::epoll_event> events) noexcept { + for (const auto& event : events) { + if (event.data.u64 == kEventFdData) { + std::uint64_t value{}; + + const auto res = + read(m_event_fd_, std::span{reinterpret_cast(&value), sizeof(value)}); + + static_cast(res); + + continue; + } + + auto& registered_io = *reinterpret_cast(event.data.ptr); + + const auto readable = event.events & (EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR); + if (readable && registered_io.read_cb) { + auto cb = std::move(registered_io.read_cb); + + cb(); + } + + const auto writable = event.events & (EPOLLOUT | EPOLLHUP | EPOLLERR); + if (writable && registered_io.write_cb) { + auto cb = std::move(registered_io.write_cb); + + cb(); + } + } +} + +expected Reactor::interrupt() noexcept { + std::uint64_t value{}; + + return write(m_event_fd_, std::span{reinterpret_cast(&value), sizeof(value)}) + .transform([](std::size_t) {}); +} + +expected Reactor::registerFd(const Fd& fd) noexcept { + auto* registered_io = + &m_registered.emplace(fd.fd(), Registration{.fd = fd.fd(), .read_cb = {}, .write_cb = {}}).first->second; + + // TODO: make this configurable + const auto interests = EPOLLIN | EPOLLOUT; + + ::epoll_event event{.events = interests | EPOLLRDHUP | EPOLLET, .data = {.ptr = registered_io}}; + + return sysCheck(::epoll_ctl(m_epoll_fd_.fd(), EPOLL_CTL_ADD, fd.fd(), &event)).transform([&]() { + // TODO: perhaps just use a fixed buffer + m_events_buf_.resize(m_events_buf_.size() + 1); + + return registered_io; + }); +} + +expected Reactor::unregister(Registration* registration) noexcept { + // TODO: suboptimal + const auto res = sysCheck(::epoll_ctl(m_epoll_fd_.fd(), EPOLL_CTL_DEL, registration->fd, nullptr)); + + const auto it = m_registered.find(registration->fd); + assert(it != m_registered.end()); + m_registered.erase(it); + + return res; +} + +} // namespace fastipc::io diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx new file mode 100644 index 0000000..94a57ee --- /dev/null +++ b/src/io/reactor.hxx @@ -0,0 +1,80 @@ +/* + * reactor.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include +#include "fd.hxx" +#include "result.hxx" + +#include +#include +#include +#include +#include +#include + +namespace fastipc::io { + +enum class Direction : std::uint8_t { + Read, + Write, +}; + +class Reactor final { + public: + struct Registration { + int fd; + std::function read_cb; + std::function write_cb; + }; + + Reactor(const Reactor&) noexcept = delete; + Reactor& operator=(const Reactor&) noexcept = delete; + + Reactor(Reactor&&) noexcept = default; + Reactor& operator=(Reactor&&) noexcept = default; + + ~Reactor() = default; + + static expected create() noexcept; + expected react(std::optional timeout) noexcept; + + expected interrupt() noexcept; + + expected registerFd(const Fd& fd) noexcept; + + expected unregister(Registration* registration) noexcept; + + private: + explicit Reactor(Fd event_fd, Fd epoll_fd); + + expected> wait(std::optional timeout) noexcept; + void process(std::span<::epoll_event> events) noexcept; + + static constexpr std::uint64_t kEventFdData = 1; + Fd m_event_fd_; + + Fd m_epoll_fd_; + std::vector<::epoll_event> m_events_buf_; + + std::unordered_map m_registered; +}; + +} // namespace fastipc::io diff --git a/src/main.cxx b/src/main.cxx index e4070e0..67348d1 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -16,9 +16,38 @@ * */ +#include "co/task.hxx" +#include "io/io_env.hxx" #include "tower.hxx" +namespace fastipc { +namespace { + +io::Co main() { + auto tower = co_await fastipc::Tower::create("fastipcd"); + co_await tower.run(); + + co_return 0; +} + +} // namespace +} // namespace fastipc + int main() { - auto tower = fastipc::Tower::create("fastipcd"); - tower.run(); + auto scheduler = fastipc::co::Scheduler{}; + auto reactor = fastipc::expect(fastipc::io::Reactor::create()); + + fastipc::io::Env env{.scheduler = &scheduler, .reactor = &reactor}; + + // block on? + fastipc::co::spawn(fastipc::main(), env); + + while (scheduler.can_run()) { + while (scheduler.can_run()) { + scheduler.run(); + fastipc::expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + } + + fastipc::expect(reactor.react({}), "failed to react to io events"); + } } diff --git a/src/tower.cxx b/src/tower.cxx index 1023bdc..e78d335 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -39,6 +39,7 @@ #include #include #include +#include "co/task.hxx" #include "io/cursor.hxx" #include "io/fd.hxx" @@ -84,7 +85,7 @@ namespace { } // namespace -[[nodiscard]] Tower Tower::create(std::string_view path) { +[[nodiscard]] io::Co Tower::create(std::string_view path) { auto sockfd = expect(io::adoptSysFd(::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0)), "failed to create tower socket"); @@ -104,10 +105,10 @@ namespace { constexpr int kListenQueueSize{128}; expect(io::sysCheck(::listen(sockfd.fd(), kListenQueueSize)), "failed to listen to tower socket"); - return Tower{std::move(sockfd)}; + co_return Tower{std::move(sockfd)}; } -void Tower::run() { +io::Co Tower::run() { // NOLINTNEXTLINE(altera-unroll-loops) Service loops should not be unrolled for (;;) { auto expected_clientfd = io::adoptSysFd(::accept(m_sockfd.fd(), nullptr, nullptr)); @@ -119,13 +120,14 @@ void Tower::run() { } auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); - serve(std::move(clientfd)); + + co_await co::spawn(serve(std::move(clientfd))); } } void Tower::shutdown() { expect(io::sysCheck(::shutdown(m_sockfd.fd(), SHUT_RD)), "Failed to shutdown tower socket"); } -void Tower::serve(io::Fd clientfd) { +io::Co Tower::serve(io::Fd clientfd) { std::array buf{}; // NOLINT(*-magic-numbers) const auto bytes_read = expect(io::read(clientfd, std::span{buf}), "failed to read from client"); @@ -183,6 +185,8 @@ void Tower::serve(io::Fd clientfd) { msg.msg_controllen = cmsg->cmsg_len; static_cast(expect(io::sysVal(::sendmsg(clientfd.fd(), &msg, 0)), "failed to send reply to client")); + + co_return; } } // namespace fastipc diff --git a/src/tower.hxx b/src/tower.hxx index bbc485b..7d9ca47 100644 --- a/src/tower.hxx +++ b/src/tower.hxx @@ -23,16 +23,16 @@ #include #include "io/fd.hxx" +#include "io/io_env.hxx" #include "channel.hxx" namespace fastipc { class Tower final { public: - [[nodiscard]] static Tower create(std::string_view path); - - void run(); + [[nodiscard]] static io::Co create(std::string_view path); + io::Co run(); void shutdown(); private: @@ -45,7 +45,7 @@ class Tower final { explicit Tower(io::Fd sockfd) noexcept : m_sockfd{std::move(sockfd)} {} - void serve(io::Fd clientfd); + io::Co serve(io::Fd clientfd); io::Fd m_sockfd; std::unordered_map m_channels; diff --git a/src/visitor.hxx b/src/visitor.hxx new file mode 100644 index 0000000..fc95a33 --- /dev/null +++ b/src/visitor.hxx @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace fastipc { + +template +struct Visitor final : Ts... { + using Ts::operator()...; +}; + +template +decltype(auto) match(V&& variant, Ts&&... arms) { + return std::visit(Visitor{std::forward(arms)...}, std::forward(variant)); +} + +} // namespace fastipc \ No newline at end of file diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index a8aa09b..c1b0801 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -18,41 +18,55 @@ #include #include +#include #include #include "fastipc.hxx" #include "tower.hxx" -int main() { +#include "co/coroutine.hxx" +#include "co/task.hxx" +#include "io/context.hxx" +#include "io/io_env.hxx" - auto tower = fastipc::Tower::create("fastipcd"); - const std::jthread tower_thread{[&] { tower.run(); }}; +namespace { - constexpr std::string_view channel_name{"Hallowed are the Ori"}; - constexpr std::size_t max_payload_size{sizeof(int)}; +fastipc::io::Co co_main() { + auto tower = co_await fastipc::Tower::create("fastipcd"); - fastipc::Writer writer{channel_name, max_payload_size}; - fastipc::Reader reader{channel_name, max_payload_size}; + auto test = std::jthread{[&] { + constexpr std::string_view channel_name{"Hallowed are the Ori"}; + constexpr std::size_t max_payload_size{sizeof(int)}; - { - auto sample = reader.acquire(); - assert(sample.getSequenceId() == 0); - reader.release(sample); - } + fastipc::Writer writer{channel_name, max_payload_size}; + fastipc::Reader reader{channel_name, max_payload_size}; - { - auto sample = writer.prepare(); - assert(sample.getSequenceId() == 1); - *static_cast(sample.getPayload()) = 5; // NOLINT(*-magic-numbers) - writer.submit(sample); - } + { + auto sample = reader.acquire(); + assert(sample.getSequenceId() == 0); + reader.release(sample); + } - { - auto sample = reader.acquire(); - assert(sample.getSequenceId() == 1); - assert(*static_cast(sample.getPayload()) == 5); - reader.release(sample); - } + { + auto sample = writer.prepare(); + assert(sample.getSequenceId() == 1); + *static_cast(sample.getPayload()) = 5; // NOLINT(*-magic-numbers) + writer.submit(sample); + } - tower.shutdown(); + { + auto sample = reader.acquire(); + assert(sample.getSequenceId() == 1); + assert(*static_cast(sample.getPayload()) == 5); + reader.release(sample); + } + + tower.shutdown(); + }}; + + co_await tower.run(); } + +} // namespace + +int main() { fastipc::io::context(co_main); } From 130f3bf15be7bb267086a8e4b4bed26237658da8 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sat, 27 Sep 2025 21:21:08 +0200 Subject: [PATCH 04/41] non working stop --- CMakeLists.txt | 9 +- src/co/coroutine.hxx | 211 +++++++++++++++++------- src/co/received.hxx | 19 ++- src/co/task.hxx | 162 ++++++++++++------ src/io/context.hxx | 9 +- src/io/io_env.hxx | 1 + src/io/{polled_io.hxx => polled_fd.hxx} | 105 +++++++++--- src/io/reactor.cxx | 9 +- src/io/reactor.hxx | 10 ++ src/main.cxx | 19 +-- src/tower.cxx | 26 ++- src/tower.hxx | 9 +- test/intraprocess.cxx | 18 +- 13 files changed, 425 insertions(+), 182 deletions(-) rename src/io/{polled_io.hxx => polled_fd.hxx} (58%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f549890..45bbb8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ target_sources ( src/io/addr.hxx src/io/addr.cxx src/io/endian.hxx - src/io/polled_io.hxx + src/io/polled_fd.hxx src/io/reactor.hxx src/io/reactor.cxx src/io/io_env.hxx @@ -35,17 +35,20 @@ target_sources ( src/co/scheduler.hxx src/co/coroutine.hxx src/co/task.hxx + src/co/received.hxx src/fastipc.cxx src/visitor.hxx src/channel.hxx ) + if (NOT DEFINED CMAKE_CXX_CLANG_TIDY OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") target_precompile_headers (fastipc PUBLIC include/fastipc.hxx) endif () -target_compile_features (fastipc INTERFACE cxx_std_17) +target_compile_features (fastipc INTERFACE cxx_std_23) target_compile_features (fastipc PRIVATE cxx_std_23) target_compile_options (fastipc PRIVATE ${FASTIPC_COMPILE_OPTIONS} -Wno-zero-length-array) +target_include_directories (fastipc PUBLIC src) add_library (tower OBJECT) target_include_directories (tower PUBLIC src) @@ -57,7 +60,7 @@ target_link_libraries (tower PRIVATE fastipc) add_executable (fastipcd) target_sources (fastipcd PRIVATE src/main.cxx) target_compile_options (fastipcd PRIVATE ${FASTIPC_COMPILE_OPTIONS}) -target_link_libraries (fastipcd PRIVATE tower) +target_link_libraries (fastipcd PRIVATE tower fastipc) enable_testing () add_subdirectory (test) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index a0b23ec..c51f569 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -22,16 +22,66 @@ #include #include #include -#include #include +#include #include #include -#include #include "received.hxx" -#include "visitor.hxx" namespace fastipc::co { +template +class Receiver { + public: + Receiver() = default; + + Receiver(Receiver&&) noexcept = default; + Receiver& operator=(Receiver&&) noexcept = default; + + Receiver(const Receiver&) noexcept = default; + Receiver& operator=(const Receiver&) noexcept = default; + + virtual ~Receiver() = default; + + virtual void set_value(T value) noexcept = 0; + virtual void set_exception(std::exception_ptr error) noexcept = 0; + virtual void set_stopped() noexcept = 0; +}; + +template <> +class Receiver { + public: + Receiver() = default; + + Receiver(Receiver&&) noexcept = default; + Receiver& operator=(Receiver&&) noexcept = default; + + Receiver(const Receiver&) noexcept = default; + Receiver& operator=(const Receiver&) noexcept = default; + + virtual ~Receiver() = default; + + virtual void set_value() noexcept = 0; + virtual void set_exception(std::exception_ptr error) noexcept = 0; + virtual void set_stopped() noexcept = 0; +}; + +class Handler { + public: + Handler() = default; + + Handler(Handler&&) noexcept = delete; + Handler& operator=(Handler&&) noexcept = delete; + + Handler(const Handler&) noexcept = delete; + Handler& operator=(const Handler&) noexcept = delete; + + virtual ~Handler() = default; + + virtual void unhandled_exception() noexcept = 0; + virtual void unhandled_stopped() noexcept = 0; +}; + template concept valueless = std::is_void_v; @@ -45,7 +95,7 @@ template class Task; template -class EnvAwaiter final { +class EnvAwaiter { public: bool await_ready() noexcept { return false; } @@ -62,12 +112,12 @@ class EnvAwaiter final { const Env* m_env = nullptr; }; -struct GetEnv final {}; +struct GetEnv {}; constexpr GetEnv getEnv() noexcept { return {}; } template -class [[nodiscard]] Co final { +class [[nodiscard]] Co { public: using promise_type = Promise; using handle_type = std::coroutine_handle; @@ -104,42 +154,8 @@ class [[nodiscard]] Co final { handle_type m_handle; }; -template -class Receiver { - public: - Receiver() = default; - - Receiver(Receiver&&) noexcept = default; - Receiver& operator=(Receiver&&) noexcept = default; - - Receiver(const Receiver&) noexcept = default; - Receiver& operator=(const Receiver&) noexcept = default; - - virtual ~Receiver() = default; - - virtual void set_value(T value) noexcept = 0; - virtual void set_exception(std::exception_ptr error) noexcept = 0; -}; - -template <> -class Receiver { - public: - Receiver() = default; - - Receiver(Receiver&&) noexcept = default; - Receiver& operator=(Receiver&&) noexcept = default; - - Receiver(const Receiver&) noexcept = default; - Receiver& operator=(const Receiver&) noexcept = default; - - virtual ~Receiver() = default; - - virtual void set_value() noexcept = 0; - virtual void set_exception(std::exception_ptr error) noexcept = 0; -}; - template -class FinalAwaiter final { +class FinalAwaiter { public: bool await_ready() noexcept { return false; } @@ -147,20 +163,16 @@ class FinalAwaiter final { assert(completed.promise().m_received.has_value()); auto& promise = completed.promise(); - return match( - std::move(promise.m_cont), [](std::coroutine_handle<> cont) -> std::coroutine_handle<> { return cont; }, - [&promise](std::shared_ptr> receiver) -> std::coroutine_handle<> { - std::move(promise.m_received).forward(*receiver); + std::move(promise.m_received).forward(*promise.m_cont); - return std::noop_coroutine(); - }); + return std::noop_coroutine(); } void await_resume() noexcept {} }; template -class Promise final { +class Promise : public Handler { public: Promise() = default; @@ -170,7 +182,7 @@ class Promise final { Promise(const Promise&) noexcept = delete; Promise& operator=(const Promise&) noexcept = delete; - ~Promise() noexcept = default; + ~Promise() noexcept override = default; Co get_return_object() { return Co{std::coroutine_handle::from_promise(*this)}; } @@ -182,7 +194,8 @@ class Promise final { } void return_value(T value) noexcept { m_received.set_value(std::move(value)); } - void unhandled_exception() noexcept { m_received.set_exception(std::current_exception()); } + void unhandled_exception() noexcept override { m_received.set_exception(std::current_exception()); } + void unhandled_stopped() noexcept override { m_received.set_stopped(); } template decltype(auto) await_transform(A&& awaitable) noexcept { @@ -192,6 +205,7 @@ class Promise final { EnvAwaiter await_transform(GetEnv) { return EnvAwaiter{}; } const Env& env() const { return *m_env; } + std::stop_token stop_token() { return m_env->stop_token; } private: friend class Co; @@ -201,13 +215,13 @@ class Promise final { Received m_received; - std::variant, std::shared_ptr>> m_cont = std::noop_coroutine(); + Receiver* m_cont = nullptr; const Env* m_env; }; template -class Promise final { +class Promise : public Handler { public: Promise() = default; @@ -217,7 +231,7 @@ class Promise final { Promise(const Promise&) noexcept = delete; Promise& operator=(const Promise&) noexcept = delete; - ~Promise() noexcept = default; + ~Promise() noexcept override = default; Co get_return_object() { return Co{std::coroutine_handle::from_promise(*this)}; } @@ -229,8 +243,8 @@ class Promise final { } void return_void() noexcept { m_received.set_value(); } - - void unhandled_exception() noexcept { m_received.set_exception(std::current_exception()); } + void unhandled_exception() noexcept override { m_received.set_exception(std::current_exception()); } + void unhandled_stopped() noexcept override { m_received.set_stopped(); } template decltype(auto) await_transform(A&& awaitable) noexcept { @@ -240,6 +254,7 @@ class Promise final { EnvAwaiter await_transform(GetEnv) { return EnvAwaiter{}; } const Env& env() const { return *m_env; } + std::stop_token stop_token() { return m_env->stop_token; } private: friend class Co; @@ -248,13 +263,78 @@ class Promise final { friend class Task; Received m_received; - std::variant, std::shared_ptr>> m_cont = std::noop_coroutine(); + Receiver* m_cont = nullptr; + + const Env* m_env; +}; + +template +class CoReceiver : public Receiver { + public: + CoReceiver() : Receiver{} {} + CoReceiver(std::coroutine_handle<> cont, const Env* env) : m_cont{cont}, m_env{env} {} + + void set_value(T value) noexcept override { + m_received.set_value(std::move(value)); + + m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); + }; + void set_exception(std::exception_ptr error) noexcept override { + m_received.set_exception(error); + + m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); + }; + + // these also need to be scheduled... + void set_stopped() noexcept override { m_received.set_stopped(); }; + + T resume() { + assert(m_received.has_value()); + + return std::move(m_received).consume(); + } + + private: + Received m_received; + std::coroutine_handle<> m_cont; + const Env* m_env; +}; + +template +class CoReceiver : public Receiver { + public: + CoReceiver() : Receiver{} {} + CoReceiver(std::coroutine_handle<> cont, const Env* env) : m_cont{cont}, m_env{env} {} + + void set_value() noexcept override { + m_received.set_value(); + + m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); + }; + + void set_exception(std::exception_ptr error) noexcept override { + m_received.set_exception(error); + + m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); + }; + + // these also need to be scheduled... + void set_stopped() noexcept override { m_received.set_stopped(); }; + + void resume() { + assert(m_received.has_value()); + std::move(m_received).consume(); + } + + private: + Received m_received; + std::coroutine_handle<> m_cont; const Env* m_env; }; template -class [[nodiscard]] CoAwaiter final { +class [[nodiscard]] CoAwaiter { public: explicit CoAwaiter(Co co) noexcept : m_co{std::move(co)} {} @@ -270,23 +350,26 @@ class [[nodiscard]] CoAwaiter final { template std::coroutine_handle<> await_suspend(std::coroutine_handle> cont) { - m_co.m_handle.promise().m_env = &cont.promise().env(); - m_co.m_handle.promise().m_cont = cont; + const auto env = &cont.promise().env(); + m_co.env(env); + + m_receiver = {cont, env}; + m_co.m_handle.promise().m_cont = &m_receiver; + + // TODO: move to scheduler return m_co.m_handle; } T await_resume() { - auto co = std::move(m_co); - - auto& promise = co.m_handle.promise(); + auto _ = std::move(m_co); - assert(promise.m_received.has_value()); - return std::move(promise.m_received).consume(); + return m_receiver.resume(); } private: Co m_co; + CoReceiver m_receiver; }; template diff --git a/src/co/received.hxx b/src/co/received.hxx index 86a1078..c1f3115 100644 --- a/src/co/received.hxx +++ b/src/co/received.hxx @@ -20,18 +20,21 @@ #include #include -#include #include #include #include "visitor.hxx" namespace fastipc::co { +struct has_value_tag final {}; +struct has_stopped_tag final {}; + template struct Received final { void set_value(T value) { m_value = std::move(value); } void set_exception(std::exception_ptr exc) { m_value = std::move(exc); } + void set_stopped() { m_value = has_stopped_tag{}; } [[nodiscard]] bool has_value() const { return !std::holds_alternative(m_value); } @@ -39,7 +42,7 @@ struct Received final { return match( std::move(m_value), [](std::exception_ptr exc) -> T { std::rethrow_exception(std::move(exc)); }, [](T&& value) { return std::move(value); }, - [](std::monostate) -> T { + [](auto) -> T { assert(false); std::unreachable(); }); @@ -50,13 +53,14 @@ struct Received final { match( std::move(m_value), [&receiver](std::exception_ptr exc) { receiver.set_exception(std::move(exc)); }, [&receiver](T&& value) { receiver.set_value(std::move(value)); }, + [&](has_stopped_tag) { receiver.set_stopped(); }, [](std::monostate) { assert(false); std::unreachable(); }); } - std::variant m_value; + std::variant m_value; }; template <> @@ -64,6 +68,7 @@ struct Received final { void set_value() { m_value = has_value_tag{}; } void set_exception(std::exception_ptr exc) { m_value = std::move(exc); } + void set_stopped() { m_value = has_stopped_tag{}; } [[nodiscard]] bool has_value() const { return !std::holds_alternative(m_value); } @@ -71,7 +76,7 @@ struct Received final { match( std::move(m_value), [](std::exception_ptr exc) -> void { std::rethrow_exception(std::move(exc)); }, [](has_value_tag) {}, - [](std::monostate) { + [](auto) { assert(false); std::unreachable(); }); @@ -81,16 +86,14 @@ struct Received final { void forward(R& receiver) && { return match( std::move(m_value), [&](std::exception_ptr exc) { receiver.set_exception(std::move(exc)); }, - [&](has_value_tag) { receiver.set_value(); }, + [&](has_value_tag) { receiver.set_value(); }, [&](has_stopped_tag) { receiver.set_stopped(); }, [](std::monostate) { assert(false); std::unreachable(); }); } - struct has_value_tag final {}; - - std::variant m_value; + std::variant m_value; }; } // namespace fastipc::co \ No newline at end of file diff --git a/src/co/task.hxx b/src/co/task.hxx index 8b83104..d4c9749 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -18,11 +18,13 @@ #pragma once +#include #include #include #include #include #include +#include #include "coroutine.hxx" namespace fastipc::co { @@ -30,9 +32,12 @@ namespace fastipc::co { struct Unit final {}; template -class Task final : public Receiver, public std::enable_shared_from_this> { +class Task : public std::enable_shared_from_this> { public: - Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{&env} { std::println("Task created"); } + Task(Co co, const Env& env) : m_stop_cb{m_stop_source.get_token(), Canary{}}, m_co_run{std::move(co)}, m_env{env} { + // think about if we want to propagate the stop request from parent env + m_env.stop_token = m_stop_source.get_token(); + } Task(const Task&) = delete; Task& operator=(const Task&) = delete; @@ -42,89 +47,141 @@ class Task final : public Receiver, public std::enable_shared_from_this>::shared_from_this(); + promise.m_env = &m_env; - m_env->scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); - } - - void set_value(T value) noexcept override { - m_value = std::move(value); - m_done = true; + m_op = Op{std::enable_shared_from_this::shared_from_this()}; + promise.m_cont = &m_op.value(); - if (m_cont) { - m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); - } + m_env.scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); } - void set_exception(std::exception_ptr) noexcept override { std::terminate(); } - private: template friend class JoinHandle; - Co m_co_run; - const Env* m_env; + struct Op : public Receiver { + std::shared_ptr m_self; + + explicit Op(std::shared_ptr self) : Receiver{}, m_self{self} {} + void set_value(T value) noexcept override { + assert(m_self); + + auto self = std::move(m_self); + self->m_done = true; + self->m_value = std::move(value); + + if (self->m_cont) { + self->m_env.scheduler->schedule([cont = self->m_cont]() { cont.resume(); }); + } + } + + void set_exception(std::exception_ptr exc) noexcept override { + auto self = std::move(m_self); + self->m_done = true; + + std::rethrow_exception(std::move(exc)); // wrong + } + + void set_stopped() noexcept override { + auto self = std::move(m_self); + self->m_done = true; + + std::terminate(); + } + }; + + struct Canary { + void operator()() { + std::println("task abort requested"); + } + }; + + std::stop_source m_stop_source; + std::stop_callback m_stop_cb; bool m_done = false; std::optional m_value{}; + std::optional m_op; + + Co m_co_run; + Env m_env; + + + // I think this also needs to become a Receiver.. std::coroutine_handle<> m_cont; }; -template -class Task final : public Receiver, public std::enable_shared_from_this> { - public: - Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{&env} {} +// template +// class Task final : public Receiver, public std::enable_shared_from_this> { +// public: +// Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{&env} {} - Task(const Task&) = delete; - Task& operator=(const Task&) = delete; +// Task(const Task&) = delete; +// Task& operator=(const Task&) = delete; - Task(Task&&) = default; - Task& operator=(Task&&) = default; +// Task(Task&&) = default; +// Task& operator=(Task&&) = default; - [[nodiscard]] bool completed() const noexcept { return m_done; } +// [[nodiscard]] bool completed() const noexcept { return m_done; } - ~Task() noexcept override = default; +// ~Task() noexcept override = default; - void start() { - auto& promise = m_co_run.m_handle.promise(); +// void start() { +// auto& promise = m_co_run.m_handle.promise(); - promise.m_env = m_env; - promise.m_cont = std::enable_shared_from_this>::shared_from_this(); +// promise.m_env = m_env; +// promise.m_cont = this; - m_env->scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); - } +// m_env->scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); - void set_value() noexcept override { - m_done = true; +// m_lifetime = std::enable_shared_from_this::shared_from_this(); +// } - if (m_cont) { - m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); - } - } +// void set_value() noexcept override { +// auto _ = std::move(m_lifetime); +// m_done = true; - void set_exception(std::exception_ptr exc) noexcept override { - m_done = true; - std::rethrow_exception(std::move(exc)); - } +// if (m_cont) { +// m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); +// } +// } - private: - template - friend class JoinHandle; +// void set_exception(std::exception_ptr exc) noexcept override { +// auto _ = std::move(m_lifetime); +// m_done = true; +// std::rethrow_exception(std::move(exc)); +// } - Co m_co_run; - const Env* m_env; +// void set_stopped() noexcept override { +// auto _ = std::move(m_lifetime); +// m_done = true; +// if (m_cont) { +// m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); +// } +// } - bool m_done = false; +// private: +// template +// friend class JoinHandle; - std::coroutine_handle<> m_cont; -}; +// Co m_co_run; +// const Env* m_env; + +// bool m_done = false; + +// std::coroutine_handle<> m_cont; +// std::shared_ptr m_lifetime; +// }; template class JoinHandle final { @@ -133,6 +190,7 @@ class JoinHandle final { explicit JoinHandle(std::shared_ptr> task) noexcept : m_task{std::move(task)} {} [[nodiscard]] bool completed() const noexcept { return m_task->completed(); } + void abort() noexcept { m_task->abort(); } bool await_ready() noexcept { return completed(); } diff --git a/src/io/context.hxx b/src/io/context.hxx index e2657a6..aa7bf9c 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,5 +1,7 @@ #pragma once +#include #include "co/scheduler.hxx" +#include "co/task.hxx" #include "io_env.hxx" #include "reactor.hxx" @@ -10,17 +12,22 @@ void context(F func) { auto scheduler = co::Scheduler{}; auto reactor = expect(Reactor::create()); - Env env{.scheduler = &scheduler, .reactor = &reactor}; + Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; auto task = co::spawn(func(), env); while (scheduler.can_run()) { while (scheduler.can_run()) { scheduler.run(); + std::println("inner loop"); expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + std::println("inner loop wake"); } + std::println("outer loop"); expect(reactor.react({}), "failed to react to io events"); + std::println("outer loop wake"); + } static_cast(task); diff --git a/src/io/io_env.hxx b/src/io/io_env.hxx index 0f699b6..c8f0982 100644 --- a/src/io/io_env.hxx +++ b/src/io/io_env.hxx @@ -27,6 +27,7 @@ namespace fastipc::io { struct Env final { co::Scheduler* scheduler; Reactor* reactor; + std::stop_token stop_token; }; template diff --git a/src/io/polled_io.hxx b/src/io/polled_fd.hxx similarity index 58% rename from src/io/polled_io.hxx rename to src/io/polled_fd.hxx index f6e0cbe..2aa902b 100644 --- a/src/io/polled_io.hxx +++ b/src/io/polled_fd.hxx @@ -1,5 +1,5 @@ /* - * polled_io.hxx + * polled_fd.hxx * Copyright 2025 ItJustWorksTM * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,13 @@ #pragma once +#include +#include #include +#include +#include +#include +#include #include "co/coroutine.hxx" #include "io/io_env.hxx" #include "fd.hxx" @@ -79,61 +85,118 @@ class TryIoAwaiter final { void await_suspend(std::coroutine_handle> cont) { m_cont = cont; m_env = &cont.promise().env(); + std::stop_token st = cont.promise().stop_token(); - m_env->scheduler->schedule([this]() { poll(); }); + if (st.stop_requested()) { + + set_stopped(); + return; + } + + m_stop_fn.emplace(std::move(st), StopFn{this}); + + schedule_poll(); } value_type await_resume() noexcept { return std::move(m_value).value(); } private: void poll() { + if (stop_requested) { + std::println("stop request so we set_stopped!"); + set_stopped(); + return; + } + + // POSSIBLE RACE CONDITION + // stop_request set to true, but in flight, set new reactor callback, ignore stop.. + auto res = m_io(); if (!res.has_value()) { if (res.error() == std::errc::operation_would_block || res.error() == std::errc::resource_unavailable_try_again) { - auto& registration = *m_fd->m_registration; - auto& cb = m_direction == io::Direction::Read ? registration.read_cb : registration.write_cb; - - cb = [this]() { m_env->scheduler->schedule([this]() { poll(); }); }; + state = State::Blocked; + m_fd->m_registration->callback(m_direction, [this]() { schedule_poll(); }); return; } } + set_value(std::move(res)); + } + + void set_value(value_type res) { + m_stop_fn.reset(); + m_value = std::move(res); + state = State::Done; m_env->scheduler->schedule([&]() { m_cont.resume(); }); } + void set_stopped() { + m_stop_fn.reset(); + + state = State::Done; + + // TODO: propagate, use adapter for that + assert(false); + } + + void schedule_poll() noexcept { + if (state != State::Blocked) { + return; + } + + state = State::Flight; + m_env->scheduler->schedule([this]() { poll(); }); + } + const io::PolledFd* m_fd; io::Direction m_direction; F m_io; + enum class State : std::uint8_t { + Blocked, + Flight, + Done, + }; + + State state = State::Blocked; + bool stop_requested = false; + const Env* m_env = nullptr; std::optional m_value = {}; std::coroutine_handle<> m_cont; -}; -// [[nodiscard]] inline Co>> makePipeAsync() { -// auto make_res = makePipe(); + struct StopFn final { + TryIoAwaiter* self; -// if (make_res.error()) { -// co_return unexpected{make_res.error()}; -// } + void operator()() noexcept { -// auto [r, w] = std::move(make_res).value(); + std::println("TryIoAwaiter::StopFn"); -// co_return std::pair{ -// co_await PolledFd::create(std::move(r)), -// co_await PolledFd::create(std::move(w)), -// }; -// } + self->stop_requested = true; + self->m_fd->m_registration->callback(self->m_direction, {}); + // need to interrupt the reactor... + expect(self->m_fd->m_reactor->interrupt(), "fuck"); + } + }; -// auto [read_fd, write_fd] = expect(io::makePipe()); + std::optional> m_stop_fn; +}; + +inline Co> accept(PolledFd& fd) { + auto accepted_fd_res = co_await io::TryIoAwaiter{fd, io::Direction::Read, + [&]() { return adoptSysFd(::accept(fd.fd(), nullptr, nullptr)); }}; + + if (!accepted_fd_res.has_value()) { + co_return unexpected{accepted_fd_res.error()}; + } -// auto aread_fd = expect(co_await io::PolledFd::create(std::move(read_fd))); -// auto awrite_fd = expect(co_await io::PolledFd::create(std::move(write_fd))); + co_return co_await PolledFd::create(std::move(accepted_fd_res).value()); +} } // namespace fastipc::io diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index ae2f3e3..28bc120 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,8 @@ expected> Reactor::wait(std::optional events) noexcept { for (const auto& event : events) { + std::println("event: {}", event.data.u64); + if (event.data.u64 == kEventFdData) { std::uint64_t value{}; @@ -76,6 +79,8 @@ void Reactor::process(std::span<::epoll_event> events) noexcept { static_cast(res); + std::println("interrupt received"); + continue; } @@ -100,8 +105,10 @@ void Reactor::process(std::span<::epoll_event> events) noexcept { expected Reactor::interrupt() noexcept { std::uint64_t value{}; + std::println("interrupting reactor {}", m_event_fd_.fd()); + return write(m_event_fd_, std::span{reinterpret_cast(&value), sizeof(value)}) - .transform([](std::size_t) {}); + .transform([](std::size_t n) { std::println("interrupt written {}", n); }); } expected Reactor::registerFd(const Fd& fd) noexcept { diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index 94a57ee..b1669de 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include #include @@ -43,6 +45,14 @@ class Reactor final { int fd; std::function read_cb; std::function write_cb; + + void callback(io::Direction direction, std::function cb) noexcept { + auto old = std::exchange(direction == io::Direction::Read ? read_cb : write_cb, std::move(cb)); + + if (old) { + old(); + } + } }; Reactor(const Reactor&) noexcept = delete; diff --git a/src/main.cxx b/src/main.cxx index 67348d1..3220f12 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -16,7 +16,7 @@ * */ -#include "co/task.hxx" +#include "io/context.hxx" #include "io/io_env.hxx" #include "tower.hxx" @@ -34,20 +34,5 @@ io::Co main() { } // namespace fastipc int main() { - auto scheduler = fastipc::co::Scheduler{}; - auto reactor = fastipc::expect(fastipc::io::Reactor::create()); - - fastipc::io::Env env{.scheduler = &scheduler, .reactor = &reactor}; - - // block on? - fastipc::co::spawn(fastipc::main(), env); - - while (scheduler.can_run()) { - while (scheduler.can_run()) { - scheduler.run(); - fastipc::expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); - } - - fastipc::expect(reactor.react({}), "failed to react to io events"); - } + fastipc::io::context(fastipc::main); } diff --git a/src/tower.cxx b/src/tower.cxx index e78d335..4e05fdb 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -43,6 +43,8 @@ #include "io/cursor.hxx" #include "io/fd.hxx" +#include "io/polled_fd.hxx" +#include "io/reactor.hxx" #include "io/result.hxx" #include "channel.hxx" #include "local_proto.hxx" @@ -86,6 +88,7 @@ namespace { } // namespace [[nodiscard]] io::Co Tower::create(std::string_view path) { + auto sockfd = expect(io::adoptSysFd(::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0)), "failed to create tower socket"); @@ -98,6 +101,7 @@ namespace { auto unlink_res = io::sysCheck(::unlink(addr.sun_path)); static_cast(unlink_res); + // TODO: This technically has to be async as well // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) expect(io::sysCheck(::bind(sockfd.fd(), reinterpret_cast(&addr), sizeof(addr))), "failed to bind tower socket"); @@ -105,29 +109,37 @@ namespace { constexpr int kListenQueueSize{128}; expect(io::sysCheck(::listen(sockfd.fd(), kListenQueueSize)), "failed to listen to tower socket"); - co_return Tower{std::move(sockfd)}; + co_return Tower{expect(co_await io::PolledFd::create(std::move(sockfd)), "failed to created polled fd")}; } -io::Co Tower::run() { +io::Co Tower::run() { // NOLINTNEXTLINE(altera-unroll-loops) Service loops should not be unrolled for (;;) { - auto expected_clientfd = io::adoptSysFd(::accept(m_sockfd.fd(), nullptr, nullptr)); + auto expected_clientfd = co_await accept(m_sockfd); + if (!expected_clientfd.has_value()) { - if (expected_clientfd.error() == std::errc::invalid_argument) + if (expected_clientfd.error() == std::errc::bad_file_descriptor) break; if (expected_clientfd.error() == std::errc::connection_aborted) continue; + + std::println("warn: {}", expected_clientfd.error().message()); } auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); + // by detaching we have no way of shutting clients down co_await co::spawn(serve(std::move(clientfd))); } + + co_return 0; // lazy void } -void Tower::shutdown() { expect(io::sysCheck(::shutdown(m_sockfd.fd(), SHUT_RD)), "Failed to shutdown tower socket"); } +void Tower::shutdown() { + // expect(io::sysCheck(::shutdown(m_sockfd.fd(), SHUT_RD)), "Failed to shutdown tower socket"); +} -io::Co Tower::serve(io::Fd clientfd) { +io::Co Tower::serve(io::PolledFd clientfd) { std::array buf{}; // NOLINT(*-magic-numbers) const auto bytes_read = expect(io::read(clientfd, std::span{buf}), "failed to read from client"); @@ -186,7 +198,7 @@ io::Co Tower::serve(io::Fd clientfd) { static_cast(expect(io::sysVal(::sendmsg(clientfd.fd(), &msg, 0)), "failed to send reply to client")); - co_return; + co_return 0; // too lazy for void } } // namespace fastipc diff --git a/src/tower.hxx b/src/tower.hxx index 7d9ca47..554cfb8 100644 --- a/src/tower.hxx +++ b/src/tower.hxx @@ -23,6 +23,7 @@ #include #include "io/fd.hxx" +#include "io/polled_fd.hxx" #include "io/io_env.hxx" #include "channel.hxx" @@ -32,7 +33,7 @@ class Tower final { public: [[nodiscard]] static io::Co create(std::string_view path); - io::Co run(); + io::Co run(); void shutdown(); private: @@ -43,11 +44,11 @@ class Tower final { impl::ChannelPage* page{nullptr}; }; - explicit Tower(io::Fd sockfd) noexcept : m_sockfd{std::move(sockfd)} {} + explicit Tower(io::PolledFd sockfd) noexcept : m_sockfd{std::move(sockfd)} {} - io::Co serve(io::Fd clientfd); + io::Co serve(io::PolledFd clientfd); - io::Fd m_sockfd; + io::PolledFd m_sockfd; std::unordered_map m_channels; }; diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index c1b0801..f13a18e 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -24,16 +24,19 @@ #include "fastipc.hxx" #include "tower.hxx" -#include "co/coroutine.hxx" #include "co/task.hxx" #include "io/context.hxx" #include "io/io_env.hxx" namespace { -fastipc::io::Co co_main() { +fastipc::io::Co co_main() { auto tower = co_await fastipc::Tower::create("fastipcd"); + std::println("starting run task"); + auto handle = co_await fastipc::co::spawn(tower.run()); + + std::println("starting test thread"); auto test = std::jthread{[&] { constexpr std::string_view channel_name{"Hallowed are the Ori"}; constexpr std::size_t max_payload_size{sizeof(int)}; @@ -61,10 +64,17 @@ fastipc::io::Co co_main() { reader.release(sample); } - tower.shutdown(); + // tower.shutdown(); + std::println("test done. stopping handle"); + handle.abort(); }}; - co_await tower.run(); + std::println("waiting for complete"); + co_await handle; + + std::println("run done!"); + + co_return 0; // too lazy for void } } // namespace From 1a559c1c3cd24674f8712a603d71da942ef5c124 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 28 Sep 2025 12:54:46 +0200 Subject: [PATCH 05/41] Lets start stacking commits --- src/co/scheduler.hxx | 18 +++++++++++++++--- src/io/context.hxx | 6 +----- src/io/polled_fd.hxx | 10 +++------- src/io/reactor.cxx | 19 +++++++------------ src/io/reactor.hxx | 1 + test/intraprocess.cxx | 3 --- 6 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx index ebfde3f..45f44df 100644 --- a/src/co/scheduler.hxx +++ b/src/co/scheduler.hxx @@ -20,6 +20,7 @@ #include #include +#include #include namespace fastipc::co { @@ -27,11 +28,21 @@ namespace fastipc::co { class Scheduler final { public: // TODO: use std::function_ref - void schedule(std::function fn) { m_queue.push(std::move(fn)); } + void schedule(std::function fn) { + auto lock = std::scoped_lock{m_child_lock}; - [[nodiscard]] bool can_run() const noexcept { return !m_queue.empty(); } + m_queue.push(std::move(fn)); + } + + [[nodiscard]] bool can_run() const noexcept { + auto lock = std::scoped_lock{m_child_lock}; + + return !m_queue.empty(); + } void run() noexcept { + auto lock = std::scoped_lock{m_child_lock}; + const auto queued = m_queue.size(); for (std::size_t i = 0; i < queued; ++i) { @@ -41,7 +52,8 @@ class Scheduler final { } private: + mutable std::recursive_mutex m_child_lock; std::queue> m_queue; }; -} // namespace fastipc::io \ No newline at end of file +} // namespace fastipc::co \ No newline at end of file diff --git a/src/io/context.hxx b/src/io/context.hxx index aa7bf9c..fdda40a 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,4 +1,5 @@ #pragma once +#include #include #include "co/scheduler.hxx" #include "co/task.hxx" @@ -19,15 +20,10 @@ void context(F func) { while (scheduler.can_run()) { while (scheduler.can_run()) { scheduler.run(); - std::println("inner loop"); expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); - std::println("inner loop wake"); } - std::println("outer loop"); expect(reactor.react({}), "failed to react to io events"); - std::println("outer loop wake"); - } static_cast(task); diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 2aa902b..51b0849 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -19,12 +19,10 @@ #pragma once #include -#include #include #include #include #include -#include #include "co/coroutine.hxx" #include "io/io_env.hxx" #include "fd.hxx" @@ -142,6 +140,7 @@ class TryIoAwaiter final { state = State::Done; // TODO: propagate, use adapter for that + std::println("set_stopped was called on TryIoAwaiter"); assert(false); } @@ -175,13 +174,10 @@ class TryIoAwaiter final { TryIoAwaiter* self; void operator()() noexcept { - - std::println("TryIoAwaiter::StopFn"); - self->stop_requested = true; self->m_fd->m_registration->callback(self->m_direction, {}); - // need to interrupt the reactor... - expect(self->m_fd->m_reactor->interrupt(), "fuck"); + // need to interrupt the reactor, idk if that should be done here though... + self->m_fd->m_reactor->interrupt(); } }; diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index 28bc120..f7c9dcc 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -32,10 +32,12 @@ namespace fastipc::io { -Reactor::Reactor(Fd event_fd, Fd epoll_fd) : m_event_fd_{std::move(event_fd)}, m_epoll_fd_{std::move(epoll_fd)} {} +Reactor::Reactor(Fd event_fd, Fd epoll_fd) : m_event_fd_{std::move(event_fd)}, m_epoll_fd_{std::move(epoll_fd)} { + m_events_buf_.resize(512); +} expected Reactor::create() noexcept { - return adoptSysFd(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) + return adoptSysFd(::eventfd(0, EFD_CLOEXEC)) .and_then([](Fd event_fd) { return adoptSysFd(::epoll_create1(0)) .and_then([&](Fd epoll_fd) { @@ -69,8 +71,6 @@ expected> Reactor::wait(std::optional events) noexcept { for (const auto& event : events) { - std::println("event: {}", event.data.u64); - if (event.data.u64 == kEventFdData) { std::uint64_t value{}; @@ -79,7 +79,7 @@ void Reactor::process(std::span<::epoll_event> events) noexcept { static_cast(res); - std::println("interrupt received"); + std::println("reactor interrupt received"); continue; } @@ -103,12 +103,10 @@ void Reactor::process(std::span<::epoll_event> events) noexcept { } expected Reactor::interrupt() noexcept { - std::uint64_t value{}; - - std::println("interrupting reactor {}", m_event_fd_.fd()); + std::uint64_t value{1}; return write(m_event_fd_, std::span{reinterpret_cast(&value), sizeof(value)}) - .transform([](std::size_t n) { std::println("interrupt written {}", n); }); + .transform([](std::size_t) {}); } expected Reactor::registerFd(const Fd& fd) noexcept { @@ -121,9 +119,6 @@ expected Reactor::registerFd(const Fd& fd) noexcept { ::epoll_event event{.events = interests | EPOLLRDHUP | EPOLLET, .data = {.ptr = registered_io}}; return sysCheck(::epoll_ctl(m_epoll_fd_.fd(), EPOLL_CTL_ADD, fd.fd(), &event)).transform([&]() { - // TODO: perhaps just use a fixed buffer - m_events_buf_.resize(m_events_buf_.size() + 1); - return registered_io; }); } diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index b1669de..73cd547 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -47,6 +47,7 @@ class Reactor final { std::function write_cb; void callback(io::Direction direction, std::function cb) noexcept { + // could make thread safe if we want to interrupt from different threads... auto old = std::exchange(direction == io::Direction::Read ? read_cb : write_cb, std::move(cb)); if (old) { diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index f13a18e..58fe41a 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -33,10 +33,8 @@ namespace { fastipc::io::Co co_main() { auto tower = co_await fastipc::Tower::create("fastipcd"); - std::println("starting run task"); auto handle = co_await fastipc::co::spawn(tower.run()); - std::println("starting test thread"); auto test = std::jthread{[&] { constexpr std::string_view channel_name{"Hallowed are the Ori"}; constexpr std::size_t max_payload_size{sizeof(int)}; @@ -69,7 +67,6 @@ fastipc::io::Co co_main() { handle.abort(); }}; - std::println("waiting for complete"); co_await handle; std::println("run done!"); From 58ec7523d80100e3371eff0b5bd9dbe4c22454c5 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 3 Oct 2025 19:35:16 +0200 Subject: [PATCH 06/41] Almost got a good pattern --- src/co/coroutine.hxx | 13 ++- src/co/coroutine3.hxx | 238 ++++++++++++++++++++++++++++++++++++++++++ src/io/context.hxx | 2 - src/io/polled_fd.hxx | 8 +- 4 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 src/co/coroutine3.hxx diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index c51f569..d70894c 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -242,9 +242,12 @@ class Promise : public Handler { return {}; } + // So what if we call the receiver directly? why cache it? void return_void() noexcept { m_received.set_value(); } void unhandled_exception() noexcept override { m_received.set_exception(std::current_exception()); } - void unhandled_stopped() noexcept override { m_received.set_stopped(); } + void unhandled_stopped() noexcept override { + // this does not trigger final_suspend, so that means we cant rely on it... + m_received.set_stopped(); } template decltype(auto) await_transform(A&& awaitable) noexcept { @@ -319,8 +322,12 @@ class CoReceiver : public Receiver { m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); }; - // these also need to be scheduled... - void set_stopped() noexcept override { m_received.set_stopped(); }; + void set_stopped() noexcept override { + m_received.set_stopped(); + + // TODO: also needs to be scheduled... + assert(false); + }; void resume() { assert(m_received.has_value()); diff --git a/src/co/coroutine3.hxx b/src/co/coroutine3.hxx new file mode 100644 index 0000000..3ea383a --- /dev/null +++ b/src/co/coroutine3.hxx @@ -0,0 +1,238 @@ +#pragma once + +#include +#include +#include +#include +#include "co/received.hxx" + +namespace fastipc::co { + +template +class Receiver { + public: + Receiver() = default; + + Receiver(Receiver&&) noexcept = default; + Receiver& operator=(Receiver&&) noexcept = default; + + Receiver(const Receiver&) noexcept = default; + Receiver& operator=(const Receiver&) noexcept = default; + + virtual ~Receiver() = default; + + virtual void set_value(T value) = 0; + virtual void set_exception(std::exception_ptr exc) = 0; + virtual void set_stopped() = 0; + + virtual Env& env() = 0; +}; + +template +struct AwaitedBy { + A value; +}; + +template +class [[nodiscard]] Promise { + public: + class State { + + public: + using env_type = Env; + + Promise get_return_object() { return Promise{std::coroutine_handle::from_promise(*this)}; } + + std::suspend_always initial_suspend() noexcept { return {}; } + + struct FinalSuspend { + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle h) noexcept { h.promise().complete(); } + void await_resume() noexcept {} + }; + + FinalSuspend final_suspend() noexcept { return {}; } + + void return_value(T value) { received.set_value(std::move(value)); } + void unhandled_exception() { received.set_exception(std::current_exception()); } + void unhandled_stopped() { received.set_stopped(); } + + template + AwaitedBy::State> await_transform(A&& awaitable) { + return {std::forward(awaitable)}; + } + + Receiver* receiver; + + Env& env() { return receiver->env(); } + + private: + // sadly we need to buffer the received value to allow final_suspend to run + void complete() { std::move(received).forward(*receiver); } + + Received received; + }; + + explicit Promise(std::coroutine_handle handle) : m_handle{std::move(handle)} {} + + Promise(Promise&) noexcept = delete; + Promise& operator=(Promise&) noexcept = delete; + + Promise(Promise&& it) noexcept : m_handle{std::exchange(it.m_handle, {})} {} + Promise& operator=(Promise&& rhs) noexcept { + auto other = Promise{std::move(rhs)}; + + std::swap(m_handle, other.m_handle); + + return *this; + } + + ~Promise() noexcept { + if (m_handle) { + m_handle.destroy(); + } + } + + std::coroutine_handle handle() { return m_handle; } + std::coroutine_handle handle() const { return m_handle; } + + private: + std::coroutine_handle m_handle; +}; + +template +class SenderAwaiter { + + public: + using sender_type = S; + using value_type = typename sender_type::template value_type; + + explicit SenderAwaiter(sender_type sender) : state{std::move(sender)} {} + + bool await_ready() noexcept { return false; } + + std::coroutine_handle<> await_suspend(std::coroutine_handle

cont) { + auto& operation_state = state.template emplace( + std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); + + operation_state.start(); + + return std::noop_coroutine(); + } + + [[nodiscard]] value_type await_resume() noexcept { return std::move(received).consume(); } + + private: + class AwaiterReceiver { + public: + AwaiterReceiver(Received& received, std::coroutine_handle

awaiter) + : m_received{&received}, m_awaiter{awaiter} {} + + void set_value(value_type value) { + m_received->set_value(std::move(value)); + + m_awaiter.resume(); + } + + void set_exception(std::exception_ptr ptr) { + m_received->set_exception(ptr); + + m_awaiter.resume(); + } + + void set_stopped() { + m_received->set_stopped(); + + m_awaiter.promise().unhandled_stopped(); + } + + auto& env() { return m_awaiter.promise().env(); } + + private: + Received* m_received; + std::coroutine_handle

m_awaiter; + }; + + using operation_state_type = decltype(std::declval().connect(std::declval())); + + std::variant state; + Received received; +}; + +// todo sender concept +template +SenderAwaiter operator co_await(AwaitedBy&& awaited_by) { + return SenderAwaiter{std::move(awaited_by).value}; +} + +template +class [[nodiscard]] Co { + + public: + using promise_type = Promise::State; + + template + using value_type = T; + + explicit(false) Co(Promise promise) : m_promise{std::move(promise)} {} + + template + auto connect(R&& receiver) && { + class OperationState { + + public: + OperationState(Promise promise, R receiver) + : m_receiver{std::move(receiver)}, m_promise{std::move(promise)} {} + + void start() { + m_promise.handle().promise().receiver = &m_receiver; + + // TODO: schedule it + m_promise.handle().resume(); + } + + private: + class PromiseReceiver : public Receiver { + + public: + explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} + + void set_value(T value) override { m_receiver.set_value(std::move(value)); } + void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } + void set_stopped() override { m_receiver.set_stopped(); } + + Env& env() override { return m_receiver.env(); } + + private: + R m_receiver; + }; + + PromiseReceiver m_receiver; + Promise m_promise; + }; + + return OperationState{std::move(*this).m_promise, std::forward(receiver)}; + } + + private: + Promise m_promise; +}; + +struct EnvSender { + template + using value_type = Env; + + template + struct OperationState { + R receiver; + + void start() { receiver.set_value(receiver.env()); } + }; + + template + OperationState connect(R&& receiver) { + return {std::forward(receiver)}; + } +}; + +} // namespace fastipc::co diff --git a/src/io/context.hxx b/src/io/context.hxx index fdda40a..2505ce0 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,6 +1,4 @@ #pragma once -#include -#include #include "co/scheduler.hxx" #include "co/task.hxx" #include "io_env.hxx" diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 51b0849..2edbd5f 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -83,6 +83,7 @@ class TryIoAwaiter final { void await_suspend(std::coroutine_handle> cont) { m_cont = cont; m_env = &cont.promise().env(); + std::stop_token st = cont.promise().stop_token(); if (st.stop_requested()) { @@ -139,16 +140,19 @@ class TryIoAwaiter final { state = State::Done; - // TODO: propagate, use adapter for that + // TODO: propagate, use adapter for that? std::println("set_stopped was called on TryIoAwaiter"); assert(false); + + // this is why we want Handler? + m_cont.promise().unhandled_stopped(); } void schedule_poll() noexcept { if (state != State::Blocked) { return; } - + state = State::Flight; m_env->scheduler->schedule([this]() { poll(); }); } From 32de7865c8b00d284c4167523368d6a080e90884 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sat, 4 Oct 2025 12:07:19 +0200 Subject: [PATCH 07/41] More progress yay, just need Task to work now --- src/co/coroutine.hxx | 416 ++++++++++++++---------------------------- src/co/coroutine3.hxx | 238 ------------------------ src/co/scheduler.hxx | 4 +- src/co/task.hxx | 172 ++++++++--------- src/io/context.hxx | 20 +- src/io/io_env.hxx | 1 + src/io/polled_fd.hxx | 185 ++++++++++--------- src/io/reactor.hxx | 6 +- src/main.cxx | 2 +- src/tower.cxx | 3 +- 10 files changed, 345 insertions(+), 702 deletions(-) delete mode 100644 src/co/coroutine3.hxx diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index d70894c..fe69004 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -1,36 +1,15 @@ -/* - * coroutine.hxx - * Copyright 2025 ItJustWorksTM - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - #pragma once -#include -#include #include #include -#include -#include #include #include -#include "received.hxx" +#include +#include "co/received.hxx" namespace fastipc::co { -template +template class Receiver { public: Receiver() = default; @@ -43,345 +22,218 @@ class Receiver { virtual ~Receiver() = default; - virtual void set_value(T value) noexcept = 0; - virtual void set_exception(std::exception_ptr error) noexcept = 0; - virtual void set_stopped() noexcept = 0; -}; - -template <> -class Receiver { - public: - Receiver() = default; - - Receiver(Receiver&&) noexcept = default; - Receiver& operator=(Receiver&&) noexcept = default; - - Receiver(const Receiver&) noexcept = default; - Receiver& operator=(const Receiver&) noexcept = default; - - virtual ~Receiver() = default; + virtual void set_value(T value) = 0; + virtual void set_exception(std::exception_ptr exc) = 0; + virtual void set_stopped() = 0; - virtual void set_value() noexcept = 0; - virtual void set_exception(std::exception_ptr error) noexcept = 0; - virtual void set_stopped() noexcept = 0; + virtual Env& env() = 0; }; -class Handler { - public: - Handler() = default; - - Handler(Handler&&) noexcept = delete; - Handler& operator=(Handler&&) noexcept = delete; - - Handler(const Handler&) noexcept = delete; - Handler& operator=(const Handler&) noexcept = delete; - - virtual ~Handler() = default; - - virtual void unhandled_exception() noexcept = 0; - virtual void unhandled_stopped() noexcept = 0; +template +struct AwaitedBy { + A value; }; -template -concept valueless = std::is_void_v; - template -class Promise; +class [[nodiscard]] Promise { + public: + class State { -template -class CoAwaiter; + public: + using env_type = Env; -template -class Task; + Promise get_return_object() { return Promise{std::coroutine_handle::from_promise(*this)}; } -template -class EnvAwaiter { - public: - bool await_ready() noexcept { return false; } + std::suspend_always initial_suspend() noexcept { return {}; } - template - std::coroutine_handle<> await_suspend(std::coroutine_handle> cont) { - m_env = &cont.promise().env(); + struct FinalSuspend { + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle h) noexcept { h.promise().complete(); } + void await_resume() noexcept {} + }; - return cont; - } + FinalSuspend final_suspend() noexcept { return {}; } - const Env& await_resume() const noexcept { return *m_env; } + void return_value(T value) { received.set_value(std::move(value)); } + void unhandled_exception() { received.set_exception(std::current_exception()); } + void unhandled_stopped() { received.set_stopped(); } - private: - const Env* m_env = nullptr; -}; + template + AwaitedBy::State> await_transform(A&& awaitable) { + return {std::forward(awaitable)}; + } -struct GetEnv {}; + Receiver* receiver; -constexpr GetEnv getEnv() noexcept { return {}; } + Env& env() { return receiver->env(); } -template -class [[nodiscard]] Co { - public: - using promise_type = Promise; - using handle_type = std::coroutine_handle; + private: + // sadly we need to buffer the received value to allow final_suspend to run + void complete() { std::move(received).forward(*receiver); } - explicit Co(handle_type handle) : m_handle{std::move(handle)} {} + Received received; + }; - Co(Co&) noexcept = delete; - Co& operator=(Co&) noexcept = delete; + explicit Promise(std::coroutine_handle handle) : m_handle{std::move(handle)} {} - Co(Co&& it) noexcept : m_handle{std::exchange(it.m_handle, {})} {} - Co& operator=(Co&& rhs) noexcept { - auto other = Co{std::move(rhs)}; + Promise(Promise&) noexcept = delete; + Promise& operator=(Promise&) noexcept = delete; + + Promise(Promise&& it) noexcept : m_handle{std::exchange(it.m_handle, {})} {} + Promise& operator=(Promise&& rhs) noexcept { + auto other = Promise{std::move(rhs)}; std::swap(m_handle, other.m_handle); return *this; } - ~Co() noexcept { + ~Promise() noexcept { if (m_handle) { m_handle.destroy(); } } - void env(const Env* env) { m_handle.promise().m_env = env; } - - CoAwaiter operator co_await() && noexcept; + std::coroutine_handle handle() { return m_handle; } + std::coroutine_handle handle() const { return m_handle; } private: - friend class CoAwaiter; - friend class Promise; - friend class Task; - - handle_type m_handle; + std::coroutine_handle m_handle; }; -template -class FinalAwaiter { - public: - bool await_ready() noexcept { return false; } +template +class SenderAwaiter { - std::coroutine_handle<> await_suspend(std::coroutine_handle> completed) { - assert(completed.promise().m_received.has_value()); - auto& promise = completed.promise(); - - std::move(promise.m_received).forward(*promise.m_cont); - - return std::noop_coroutine(); - } - - void await_resume() noexcept {} -}; - -template -class Promise : public Handler { public: - Promise() = default; - - Promise(Promise&&) noexcept = delete; - Promise& operator=(Promise&&) noexcept = delete; + using sender_type = S; + using value_type = typename sender_type::template value_type; - Promise(const Promise&) noexcept = delete; - Promise& operator=(const Promise&) noexcept = delete; + explicit SenderAwaiter(sender_type sender) : state{std::move(sender)} {} - ~Promise() noexcept override = default; - - Co get_return_object() { return Co{std::coroutine_handle::from_promise(*this)}; } - - std::suspend_always initial_suspend() noexcept { return {}; } + bool await_ready() noexcept { return false; } - FinalAwaiter final_suspend() noexcept { - assert(m_received.has_value()); - return {}; - } + std::coroutine_handle<> await_suspend(std::coroutine_handle

cont) { + // TODO: somehow operation state needs to be moveable.. + auto& operation_state = state.template emplace(std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); - void return_value(T value) noexcept { m_received.set_value(std::move(value)); } - void unhandled_exception() noexcept override { m_received.set_exception(std::current_exception()); } - void unhandled_stopped() noexcept override { m_received.set_stopped(); } + operation_state.start(); - template - decltype(auto) await_transform(A&& awaitable) noexcept { - return std::forward(awaitable); + return std::noop_coroutine(); } - EnvAwaiter await_transform(GetEnv) { return EnvAwaiter{}; } - - const Env& env() const { return *m_env; } - std::stop_token stop_token() { return m_env->stop_token; } + [[nodiscard]] value_type await_resume() noexcept { return std::move(received).consume(); } private: - friend class Co; - friend class CoAwaiter; - friend class FinalAwaiter; - friend class Task; - - Received m_received; + class AwaiterReceiver { + public: + AwaiterReceiver(Received& received, std::coroutine_handle

awaiter) + : m_received{&received}, m_awaiter{awaiter} {} - Receiver* m_cont = nullptr; + void set_value(value_type value) { + m_received->set_value(std::move(value)); - const Env* m_env; -}; - -template -class Promise : public Handler { - public: - Promise() = default; - - Promise(Promise&&) noexcept = delete; - Promise& operator=(Promise&&) noexcept = delete; - - Promise(const Promise&) noexcept = delete; - Promise& operator=(const Promise&) noexcept = delete; - - ~Promise() noexcept override = default; - - Co get_return_object() { return Co{std::coroutine_handle::from_promise(*this)}; } - - std::suspend_always initial_suspend() noexcept { return {}; } + m_awaiter.resume(); + } - FinalAwaiter final_suspend() noexcept { - assert(m_received.has_value()); - return {}; - } + void set_exception(std::exception_ptr ptr) { + m_received->set_exception(ptr); - // So what if we call the receiver directly? why cache it? - void return_void() noexcept { m_received.set_value(); } - void unhandled_exception() noexcept override { m_received.set_exception(std::current_exception()); } - void unhandled_stopped() noexcept override { - // this does not trigger final_suspend, so that means we cant rely on it... - m_received.set_stopped(); } + m_awaiter.resume(); + } - template - decltype(auto) await_transform(A&& awaitable) noexcept { - return std::forward(awaitable); - } + void set_stopped() { + m_received->set_stopped(); - EnvAwaiter await_transform(GetEnv) { return EnvAwaiter{}; } + m_awaiter.promise().unhandled_stopped(); + } - const Env& env() const { return *m_env; } - std::stop_token stop_token() { return m_env->stop_token; } + auto& env() { return m_awaiter.promise().env(); } - private: - friend class Co; - friend class CoAwaiter; - friend class FinalAwaiter; - friend class Task; + private: + Received* m_received; + std::coroutine_handle

m_awaiter; + }; - Received m_received; - Receiver* m_cont = nullptr; + using operation_state_type = decltype(std::declval().connect(std::declval())); - const Env* m_env; + std::variant state; + Received received; }; +// todo sender concept +template +SenderAwaiter operator co_await(AwaitedBy&& awaited_by) { + return SenderAwaiter{std::move(awaited_by).value}; +} + template -class CoReceiver : public Receiver { +class [[nodiscard]] Co { + public: - CoReceiver() : Receiver{} {} - CoReceiver(std::coroutine_handle<> cont, const Env* env) : m_cont{cont}, m_env{env} {} + using promise_type = Promise::State; - void set_value(T value) noexcept override { - m_received.set_value(std::move(value)); + template + using value_type = T; - m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); - }; + explicit(false) Co(Promise promise) : m_promise{std::move(promise)} {} - void set_exception(std::exception_ptr error) noexcept override { - m_received.set_exception(error); + template + auto connect(R&& receiver) && { + class OperationState { - m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); - }; + public: + OperationState(Promise promise, R receiver) + : m_receiver{std::move(receiver)}, m_promise{std::move(promise)} {} - // these also need to be scheduled... - void set_stopped() noexcept override { m_received.set_stopped(); }; + void start() { + m_promise.handle().promise().receiver = &m_receiver; - T resume() { - assert(m_received.has_value()); + // TODO: schedule it + m_promise.handle().resume(); + } - return std::move(m_received).consume(); - } + private: + class PromiseReceiver : public Receiver { - private: - Received m_received; - std::coroutine_handle<> m_cont; - const Env* m_env; -}; + public: + explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} -template -class CoReceiver : public Receiver { - public: - CoReceiver() : Receiver{} {} - CoReceiver(std::coroutine_handle<> cont, const Env* env) : m_cont{cont}, m_env{env} {} + void set_value(T value) override { m_receiver.set_value(std::move(value)); } + void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } + void set_stopped() override { m_receiver.set_stopped(); } - void set_value() noexcept override { - m_received.set_value(); + Env& env() override { return m_receiver.env(); } - m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); - }; + private: + R m_receiver; + }; - void set_exception(std::exception_ptr error) noexcept override { - m_received.set_exception(error); + PromiseReceiver m_receiver; + Promise m_promise; + }; - m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); - }; - - void set_stopped() noexcept override { - m_received.set_stopped(); - - // TODO: also needs to be scheduled... - assert(false); - }; - - void resume() { - assert(m_received.has_value()); - std::move(m_received).consume(); + return OperationState{std::move(*this).m_promise, std::forward(receiver)}; } private: - Received m_received; - std::coroutine_handle<> m_cont; - const Env* m_env; + Promise m_promise; }; -template -class [[nodiscard]] CoAwaiter { - public: - explicit CoAwaiter(Co co) noexcept : m_co{std::move(co)} {} - - CoAwaiter(CoAwaiter&&) noexcept = delete; - CoAwaiter& operator=(CoAwaiter&&) noexcept = delete; - - CoAwaiter(const CoAwaiter&) noexcept = delete; - CoAwaiter& operator=(const CoAwaiter&) noexcept = delete; +struct EnvSender { + template + using value_type = Env; - ~CoAwaiter() noexcept = default; + template + struct OperationState { + R receiver; - bool await_ready() noexcept { return false; } - - template - std::coroutine_handle<> await_suspend(std::coroutine_handle> cont) { - const auto env = &cont.promise().env(); - - m_co.env(env); - - m_receiver = {cont, env}; - m_co.m_handle.promise().m_cont = &m_receiver; - - // TODO: move to scheduler - return m_co.m_handle; - } - - T await_resume() { - auto _ = std::move(m_co); + void start() { receiver.set_value(receiver.env()); } + }; - return m_receiver.resume(); + template + OperationState connect(R&& receiver) { + return {std::forward(receiver)}; } - - private: - Co m_co; - CoReceiver m_receiver; }; -template -CoAwaiter Co::operator co_await() && noexcept { - return CoAwaiter{std::move(*this)}; -} - } // namespace fastipc::co diff --git a/src/co/coroutine3.hxx b/src/co/coroutine3.hxx deleted file mode 100644 index 3ea383a..0000000 --- a/src/co/coroutine3.hxx +++ /dev/null @@ -1,238 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "co/received.hxx" - -namespace fastipc::co { - -template -class Receiver { - public: - Receiver() = default; - - Receiver(Receiver&&) noexcept = default; - Receiver& operator=(Receiver&&) noexcept = default; - - Receiver(const Receiver&) noexcept = default; - Receiver& operator=(const Receiver&) noexcept = default; - - virtual ~Receiver() = default; - - virtual void set_value(T value) = 0; - virtual void set_exception(std::exception_ptr exc) = 0; - virtual void set_stopped() = 0; - - virtual Env& env() = 0; -}; - -template -struct AwaitedBy { - A value; -}; - -template -class [[nodiscard]] Promise { - public: - class State { - - public: - using env_type = Env; - - Promise get_return_object() { return Promise{std::coroutine_handle::from_promise(*this)}; } - - std::suspend_always initial_suspend() noexcept { return {}; } - - struct FinalSuspend { - bool await_ready() noexcept { return false; } - void await_suspend(std::coroutine_handle h) noexcept { h.promise().complete(); } - void await_resume() noexcept {} - }; - - FinalSuspend final_suspend() noexcept { return {}; } - - void return_value(T value) { received.set_value(std::move(value)); } - void unhandled_exception() { received.set_exception(std::current_exception()); } - void unhandled_stopped() { received.set_stopped(); } - - template - AwaitedBy::State> await_transform(A&& awaitable) { - return {std::forward(awaitable)}; - } - - Receiver* receiver; - - Env& env() { return receiver->env(); } - - private: - // sadly we need to buffer the received value to allow final_suspend to run - void complete() { std::move(received).forward(*receiver); } - - Received received; - }; - - explicit Promise(std::coroutine_handle handle) : m_handle{std::move(handle)} {} - - Promise(Promise&) noexcept = delete; - Promise& operator=(Promise&) noexcept = delete; - - Promise(Promise&& it) noexcept : m_handle{std::exchange(it.m_handle, {})} {} - Promise& operator=(Promise&& rhs) noexcept { - auto other = Promise{std::move(rhs)}; - - std::swap(m_handle, other.m_handle); - - return *this; - } - - ~Promise() noexcept { - if (m_handle) { - m_handle.destroy(); - } - } - - std::coroutine_handle handle() { return m_handle; } - std::coroutine_handle handle() const { return m_handle; } - - private: - std::coroutine_handle m_handle; -}; - -template -class SenderAwaiter { - - public: - using sender_type = S; - using value_type = typename sender_type::template value_type; - - explicit SenderAwaiter(sender_type sender) : state{std::move(sender)} {} - - bool await_ready() noexcept { return false; } - - std::coroutine_handle<> await_suspend(std::coroutine_handle

cont) { - auto& operation_state = state.template emplace( - std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); - - operation_state.start(); - - return std::noop_coroutine(); - } - - [[nodiscard]] value_type await_resume() noexcept { return std::move(received).consume(); } - - private: - class AwaiterReceiver { - public: - AwaiterReceiver(Received& received, std::coroutine_handle

awaiter) - : m_received{&received}, m_awaiter{awaiter} {} - - void set_value(value_type value) { - m_received->set_value(std::move(value)); - - m_awaiter.resume(); - } - - void set_exception(std::exception_ptr ptr) { - m_received->set_exception(ptr); - - m_awaiter.resume(); - } - - void set_stopped() { - m_received->set_stopped(); - - m_awaiter.promise().unhandled_stopped(); - } - - auto& env() { return m_awaiter.promise().env(); } - - private: - Received* m_received; - std::coroutine_handle

m_awaiter; - }; - - using operation_state_type = decltype(std::declval().connect(std::declval())); - - std::variant state; - Received received; -}; - -// todo sender concept -template -SenderAwaiter operator co_await(AwaitedBy&& awaited_by) { - return SenderAwaiter{std::move(awaited_by).value}; -} - -template -class [[nodiscard]] Co { - - public: - using promise_type = Promise::State; - - template - using value_type = T; - - explicit(false) Co(Promise promise) : m_promise{std::move(promise)} {} - - template - auto connect(R&& receiver) && { - class OperationState { - - public: - OperationState(Promise promise, R receiver) - : m_receiver{std::move(receiver)}, m_promise{std::move(promise)} {} - - void start() { - m_promise.handle().promise().receiver = &m_receiver; - - // TODO: schedule it - m_promise.handle().resume(); - } - - private: - class PromiseReceiver : public Receiver { - - public: - explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} - - void set_value(T value) override { m_receiver.set_value(std::move(value)); } - void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } - void set_stopped() override { m_receiver.set_stopped(); } - - Env& env() override { return m_receiver.env(); } - - private: - R m_receiver; - }; - - PromiseReceiver m_receiver; - Promise m_promise; - }; - - return OperationState{std::move(*this).m_promise, std::forward(receiver)}; - } - - private: - Promise m_promise; -}; - -struct EnvSender { - template - using value_type = Env; - - template - struct OperationState { - R receiver; - - void start() { receiver.set_value(receiver.env()); } - }; - - template - OperationState connect(R&& receiver) { - return {std::forward(receiver)}; - } -}; - -} // namespace fastipc::co diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx index 45f44df..8bb0b78 100644 --- a/src/co/scheduler.hxx +++ b/src/co/scheduler.hxx @@ -28,7 +28,7 @@ namespace fastipc::co { class Scheduler final { public: // TODO: use std::function_ref - void schedule(std::function fn) { + void schedule(std::move_only_function fn) { auto lock = std::scoped_lock{m_child_lock}; m_queue.push(std::move(fn)); @@ -53,7 +53,7 @@ class Scheduler final { private: mutable std::recursive_mutex m_child_lock; - std::queue> m_queue; + std::queue> m_queue; }; } // namespace fastipc::co \ No newline at end of file diff --git a/src/co/task.hxx b/src/co/task.hxx index d4c9749..86035d8 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -31,95 +31,95 @@ namespace fastipc::co { struct Unit final {}; -template -class Task : public std::enable_shared_from_this> { - public: - Task(Co co, const Env& env) : m_stop_cb{m_stop_source.get_token(), Canary{}}, m_co_run{std::move(co)}, m_env{env} { - // think about if we want to propagate the stop request from parent env - m_env.stop_token = m_stop_source.get_token(); - } +// template +// class Task : public std::enable_shared_from_this> { +// public: +// Task(Co co, const Env& env) : m_stop_cb{m_stop_source.get_token(), Canary{}}, m_co_run{std::move(co)}, m_env{env} { +// // think about if we want to propagate the stop request from parent env +// m_env.stop_token = m_stop_source.get_token(); +// } - Task(const Task&) = delete; - Task& operator=(const Task&) = delete; +// Task(const Task&) = delete; +// Task& operator=(const Task&) = delete; - Task(Task&&) = default; - Task& operator=(Task&&) = default; +// Task(Task&&) = default; +// Task& operator=(Task&&) = default; - [[nodiscard]] bool completed() const noexcept { return m_done; } +// [[nodiscard]] bool completed() const noexcept { return m_done; } - void abort() { - m_stop_source.request_stop(); - } +// void abort() { +// m_stop_source.request_stop(); +// } - ~Task() noexcept = default; +// ~Task() noexcept = default; - void start() { - auto& promise = m_co_run.m_handle.promise(); +// void start() { +// auto& promise = m_co_run.m_handle.promise(); - promise.m_env = &m_env; +// promise.m_env = &m_env; - m_op = Op{std::enable_shared_from_this::shared_from_this()}; - promise.m_cont = &m_op.value(); +// m_op = Op{std::enable_shared_from_this::shared_from_this()}; +// promise.m_cont = &m_op.value(); - m_env.scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); - } +// m_env.scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); +// } - private: - template - friend class JoinHandle; +// private: +// template +// friend class JoinHandle; - struct Op : public Receiver { - std::shared_ptr m_self; +// struct Op : public Receiver { +// std::shared_ptr m_self; - explicit Op(std::shared_ptr self) : Receiver{}, m_self{self} {} +// explicit Op(std::shared_ptr self) : Receiver{}, m_self{self} {} - void set_value(T value) noexcept override { - assert(m_self); +// void set_value(T value) noexcept override { +// assert(m_self); - auto self = std::move(m_self); - self->m_done = true; - self->m_value = std::move(value); +// auto self = std::move(m_self); +// self->m_done = true; +// self->m_value = std::move(value); - if (self->m_cont) { - self->m_env.scheduler->schedule([cont = self->m_cont]() { cont.resume(); }); - } - } +// if (self->m_cont) { +// self->m_env.scheduler->schedule([cont = self->m_cont]() { cont.resume(); }); +// } +// } - void set_exception(std::exception_ptr exc) noexcept override { - auto self = std::move(m_self); - self->m_done = true; +// void set_exception(std::exception_ptr exc) noexcept override { +// auto self = std::move(m_self); +// self->m_done = true; - std::rethrow_exception(std::move(exc)); // wrong - } +// std::rethrow_exception(std::move(exc)); // wrong +// } - void set_stopped() noexcept override { - auto self = std::move(m_self); - self->m_done = true; +// void set_stopped() noexcept override { +// auto self = std::move(m_self); +// self->m_done = true; - std::terminate(); - } - }; +// std::terminate(); +// } +// }; - struct Canary { - void operator()() { - std::println("task abort requested"); - } - }; +// struct Canary { +// void operator()() { +// std::println("task abort requested"); +// } +// }; - std::stop_source m_stop_source; - std::stop_callback m_stop_cb; - bool m_done = false; - std::optional m_value{}; +// std::stop_source m_stop_source; +// std::stop_callback m_stop_cb; +// bool m_done = false; +// std::optional m_value{}; - std::optional m_op; +// std::optional m_op; - Co m_co_run; - Env m_env; +// Co m_co_run; +// Env m_env; - // I think this also needs to become a Receiver.. - std::coroutine_handle<> m_cont; -}; +// // I think this also needs to become a Receiver.. +// std::coroutine_handle<> m_cont; +// }; // template // class Task final : public Receiver, public std::enable_shared_from_this> { @@ -183,36 +183,36 @@ class Task : public std::enable_shared_from_this> { // std::shared_ptr m_lifetime; // }; -template -class JoinHandle final { +// template +// class JoinHandle final { - public: - explicit JoinHandle(std::shared_ptr> task) noexcept : m_task{std::move(task)} {} +// public: +// explicit JoinHandle(std::shared_ptr> task) noexcept : m_task{std::move(task)} {} - [[nodiscard]] bool completed() const noexcept { return m_task->completed(); } - void abort() noexcept { m_task->abort(); } +// [[nodiscard]] bool completed() const noexcept { return m_task->completed(); } +// void abort() noexcept { m_task->abort(); } - bool await_ready() noexcept { return completed(); } +// bool await_ready() noexcept { return completed(); } - void await_suspend(std::coroutine_handle<> cont) { m_task->m_cont = cont; } +// void await_suspend(std::coroutine_handle<> cont) { m_task->m_cont = cont; } - T await_resume() { return std::move(m_task->m_value).value(); } +// T await_resume() { return std::move(m_task->m_value).value(); } - private: - std::shared_ptr> m_task; -}; +// private: +// std::shared_ptr> m_task; +// }; -template -JoinHandle spawn(Co co, const Env& env) { - auto task = std::make_shared>(std::move(co), env); - task->start(); +// template +// JoinHandle spawn(Co co, const Env& env) { +// auto task = std::make_shared>(std::move(co), env); +// task->start(); - return JoinHandle{std::move(task)}; -} +// return JoinHandle{std::move(task)}; +// } -template -Co, Env> spawn(Co co) { - co_return spawn(std::move(co), co_await getEnv()); -} +// template +// Co, Env> spawn(Co co) { +// co_return spawn(std::move(co), co_await EnvSender{}); +// } } // namespace fastipc::co \ No newline at end of file diff --git a/src/io/context.hxx b/src/io/context.hxx index 2505ce0..114b5f5 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -6,6 +6,20 @@ namespace fastipc::io { + +struct Receiver { + template + void set_value(T value) { + std::println("complete: {}", value); + } + void set_exception(std::exception_ptr) { std::println("oops exception"); } + void set_stopped() { std::println("oops stopped"); } + + Env& env() { return *m_env; } + + Env* m_env; +}; + template void context(F func) { auto scheduler = co::Scheduler{}; @@ -13,7 +27,9 @@ void context(F func) { Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; - auto task = co::spawn(func(), env); + auto op = func().connect(Receiver{&env}); + + op.start(); while (scheduler.can_run()) { while (scheduler.can_run()) { @@ -24,7 +40,7 @@ void context(F func) { expect(reactor.react({}), "failed to react to io events"); } - static_cast(task); + static_cast(op); } } // namespace fastipc::io \ No newline at end of file diff --git a/src/io/io_env.hxx b/src/io/io_env.hxx index c8f0982..ff6809f 100644 --- a/src/io/io_env.hxx +++ b/src/io/io_env.hxx @@ -18,6 +18,7 @@ #pragma once +#include #include "co/coroutine.hxx" #include "co/scheduler.hxx" #include "reactor.hxx" diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 2edbd5f..073bde9 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -19,9 +19,7 @@ #pragma once #include -#include -#include -#include +#include #include #include "co/coroutine.hxx" #include "io/io_env.hxx" @@ -48,7 +46,7 @@ class PolledFd final { } static Co> create(Fd fd) noexcept { - auto& reactor = *(co_await co::getEnv()).reactor; + auto& reactor = *(co_await co::EnvSender{}).reactor; co_return setBlocking(fd, false) .and_then([&]() { return reactor.registerFd(fd); }) @@ -59,7 +57,7 @@ class PolledFd final { private: template - friend class TryIoAwaiter; + friend class TryIoSender; PolledFd(Fd fd, Reactor::Registration* registration, Reactor& reactor) noexcept : m_fd{std::move(fd)}, m_registration{registration}, m_reactor{&reactor} {} @@ -70,127 +68,140 @@ class PolledFd final { }; template -class TryIoAwaiter final { +class TryIoSender final { + public: - using value_type = std::invoke_result_t; + using result_type = std::invoke_result_t; - explicit TryIoAwaiter(const io::PolledFd& fd, io::Direction direction, F io) - : m_fd{&fd}, m_direction{direction}, m_io{std::move(io)} {} + template + using value_type = result_type; - bool await_ready() noexcept { return false; } + explicit TryIoSender(const io::PolledFd& fd, io::Direction direction, F io) + : m_fd{&fd}, m_direction{direction}, m_io{std::move(io)} {} - template - void await_suspend(std::coroutine_handle> cont) { - m_cont = cont; - m_env = &cont.promise().env(); + template + class OperationState { + public: + OperationState(R&& receiver, const io::PolledFd& fd, io::Direction direction, F io) + : m_fd{&fd}, m_direction{direction}, m_io{std::move(io)}, m_receiver{std::move(receiver)} {} - std::stop_token st = cont.promise().stop_token(); + OperationState(const OperationState&) noexcept = delete; + OperationState& operator=(const OperationState&) noexcept = delete; - if (st.stop_requested()) { + OperationState(OperationState&& it) noexcept = default; + OperationState& operator=(OperationState&& rhs) noexcept = default; - set_stopped(); - return; - } + ~OperationState() = default; - m_stop_fn.emplace(std::move(st), StopFn{this}); + void start() { + std::stop_token st = m_receiver.env().stop_token; - schedule_poll(); - } + if (st.stop_requested()) { + set_stopped(); + return; + } - value_type await_resume() noexcept { return std::move(m_value).value(); } + m_stop_fn = std::make_unique>(std::move(st), StopFn{this}); - private: - void poll() { - if (stop_requested) { - std::println("stop request so we set_stopped!"); - set_stopped(); - return; + schedule_poll(); } - // POSSIBLE RACE CONDITION - // stop_request set to true, but in flight, set new reactor callback, ignore stop.. + private: + void poll() { + if (stop_requested) { + set_stopped(); + return; + } - auto res = m_io(); + // POSSIBLE RACE CONDITION + // stop_request set to true, but in flight, set new reactor callback, ignore stop.. - if (!res.has_value()) { - if (res.error() == std::errc::operation_would_block || - res.error() == std::errc::resource_unavailable_try_again) { + auto res = m_io(); - state = State::Blocked; - m_fd->m_registration->callback(m_direction, [this]() { schedule_poll(); }); + if (!res.has_value()) { + if (res.error() == std::errc::operation_would_block || + res.error() == std::errc::resource_unavailable_try_again) { - return; - } - } + state = State::Blocked; + m_fd->m_registration->callback(m_direction, [this]() { schedule_poll(); }); - set_value(std::move(res)); - } + return; + } + } - void set_value(value_type res) { - m_stop_fn.reset(); + set_value(std::move(res)); + } - m_value = std::move(res); + void set_value(result_type res) { + m_stop_fn.reset(); - state = State::Done; - m_env->scheduler->schedule([&]() { m_cont.resume(); }); - } + state = State::Done; + m_receiver.env().scheduler->schedule( + [&, value = std::move(res)]() mutable { m_receiver.set_value(std::move(value)); }); + } - void set_stopped() { - m_stop_fn.reset(); + void set_stopped() { + m_stop_fn.reset(); - state = State::Done; + state = State::Done; - // TODO: propagate, use adapter for that? - std::println("set_stopped was called on TryIoAwaiter"); - assert(false); + m_receiver.env().scheduler->schedule([&]() { m_receiver.set_stopped(); }); + } - // this is why we want Handler? - m_cont.promise().unhandled_stopped(); - } + void schedule_poll() noexcept { + if (state != State::Blocked) { + return; + } - void schedule_poll() noexcept { - if (state != State::Blocked) { - return; + state = State::Flight; + m_receiver.env().scheduler->schedule([this]() { poll(); }); } - state = State::Flight; - m_env->scheduler->schedule([this]() { poll(); }); - } + const io::PolledFd* m_fd; + io::Direction m_direction; + F m_io; - const io::PolledFd* m_fd; - io::Direction m_direction; - F m_io; + R m_receiver; - enum class State : std::uint8_t { - Blocked, - Flight, - Done, - }; + enum class State : std::uint8_t { + Blocked, + Flight, + Done, + }; - State state = State::Blocked; - bool stop_requested = false; + State state = State::Blocked; + bool stop_requested = false; - const Env* m_env = nullptr; - std::optional m_value = {}; - std::coroutine_handle<> m_cont; + struct StopFn final { + OperationState* self; - struct StopFn final { - TryIoAwaiter* self; + void operator()() noexcept { + self->stop_requested = true; + self->m_fd->m_registration->callback(self->m_direction, {}); - void operator()() noexcept { - self->stop_requested = true; - self->m_fd->m_registration->callback(self->m_direction, {}); - // need to interrupt the reactor, idk if that should be done here though... - self->m_fd->m_reactor->interrupt(); - } + // TODO: need to interrupt the reactor, idk if that should be done here though... + expect(self->m_fd->m_reactor->interrupt(), "failed to interrupt reactor"); + } + }; + + // cb is not moveable.. + std::unique_ptr> m_stop_fn{}; }; - std::optional> m_stop_fn; + template + OperationState connect(R&& receiver) && { + return OperationState{std::forward(receiver), *m_fd, m_direction, std::move(m_io)}; + } + + private: + const io::PolledFd* m_fd; + io::Direction m_direction; + F m_io; }; inline Co> accept(PolledFd& fd) { - auto accepted_fd_res = co_await io::TryIoAwaiter{fd, io::Direction::Read, - [&]() { return adoptSysFd(::accept(fd.fd(), nullptr, nullptr)); }}; + auto accepted_fd_res = co_await io::TryIoSender{fd, io::Direction::Read, + [&]() { return adoptSysFd(::accept(fd.fd(), nullptr, nullptr)); }}; if (!accepted_fd_res.has_value()) { co_return unexpected{accepted_fd_res.error()}; diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index 73cd547..df8b4e2 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -43,10 +43,10 @@ class Reactor final { public: struct Registration { int fd; - std::function read_cb; - std::function write_cb; + std::move_only_function read_cb; + std::move_only_function write_cb; - void callback(io::Direction direction, std::function cb) noexcept { + void callback(io::Direction direction, std::move_only_function cb) noexcept { // could make thread safe if we want to interrupt from different threads... auto old = std::exchange(direction == io::Direction::Read ? read_cb : write_cb, std::move(cb)); diff --git a/src/main.cxx b/src/main.cxx index 3220f12..559eff4 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -25,7 +25,7 @@ namespace { io::Co main() { auto tower = co_await fastipc::Tower::create("fastipcd"); - co_await tower.run(); + static_cast(co_await tower.run()); co_return 0; } diff --git a/src/tower.cxx b/src/tower.cxx index 4e05fdb..729f15c 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -129,7 +129,8 @@ io::Co Tower::run() { auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); // by detaching we have no way of shutting clients down - co_await co::spawn(serve(std::move(clientfd))); + // co_await co::spawn(serve(std::move(clientfd))); + static_cast(clientfd); } co_return 0; // lazy void From b479449def46c3b1235f9ea13e2884cb57b76e34 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Mon, 6 Oct 2025 20:49:01 +0200 Subject: [PATCH 08/41] Add basic spawn code, needs more --- src/co/task.hxx | 207 +++++++++++++++++++++++++++++++----------- src/io/context.hxx | 14 ++- src/tower.cxx | 2 +- test/intraprocess.cxx | 7 +- 4 files changed, 169 insertions(+), 61 deletions(-) diff --git a/src/co/task.hxx b/src/co/task.hxx index 86035d8..c6c3932 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -21,10 +21,14 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include "co/received.hxx" #include "coroutine.hxx" namespace fastipc::co { @@ -34,7 +38,7 @@ struct Unit final {}; // template // class Task : public std::enable_shared_from_this> { // public: -// Task(Co co, const Env& env) : m_stop_cb{m_stop_source.get_token(), Canary{}}, m_co_run{std::move(co)}, m_env{env} { +// Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{env} { // // think about if we want to propagate the stop request from parent env // m_env.stop_token = m_stop_source.get_token(); // } @@ -53,72 +57,63 @@ struct Unit final {}; // ~Task() noexcept = default; -// void start() { -// auto& promise = m_co_run.m_handle.promise(); +// void start() { +// auto& promise = m_co_run.m_handle.promise(); -// promise.m_env = &m_env; +// promise.m_env = &m_env; -// m_op = Op{std::enable_shared_from_this::shared_from_this()}; -// promise.m_cont = &m_op.value(); +// m_op = Op{std::enable_shared_from_this::shared_from_this()}; +// promise.m_cont = &m_op.value(); -// m_env.scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); -// } +// m_env.scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); +// } -// private: -// template -// friend class JoinHandle; +// private: +// template +// friend class JoinHandle; -// struct Op : public Receiver { -// std::shared_ptr m_self; +// struct Op : public Receiver { +// std::shared_ptr m_self; -// explicit Op(std::shared_ptr self) : Receiver{}, m_self{self} {} +// explicit Op(std::shared_ptr self) : Receiver{}, m_self{self} {} -// void set_value(T value) noexcept override { -// assert(m_self); +// void set_value(T value) noexcept override { +// assert(m_self); -// auto self = std::move(m_self); -// self->m_done = true; -// self->m_value = std::move(value); +// auto self = std::move(m_self); +// self->m_done = true; +// self->m_value = std::move(value); -// if (self->m_cont) { -// self->m_env.scheduler->schedule([cont = self->m_cont]() { cont.resume(); }); -// } +// if (self->m_cont) { +// self->m_env.scheduler->schedule([cont = self->m_cont]() { cont.resume(); }); // } +// } -// void set_exception(std::exception_ptr exc) noexcept override { -// auto self = std::move(m_self); -// self->m_done = true; +// void set_exception(std::exception_ptr exc) noexcept override { +// auto self = std::move(m_self); +// self->m_done = true; -// std::rethrow_exception(std::move(exc)); // wrong -// } +// std::rethrow_exception(std::move(exc)); // wrong +// } -// void set_stopped() noexcept override { -// auto self = std::move(m_self); -// self->m_done = true; +// void set_stopped() noexcept override { +// auto self = std::move(m_self); +// self->m_done = true; -// std::terminate(); -// } -// }; +// std::terminate(); +// } +// }; -// struct Canary { -// void operator()() { -// std::println("task abort requested"); -// } -// }; +// struct Canary { +// void operator()() { +// std::println("task abort requested"); +// } +// }; -// std::stop_source m_stop_source; -// std::stop_callback m_stop_cb; // bool m_done = false; -// std::optional m_value{}; - -// std::optional m_op; +// std::stop_source m_stop_source; -// Co m_co_run; // Env m_env; - - -// // I think this also needs to become a Receiver.. -// std::coroutine_handle<> m_cont; // }; // template @@ -192,12 +187,6 @@ struct Unit final {}; // [[nodiscard]] bool completed() const noexcept { return m_task->completed(); } // void abort() noexcept { m_task->abort(); } -// bool await_ready() noexcept { return completed(); } - -// void await_suspend(std::coroutine_handle<> cont) { m_task->m_cont = cont; } - -// T await_resume() { return std::move(m_task->m_value).value(); } - // private: // std::shared_ptr> m_task; // }; @@ -215,4 +204,114 @@ struct Unit final {}; // co_return spawn(std::move(co), co_await EnvSender{}); // } +template +struct State { + + State() = default; + State(const State&) = delete; + State& operator=(const State&) = delete; + State(State&&) = delete; + State& operator=(State&&) = delete; + + virtual ~State() = default; + + Received received{}; +}; + +template +struct JoinHandle { + [[nodiscard]] bool completed() const noexcept { return state->received.has_value(); } + + void abort() noexcept { + // TODO + } + + template + using value_type = T; + + template + auto connect(R&& receiver) && { + struct OperationState { + R receiver; + std::shared_ptr> state; + + void start() { + // TODO: put receiver on shared state + // OR check if received is already populated and immediately forward it + } + }; + + return OperationState{std::forward(receiver), std::move(state)}; + } + + // dtor slice? + std::shared_ptr> state; +}; + +template +struct SpawnSender { + + template + using task_value_type = typename S::template value_type; + + template + using value_type = JoinHandle>; + + template + struct OperationState { + R receiver; + S sender; + + using Env = std::remove_cvref_t().env())>; + using T = task_value_type; + + void start() { + + struct StateImpl : State { + explicit StateImpl(Env env) : State{}, env{env} {} + + StateImpl(const StateImpl&) = delete; + StateImpl& operator=(const StateImpl&) = delete; + StateImpl(StateImpl&&) = delete; + StateImpl& operator=(StateImpl&&) = delete; + + ~StateImpl() override = default; + + struct Receiver { + std::shared_ptr state; + + void set_value(T value) { state->received.set_value(std::move(value)); } + void set_exception(std::exception_ptr exc) { state->received.set_exception(std::move(exc)); } + void set_stopped() { state->received.set_stopped(); }; + + Env& env() { return state->env; } + }; + + using operation_state_type = decltype(std::declval().connect(std::declval())); + + Env env; + std::optional operation_state{}; + }; + + // TODO: add stop_source + auto state = std::make_shared(receiver.env()); + state->operation_state.emplace(std::move(sender).connect(typename StateImpl::Receiver{state})).start(); + + receiver.set_value(JoinHandle{std::move(state)}); + } + }; + + template + auto connect(R&& receiver) && { + return OperationState{std::forward(receiver), std::move(sender)}; + } + + S sender; +}; + +template +SpawnSender spawn(S&& sender) { + return SpawnSender{std::forward(sender)}; +} + } // namespace fastipc::co \ No newline at end of file diff --git a/src/io/context.hxx b/src/io/context.hxx index 114b5f5..2d707fb 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,4 +1,8 @@ #pragma once +#include +#include +#include +#include "co/received.hxx" #include "co/scheduler.hxx" #include "co/task.hxx" #include "io_env.hxx" @@ -12,14 +16,15 @@ struct Receiver { void set_value(T value) { std::println("complete: {}", value); } - void set_exception(std::exception_ptr) { std::println("oops exception"); } + void set_exception(std::exception_ptr exc) noexcept { std::rethrow_exception(std::move(exc)); } void set_stopped() { std::println("oops stopped"); } - Env& env() { return *m_env; } + Env& env() { return m_env; } - Env* m_env; + Env m_env; }; + template void context(F func) { auto scheduler = co::Scheduler{}; @@ -27,8 +32,7 @@ void context(F func) { Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; - auto op = func().connect(Receiver{&env}); - + auto op = func().connect(Receiver{env}); op.start(); while (scheduler.can_run()) { diff --git a/src/tower.cxx b/src/tower.cxx index 729f15c..e17e9fc 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -129,7 +129,7 @@ io::Co Tower::run() { auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); // by detaching we have no way of shutting clients down - // co_await co::spawn(serve(std::move(clientfd))); + co_await co::spawn(serve(std::move(clientfd))); static_cast(clientfd); } diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index 58fe41a..a400734 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -36,6 +36,7 @@ fastipc::io::Co co_main() { auto handle = co_await fastipc::co::spawn(tower.run()); auto test = std::jthread{[&] { + std::println("starting test in thead"); constexpr std::string_view channel_name{"Hallowed are the Ori"}; constexpr std::size_t max_payload_size{sizeof(int)}; @@ -43,12 +44,14 @@ fastipc::io::Co co_main() { fastipc::Reader reader{channel_name, max_payload_size}; { + std::println("reading sample"); auto sample = reader.acquire(); assert(sample.getSequenceId() == 0); reader.release(sample); } { + std::println("writing sample"); auto sample = writer.prepare(); assert(sample.getSequenceId() == 1); *static_cast(sample.getPayload()) = 5; // NOLINT(*-magic-numbers) @@ -56,6 +59,7 @@ fastipc::io::Co co_main() { } { + std::println("reading sample"); auto sample = reader.acquire(); assert(sample.getSequenceId() == 1); assert(*static_cast(sample.getPayload()) == 5); @@ -67,7 +71,8 @@ fastipc::io::Co co_main() { handle.abort(); }}; - co_await handle; + std::println("waiting for join"); + co_await std::move(handle); std::println("run done!"); From 72613980122c1e471fd3a47b15cb3cce2837baaa Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Thu, 9 Oct 2025 20:02:22 +0200 Subject: [PATCH 09/41] slowly getting there.. --- src/co/scheduler.hxx | 11 ++- src/co/task.hxx | 175 +----------------------------------------- src/io/context.hxx | 10 ++- src/io/polled_fd.hxx | 7 +- src/io/reactor.cxx | 2 - src/tower.cxx | 5 +- test/intraprocess.cxx | 3 +- 7 files changed, 25 insertions(+), 188 deletions(-) diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx index 8bb0b78..958ce7d 100644 --- a/src/co/scheduler.hxx +++ b/src/co/scheduler.hxx @@ -22,16 +22,23 @@ #include #include #include +#include "io/reactor.hxx" namespace fastipc::co { class Scheduler final { public: - // TODO: use std::function_ref + explicit Scheduler(io::Reactor* reactor = nullptr) : m_reactor(reactor) {} + void schedule(std::move_only_function fn) { auto lock = std::scoped_lock{m_child_lock}; m_queue.push(std::move(fn)); + + // make this smart.. + if (m_reactor != nullptr) { + expect(m_reactor->interrupt(), "failed to interrupt reactor"); + } } [[nodiscard]] bool can_run() const noexcept { @@ -54,6 +61,8 @@ class Scheduler final { private: mutable std::recursive_mutex m_child_lock; std::queue> m_queue; + + io::Reactor* m_reactor; }; } // namespace fastipc::co \ No newline at end of file diff --git a/src/co/task.hxx b/src/co/task.hxx index c6c3932..a328ec6 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -19,191 +19,18 @@ #pragma once #include -#include #include -#include #include #include -#include #include #include #include #include "co/received.hxx" -#include "coroutine.hxx" namespace fastipc::co { struct Unit final {}; -// template -// class Task : public std::enable_shared_from_this> { -// public: -// Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{env} { -// // think about if we want to propagate the stop request from parent env -// m_env.stop_token = m_stop_source.get_token(); -// } - -// Task(const Task&) = delete; -// Task& operator=(const Task&) = delete; - -// Task(Task&&) = default; -// Task& operator=(Task&&) = default; - -// [[nodiscard]] bool completed() const noexcept { return m_done; } - -// void abort() { -// m_stop_source.request_stop(); -// } - -// ~Task() noexcept = default; - -// void start() { -// auto& promise = m_co_run.m_handle.promise(); - -// promise.m_env = &m_env; - -// m_op = Op{std::enable_shared_from_this::shared_from_this()}; -// promise.m_cont = &m_op.value(); - -// m_env.scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); -// } - -// private: -// template -// friend class JoinHandle; - -// struct Op : public Receiver { -// std::shared_ptr m_self; - -// explicit Op(std::shared_ptr self) : Receiver{}, m_self{self} {} - -// void set_value(T value) noexcept override { -// assert(m_self); - -// auto self = std::move(m_self); -// self->m_done = true; -// self->m_value = std::move(value); - -// if (self->m_cont) { -// self->m_env.scheduler->schedule([cont = self->m_cont]() { cont.resume(); }); -// } -// } - -// void set_exception(std::exception_ptr exc) noexcept override { -// auto self = std::move(m_self); -// self->m_done = true; - -// std::rethrow_exception(std::move(exc)); // wrong -// } - -// void set_stopped() noexcept override { -// auto self = std::move(m_self); -// self->m_done = true; - -// std::terminate(); -// } -// }; - -// struct Canary { -// void operator()() { -// std::println("task abort requested"); -// } -// }; - -// bool m_done = false; -// std::stop_source m_stop_source; - -// Env m_env; -// }; - -// template -// class Task final : public Receiver, public std::enable_shared_from_this> { -// public: -// Task(Co co, const Env& env) : m_co_run{std::move(co)}, m_env{&env} {} - -// Task(const Task&) = delete; -// Task& operator=(const Task&) = delete; - -// Task(Task&&) = default; -// Task& operator=(Task&&) = default; - -// [[nodiscard]] bool completed() const noexcept { return m_done; } - -// ~Task() noexcept override = default; - -// void start() { -// auto& promise = m_co_run.m_handle.promise(); - -// promise.m_env = m_env; -// promise.m_cont = this; - -// m_env->scheduler->schedule([this]() { m_co_run.m_handle.resume(); }); - -// m_lifetime = std::enable_shared_from_this::shared_from_this(); -// } - -// void set_value() noexcept override { -// auto _ = std::move(m_lifetime); -// m_done = true; - -// if (m_cont) { -// m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); -// } -// } - -// void set_exception(std::exception_ptr exc) noexcept override { -// auto _ = std::move(m_lifetime); -// m_done = true; -// std::rethrow_exception(std::move(exc)); -// } - -// void set_stopped() noexcept override { -// auto _ = std::move(m_lifetime); -// m_done = true; -// if (m_cont) { -// m_env->scheduler->schedule([cont = m_cont]() { cont.resume(); }); -// } -// } - -// private: -// template -// friend class JoinHandle; - -// Co m_co_run; -// const Env* m_env; - -// bool m_done = false; - -// std::coroutine_handle<> m_cont; -// std::shared_ptr m_lifetime; -// }; - -// template -// class JoinHandle final { - -// public: -// explicit JoinHandle(std::shared_ptr> task) noexcept : m_task{std::move(task)} {} - -// [[nodiscard]] bool completed() const noexcept { return m_task->completed(); } -// void abort() noexcept { m_task->abort(); } - -// private: -// std::shared_ptr> m_task; -// }; - -// template -// JoinHandle spawn(Co co, const Env& env) { -// auto task = std::make_shared>(std::move(co), env); -// task->start(); - -// return JoinHandle{std::move(task)}; -// } - -// template -// Co, Env> spawn(Co co) { -// co_return spawn(std::move(co), co_await EnvSender{}); -// } - template struct State { @@ -226,7 +53,7 @@ struct JoinHandle { // TODO } - template + template using value_type = T; template diff --git a/src/io/context.hxx b/src/io/context.hxx index 2d707fb..2ca2843 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include "co/received.hxx" #include "co/scheduler.hxx" @@ -25,22 +26,25 @@ struct Receiver { }; +// so in p2300 it seems block_on will supply an in-place scheduler +// which we should probably use for the main function, but then we can introduce this i/o env with a simple receiver? + template void context(F func) { - auto scheduler = co::Scheduler{}; auto reactor = expect(Reactor::create()); + auto scheduler = co::Scheduler{&reactor}; Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; auto op = func().connect(Receiver{env}); op.start(); - while (scheduler.can_run()) { + while (true) { while (scheduler.can_run()) { scheduler.run(); expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); } - + expect(reactor.react({}), "failed to react to io events"); } diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 073bde9..0affb5c 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -178,9 +178,6 @@ class TryIoSender final { void operator()() noexcept { self->stop_requested = true; self->m_fd->m_registration->callback(self->m_direction, {}); - - // TODO: need to interrupt the reactor, idk if that should be done here though... - expect(self->m_fd->m_reactor->interrupt(), "failed to interrupt reactor"); } }; @@ -210,4 +207,8 @@ inline Co> accept(PolledFd& fd) { co_return co_await PolledFd::create(std::move(accepted_fd_res).value()); } +[[nodiscard]] inline Co> aread(PolledFd& fd, std::span buf) noexcept { + co_return co_await io::TryIoSender{fd, io::Direction::Read, [&]() { return read(fd, buf); }}; +} + } // namespace fastipc::io diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index f7c9dcc..5c9b16a 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -79,8 +79,6 @@ void Reactor::process(std::span<::epoll_event> events) noexcept { static_cast(res); - std::println("reactor interrupt received"); - continue; } diff --git a/src/tower.cxx b/src/tower.cxx index e17e9fc..6a8e6e9 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -123,13 +123,12 @@ io::Co Tower::run() { if (expected_clientfd.error() == std::errc::connection_aborted) continue; - std::println("warn: {}", expected_clientfd.error().message()); } auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); // by detaching we have no way of shutting clients down - co_await co::spawn(serve(std::move(clientfd))); + static_cast(co_await co::spawn(serve(std::move(clientfd)))); static_cast(clientfd); } @@ -142,7 +141,7 @@ void Tower::shutdown() { io::Co Tower::serve(io::PolledFd clientfd) { std::array buf{}; // NOLINT(*-magic-numbers) - const auto bytes_read = expect(io::read(clientfd, std::span{buf}), "failed to read from client"); + const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}), "failed to read from client"); auto recvbuf = std::span{buf}.first(bytes_read); const auto request = expect(expect(readClientRequest(recvbuf), "invalid request"), "incomplete message"); diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index a400734..e6d8dc3 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -71,8 +71,7 @@ fastipc::io::Co co_main() { handle.abort(); }}; - std::println("waiting for join"); - co_await std::move(handle); + static_cast(co_await std::move(handle)); std::println("run done!"); From 67f1de7d95caca586f9e71f6c79186dd169ed28d Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 20:58:54 +0100 Subject: [PATCH 10/41] Simplify --- CMakeLists.txt | 1 - src/co/coroutine.hxx | 86 ++++++++++++++++------------------- src/co/received.hxx | 12 ++--- src/co/task.hxx | 89 +++++++++++++++++++++++++------------ src/io/context.hxx | 40 ++++++++--------- src/io/io_env.hxx | 40 ----------------- src/io/polled_fd.hxx | 101 +++++++++++++++++++----------------------- src/main.cxx | 7 +-- src/tower.cxx | 10 ++--- src/tower.hxx | 7 ++- test/intraprocess.cxx | 10 ++--- 11 files changed, 184 insertions(+), 219 deletions(-) delete mode 100644 src/io/io_env.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 45bbb8d..45b6ae0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,6 @@ target_sources ( src/io/polled_fd.hxx src/io/reactor.hxx src/io/reactor.cxx - src/io/io_env.hxx src/io/context.hxx src/co/scheduler.hxx src/co/coroutine.hxx diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index fe69004..4d681d4 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -9,7 +9,12 @@ namespace fastipc::co { -template +class StoppedException final : public std::exception { + public: + [[nodiscard]] const char* what() const noexcept override { return "operation stopped"; } +}; + +template class Receiver { public: Receiver() = default; @@ -24,9 +29,23 @@ class Receiver { virtual void set_value(T value) = 0; virtual void set_exception(std::exception_ptr exc) = 0; - virtual void set_stopped() = 0; +}; + +template <> +class Receiver { + public: + Receiver() = default; + + Receiver(Receiver&&) noexcept = default; + Receiver& operator=(Receiver&&) noexcept = default; + + Receiver(const Receiver&) noexcept = default; + Receiver& operator=(const Receiver&) noexcept = default; + + virtual ~Receiver() = default; - virtual Env& env() = 0; + virtual void set_value() = 0; + virtual void set_exception(std::exception_ptr exc) = 0; }; template @@ -34,14 +53,12 @@ struct AwaitedBy { A value; }; -template +template class [[nodiscard]] Promise { public: class State { public: - using env_type = Env; - Promise get_return_object() { return Promise{std::coroutine_handle::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } @@ -56,16 +73,13 @@ class [[nodiscard]] Promise { void return_value(T value) { received.set_value(std::move(value)); } void unhandled_exception() { received.set_exception(std::current_exception()); } - void unhandled_stopped() { received.set_stopped(); } template - AwaitedBy::State> await_transform(A&& awaitable) { + AwaitedBy::State> await_transform(A&& awaitable) { return {std::forward(awaitable)}; } - Receiver* receiver; - - Env& env() { return receiver->env(); } + Receiver* receiver; private: // sadly we need to buffer the received value to allow final_suspend to run @@ -106,7 +120,7 @@ class SenderAwaiter { public: using sender_type = S; - using value_type = typename sender_type::template value_type; + using value_type = sender_type::value_type; explicit SenderAwaiter(sender_type sender) : state{std::move(sender)} {} @@ -114,7 +128,8 @@ class SenderAwaiter { std::coroutine_handle<> await_suspend(std::coroutine_handle

cont) { // TODO: somehow operation state needs to be moveable.. - auto& operation_state = state.template emplace(std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); + auto& operation_state = state.template emplace( + std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); operation_state.start(); @@ -141,14 +156,6 @@ class SenderAwaiter { m_awaiter.resume(); } - void set_stopped() { - m_received->set_stopped(); - - m_awaiter.promise().unhandled_stopped(); - } - - auto& env() { return m_awaiter.promise().env(); } - private: Received* m_received; std::coroutine_handle

m_awaiter; @@ -166,23 +173,22 @@ SenderAwaiter operator co_await(AwaitedBy&& awaited_by) { return SenderAwaiter{std::move(awaited_by).value}; } -template +template class [[nodiscard]] Co { public: - using promise_type = Promise::State; + using promise_type = Promise::State; - template using value_type = T; - explicit(false) Co(Promise promise) : m_promise{std::move(promise)} {} + explicit(false) Co(Promise promise) : m_promise{std::move(promise)} {} template auto connect(R&& receiver) && { class OperationState { public: - OperationState(Promise promise, R receiver) + OperationState(Promise promise, R receiver) : m_receiver{std::move(receiver)}, m_promise{std::move(promise)} {} void start() { @@ -193,47 +199,29 @@ class [[nodiscard]] Co { } private: - class PromiseReceiver : public Receiver { + class PromiseReceiver : public Receiver { public: - explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} + explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} void set_value(T value) override { m_receiver.set_value(std::move(value)); } void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } - void set_stopped() override { m_receiver.set_stopped(); } - - Env& env() override { return m_receiver.env(); } private: R m_receiver; }; PromiseReceiver m_receiver; - Promise m_promise; + Promise m_promise; }; return OperationState{std::move(*this).m_promise, std::forward(receiver)}; } private: - Promise m_promise; + Promise m_promise; }; -struct EnvSender { - template - using value_type = Env; - - template - struct OperationState { - R receiver; - - void start() { receiver.set_value(receiver.env()); } - }; - - template - OperationState connect(R&& receiver) { - return {std::forward(receiver)}; - } -}; +// auto block_on } // namespace fastipc::co diff --git a/src/co/received.hxx b/src/co/received.hxx index c1f3115..6abe36b 100644 --- a/src/co/received.hxx +++ b/src/co/received.hxx @@ -27,14 +27,12 @@ namespace fastipc::co { struct has_value_tag final {}; -struct has_stopped_tag final {}; template struct Received final { void set_value(T value) { m_value = std::move(value); } void set_exception(std::exception_ptr exc) { m_value = std::move(exc); } - void set_stopped() { m_value = has_stopped_tag{}; } [[nodiscard]] bool has_value() const { return !std::holds_alternative(m_value); } @@ -53,14 +51,13 @@ struct Received final { match( std::move(m_value), [&receiver](std::exception_ptr exc) { receiver.set_exception(std::move(exc)); }, [&receiver](T&& value) { receiver.set_value(std::move(value)); }, - [&](has_stopped_tag) { receiver.set_stopped(); }, [](std::monostate) { assert(false); std::unreachable(); }); } - std::variant m_value; + std::variant m_value; }; template <> @@ -68,7 +65,6 @@ struct Received final { void set_value() { m_value = has_value_tag{}; } void set_exception(std::exception_ptr exc) { m_value = std::move(exc); } - void set_stopped() { m_value = has_stopped_tag{}; } [[nodiscard]] bool has_value() const { return !std::holds_alternative(m_value); } @@ -76,7 +72,7 @@ struct Received final { match( std::move(m_value), [](std::exception_ptr exc) -> void { std::rethrow_exception(std::move(exc)); }, [](has_value_tag) {}, - [](auto) { + [](std::monostate) { assert(false); std::unreachable(); }); @@ -86,14 +82,14 @@ struct Received final { void forward(R& receiver) && { return match( std::move(m_value), [&](std::exception_ptr exc) { receiver.set_exception(std::move(exc)); }, - [&](has_value_tag) { receiver.set_value(); }, [&](has_stopped_tag) { receiver.set_stopped(); }, + [&](has_value_tag) { receiver.set_value(); }, [](std::monostate) { assert(false); std::unreachable(); }); } - std::variant m_value; + std::variant m_value; }; } // namespace fastipc::co \ No newline at end of file diff --git a/src/co/task.hxx b/src/co/task.hxx index a328ec6..f3ecab7 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -25,15 +25,30 @@ #include #include #include +#include "co/coroutine.hxx" #include "co/received.hxx" namespace fastipc::co { struct Unit final {}; +class Listener { + public: + Listener() = default; + + Listener(Listener&&) noexcept = default; + Listener& operator=(Listener&&) noexcept = default; + + Listener(const Listener&) noexcept = default; + Listener& operator=(const Listener&) noexcept = default; + + virtual ~Listener() = default; + + virtual void notify() = 0; +}; + template struct State { - State() = default; State(const State&) = delete; State& operator=(const State&) = delete; @@ -43,28 +58,45 @@ struct State { virtual ~State() = default; Received received{}; + Listener* listener{}; }; template -struct JoinHandle { +struct JoinHandle final { [[nodiscard]] bool completed() const noexcept { return state->received.has_value(); } - void abort() noexcept { - // TODO - } - - template using value_type = T; template auto connect(R&& receiver) && { - struct OperationState { + struct OperationState final : public Listener { R receiver; std::shared_ptr> state; + OperationState(R&& receiver, std::shared_ptr> state) + : receiver{std::move(receiver)}, state{std::move(state)} {} + + OperationState(OperationState&&) noexcept = default; + OperationState& operator=(OperationState&&) noexcept = default; + + OperationState(const OperationState&) noexcept = default; + OperationState& operator=(const OperationState&) noexcept = default; + + ~OperationState() override = default; + + void notify() override { + if (state->received.has_value()) { + state->listener = nullptr; + std::move(state->received).forward(receiver); + } + } + void start() { - // TODO: put receiver on shared state - // OR check if received is already populated and immediately forward it + if (state->received.has_value()) { + std::move(state->received).forward(receiver); + } else { + state->listener = this; + } } }; @@ -76,26 +108,20 @@ struct JoinHandle { }; template -struct SpawnSender { - - template - using task_value_type = typename S::template value_type; - - template - using value_type = JoinHandle>; +struct SpawnSender final { + using task_value_type = typename S::value_type; + using value_type = JoinHandle; template struct OperationState { R receiver; S sender; - using Env = std::remove_cvref_t().env())>; - using T = task_value_type; + using T = task_value_type; void start() { - struct StateImpl : State { - explicit StateImpl(Env env) : State{}, env{env} {} + explicit StateImpl() : State{} {} StateImpl(const StateImpl&) = delete; StateImpl& operator=(const StateImpl&) = delete; @@ -107,21 +133,28 @@ struct SpawnSender { struct Receiver { std::shared_ptr state; - void set_value(T value) { state->received.set_value(std::move(value)); } - void set_exception(std::exception_ptr exc) { state->received.set_exception(std::move(exc)); } - void set_stopped() { state->received.set_stopped(); }; + void set_value(T value) { + state->received.set_value(std::move(value)); + + if (state->listener) { + state->listener->notify(); + } + } + void set_exception(std::exception_ptr exc) { + state->received.set_exception(std::move(exc)); - Env& env() { return state->env; } + if (state->listener) { + state->listener->notify(); + } + } }; using operation_state_type = decltype(std::declval().connect(std::declval())); - Env env; std::optional operation_state{}; }; - // TODO: add stop_source - auto state = std::make_shared(receiver.env()); + auto state = std::make_shared(); state->operation_state.emplace(std::move(sender).connect(typename StateImpl::Receiver{state})).start(); receiver.set_value(JoinHandle{std::move(state)}); diff --git a/src/io/context.hxx b/src/io/context.hxx index 2ca2843..3848e73 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -6,7 +6,6 @@ #include "co/received.hxx" #include "co/scheduler.hxx" #include "co/task.hxx" -#include "io_env.hxx" #include "reactor.hxx" namespace fastipc::io { @@ -18,11 +17,6 @@ struct Receiver { std::println("complete: {}", value); } void set_exception(std::exception_ptr exc) noexcept { std::rethrow_exception(std::move(exc)); } - void set_stopped() { std::println("oops stopped"); } - - Env& env() { return m_env; } - - Env m_env; }; @@ -30,25 +24,29 @@ struct Receiver { // which we should probably use for the main function, but then we can introduce this i/o env with a simple receiver? template -void context(F func) { - auto reactor = expect(Reactor::create()); - auto scheduler = co::Scheduler{&reactor}; +void context(F func); + - Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; +// template +// void context(F func) { +// auto reactor = expect(Reactor::create()); +// auto scheduler = co::Scheduler{&reactor}; - auto op = func().connect(Receiver{env}); - op.start(); +// Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; - while (true) { - while (scheduler.can_run()) { - scheduler.run(); - expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); - } +// auto op = func().connect(Receiver{env}); +// op.start(); + +// while (true) { +// while (scheduler.can_run()) { +// scheduler.run(); +// expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); +// } - expect(reactor.react({}), "failed to react to io events"); - } +// expect(reactor.react({}), "failed to react to io events"); +// } - static_cast(op); -} +// static_cast(op); +// } } // namespace fastipc::io \ No newline at end of file diff --git a/src/io/io_env.hxx b/src/io/io_env.hxx deleted file mode 100644 index ff6809f..0000000 --- a/src/io/io_env.hxx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * io_env.hxx - * Copyright 2025 ItJustWorksTM - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#pragma once - -#include -#include "co/coroutine.hxx" -#include "co/scheduler.hxx" -#include "reactor.hxx" - -namespace fastipc::io { - -struct Env final { - co::Scheduler* scheduler; - Reactor* reactor; - std::stop_token stop_token; -}; - -template -using Co = co::Co; - -template -using Promise = co::Promise; - -} // namespace fastipc::io diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 0affb5c..b4360e6 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -19,16 +19,21 @@ #pragma once #include +#include #include #include +#include #include "co/coroutine.hxx" -#include "io/io_env.hxx" #include "fd.hxx" #include "reactor.hxx" #include "result.hxx" namespace fastipc::io { +constexpr bool is_error_blocking(std::error_code error) noexcept { + return error == std::errc::operation_would_block || error == std::errc::resource_unavailable_try_again; +} + class PolledFd final { public: PolledFd(const PolledFd&) noexcept = delete; @@ -45,9 +50,9 @@ class PolledFd final { } } - static Co> create(Fd fd) noexcept { - auto& reactor = *(co_await co::EnvSender{}).reactor; + static co::Co> create(Fd fd) noexcept; + static co::Co> create(Fd fd, Reactor& reactor) noexcept { co_return setBlocking(fd, false) .and_then([&]() { return reactor.registerFd(fd); }) .transform([&](auto* registration) { return PolledFd{std::move(fd), registration, reactor}; }); @@ -73,17 +78,17 @@ class TryIoSender final { public: using result_type = std::invoke_result_t; - template using value_type = result_type; - explicit TryIoSender(const io::PolledFd& fd, io::Direction direction, F io) - : m_fd{&fd}, m_direction{direction}, m_io{std::move(io)} {} + explicit TryIoSender(const io::PolledFd& fd, io::Direction direction, std::stop_token stop_token, F io) + : m_fd{&fd}, m_direction{direction}, m_stop_token{std::move(stop_token)}, m_io{std::move(io)} {} template class OperationState { public: - OperationState(R&& receiver, const io::PolledFd& fd, io::Direction direction, F io) - : m_fd{&fd}, m_direction{direction}, m_io{std::move(io)}, m_receiver{std::move(receiver)} {} + OperationState(R&& receiver, const io::PolledFd& fd, io::Direction direction, std::stop_token stop_token, F io) + : m_fd{&fd}, m_direction{direction}, m_stop_token{std::move(stop_token)}, m_io{std::move(io)}, + m_receiver{std::move(receiver)} {} OperationState(const OperationState&) noexcept = delete; OperationState& operator=(const OperationState&) noexcept = delete; @@ -94,91 +99,75 @@ class TryIoSender final { ~OperationState() = default; void start() { - std::stop_token st = m_receiver.env().stop_token; - - if (st.stop_requested()) { + if (m_stop_token.stop_requested()) { set_stopped(); return; } - m_stop_fn = std::make_unique>(std::move(st), StopFn{this}); - - schedule_poll(); + m_stop_fn = std::make_unique>(m_stop_token, StopFn{this}); + poll(); } private: void poll() { - if (stop_requested) { - set_stopped(); + if (m_state != State::Blocked) { return; } - // POSSIBLE RACE CONDITION - // stop_request set to true, but in flight, set new reactor callback, ignore stop.. + if (m_stop_token.stop_requested()) { + set_stopped(); + return; + } auto res = m_io(); - if (!res.has_value()) { - if (res.error() == std::errc::operation_would_block || - res.error() == std::errc::resource_unavailable_try_again) { - - state = State::Blocked; - m_fd->m_registration->callback(m_direction, [this]() { schedule_poll(); }); + if (res.has_value()) { + set_value(std::move(res)); + return; + } - return; - } + if (!is_error_blocking(res.error())) { + set_value(std::move(res)); + return; } - set_value(std::move(res)); + m_state = State::Blocked; + m_fd->m_registration->callback(m_direction, [this]() { poll(); }); } - void set_value(result_type res) { + void set_value(result_type value) { m_stop_fn.reset(); - state = State::Done; - m_receiver.env().scheduler->schedule( - [&, value = std::move(res)]() mutable { m_receiver.set_value(std::move(value)); }); + m_state = State::Done; + m_receiver.set_value(std::move(value)); } void set_stopped() { m_stop_fn.reset(); - state = State::Done; - - m_receiver.env().scheduler->schedule([&]() { m_receiver.set_stopped(); }); - } - - void schedule_poll() noexcept { - if (state != State::Blocked) { - return; - } - - state = State::Flight; - m_receiver.env().scheduler->schedule([this]() { poll(); }); + m_state = State::Stopped; + m_receiver.set_exception(std::make_exception_ptr(co::StoppedException{})); } const io::PolledFd* m_fd; io::Direction m_direction; + std::stop_token m_stop_token; F m_io; R m_receiver; enum class State : std::uint8_t { Blocked, - Flight, Done, + Stopped, }; - State state = State::Blocked; - bool stop_requested = false; + State m_state = State::Blocked; struct StopFn final { OperationState* self; - void operator()() noexcept { - self->stop_requested = true; - self->m_fd->m_registration->callback(self->m_direction, {}); - } + void operator()() noexcept { self->m_fd->m_registration->callback(self->m_direction, {}); } }; // cb is not moveable.. @@ -187,17 +176,18 @@ class TryIoSender final { template OperationState connect(R&& receiver) && { - return OperationState{std::forward(receiver), *m_fd, m_direction, std::move(m_io)}; + return OperationState{std::forward(receiver), *m_fd, m_direction, std::move(m_stop_token), std::move(m_io)}; } private: const io::PolledFd* m_fd; io::Direction m_direction; + std::stop_token m_stop_token; F m_io; }; -inline Co> accept(PolledFd& fd) { - auto accepted_fd_res = co_await io::TryIoSender{fd, io::Direction::Read, +inline co::Co> accept(PolledFd& fd, std::stop_token stop_token = {}) { + auto accepted_fd_res = co_await io::TryIoSender{fd, io::Direction::Read, std::move(stop_token), [&]() { return adoptSysFd(::accept(fd.fd(), nullptr, nullptr)); }}; if (!accepted_fd_res.has_value()) { @@ -207,8 +197,9 @@ inline Co> accept(PolledFd& fd) { co_return co_await PolledFd::create(std::move(accepted_fd_res).value()); } -[[nodiscard]] inline Co> aread(PolledFd& fd, std::span buf) noexcept { - co_return co_await io::TryIoSender{fd, io::Direction::Read, [&]() { return read(fd, buf); }}; +[[nodiscard]] inline co::Co> aread(PolledFd& fd, std::span buf, + std::stop_token stop_token = {}) noexcept { + co_return co_await io::TryIoSender{fd, io::Direction::Read, std::move(stop_token), [&]() { return read(fd, buf); }}; } } // namespace fastipc::io diff --git a/src/main.cxx b/src/main.cxx index 559eff4..22ca97d 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -16,16 +16,17 @@ * */ +#include #include "io/context.hxx" -#include "io/io_env.hxx" #include "tower.hxx" namespace fastipc { namespace { -io::Co main() { +co::Co main() { auto tower = co_await fastipc::Tower::create("fastipcd"); - static_cast(co_await tower.run()); + std::stop_source stop_source{}; + static_cast(co_await tower.run(stop_source.get_token())); co_return 0; } diff --git a/src/tower.cxx b/src/tower.cxx index 6a8e6e9..bb1fe74 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -87,7 +87,7 @@ namespace { } // namespace -[[nodiscard]] io::Co Tower::create(std::string_view path) { +[[nodiscard]] co::Co Tower::create(std::string_view path) { auto sockfd = expect(io::adoptSysFd(::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0)), "failed to create tower socket"); @@ -112,7 +112,7 @@ namespace { co_return Tower{expect(co_await io::PolledFd::create(std::move(sockfd)), "failed to created polled fd")}; } -io::Co Tower::run() { +co::Co Tower::run(std::stop_token stop_token) { // NOLINTNEXTLINE(altera-unroll-loops) Service loops should not be unrolled for (;;) { auto expected_clientfd = co_await accept(m_sockfd); @@ -128,7 +128,7 @@ io::Co Tower::run() { auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); // by detaching we have no way of shutting clients down - static_cast(co_await co::spawn(serve(std::move(clientfd)))); + static_cast(co_await co::spawn(serve(std::move(clientfd), stop_token))); static_cast(clientfd); } @@ -139,9 +139,9 @@ void Tower::shutdown() { // expect(io::sysCheck(::shutdown(m_sockfd.fd(), SHUT_RD)), "Failed to shutdown tower socket"); } -io::Co Tower::serve(io::PolledFd clientfd) { +co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { std::array buf{}; // NOLINT(*-magic-numbers) - const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}), "failed to read from client"); + const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}, stop_token), "failed to read from client"); auto recvbuf = std::span{buf}.first(bytes_read); const auto request = expect(expect(readClientRequest(recvbuf), "invalid request"), "incomplete message"); diff --git a/src/tower.hxx b/src/tower.hxx index 554cfb8..f6a5c67 100644 --- a/src/tower.hxx +++ b/src/tower.hxx @@ -24,16 +24,15 @@ #include "io/fd.hxx" #include "io/polled_fd.hxx" -#include "io/io_env.hxx" #include "channel.hxx" namespace fastipc { class Tower final { public: - [[nodiscard]] static io::Co create(std::string_view path); + [[nodiscard]] static co::Co create(std::string_view path); - io::Co run(); + co::Co run(std::stop_token stop_token); void shutdown(); private: @@ -46,7 +45,7 @@ class Tower final { explicit Tower(io::PolledFd sockfd) noexcept : m_sockfd{std::move(sockfd)} {} - io::Co serve(io::PolledFd clientfd); + co::Co serve(io::PolledFd clientfd, std::stop_token stop_token); io::PolledFd m_sockfd; std::unordered_map m_channels; diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index e6d8dc3..68a3124 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -26,14 +26,14 @@ #include "co/task.hxx" #include "io/context.hxx" -#include "io/io_env.hxx" namespace { -fastipc::io::Co co_main() { +fastipc::co::Co co_main() { auto tower = co_await fastipc::Tower::create("fastipcd"); - - auto handle = co_await fastipc::co::spawn(tower.run()); + + std::stop_source stop_source{}; + auto handle = co_await fastipc::co::spawn(tower.run(stop_source.get_token())); auto test = std::jthread{[&] { std::println("starting test in thead"); @@ -68,7 +68,7 @@ fastipc::io::Co co_main() { // tower.shutdown(); std::println("test done. stopping handle"); - handle.abort(); + stop_source.request_stop(); }}; static_cast(co_await std::move(handle)); From 63c1c0132fb577e7c303fa21df6930061e90e804 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 21:25:14 +0100 Subject: [PATCH 11/41] Task spawning does not need to be awaited now --- src/co/task.hxx | 84 +++++++++++++++---------------------------- src/tower.cxx | 13 ++++--- test/intraprocess.cxx | 2 +- 3 files changed, 35 insertions(+), 64 deletions(-) diff --git a/src/co/task.hxx b/src/co/task.hxx index f3ecab7..be76cca 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -22,10 +22,7 @@ #include #include #include -#include -#include #include -#include "co/coroutine.hxx" #include "co/received.hxx" namespace fastipc::co { @@ -108,70 +105,45 @@ struct JoinHandle final { }; template -struct SpawnSender final { - using task_value_type = typename S::value_type; - using value_type = JoinHandle; +JoinHandle spawn(S&& sender) { + struct StateImpl : State { + explicit StateImpl() : State{} {} - template - struct OperationState { - R receiver; - S sender; - - using T = task_value_type; - - void start() { - struct StateImpl : State { - explicit StateImpl() : State{} {} - - StateImpl(const StateImpl&) = delete; - StateImpl& operator=(const StateImpl&) = delete; - StateImpl(StateImpl&&) = delete; - StateImpl& operator=(StateImpl&&) = delete; - - ~StateImpl() override = default; + StateImpl(const StateImpl&) = delete; + StateImpl& operator=(const StateImpl&) = delete; + StateImpl(StateImpl&&) = delete; + StateImpl& operator=(StateImpl&&) = delete; - struct Receiver { - std::shared_ptr state; + ~StateImpl() override = default; - void set_value(T value) { - state->received.set_value(std::move(value)); + struct Receiver { + std::shared_ptr state; - if (state->listener) { - state->listener->notify(); - } - } - void set_exception(std::exception_ptr exc) { - state->received.set_exception(std::move(exc)); + void set_value(typename S::value_type value) { + state->received.set_value(std::move(value)); - if (state->listener) { - state->listener->notify(); - } - } - }; - - using operation_state_type = decltype(std::declval().connect(std::declval())); + if (state->listener) { + state->listener->notify(); + } + } + void set_exception(std::exception_ptr exc) { + state->received.set_exception(std::move(exc)); - std::optional operation_state{}; - }; + if (state->listener) { + state->listener->notify(); + } + } + }; - auto state = std::make_shared(); - state->operation_state.emplace(std::move(sender).connect(typename StateImpl::Receiver{state})).start(); + using operation_state_type = decltype(std::declval().connect(std::declval())); - receiver.set_value(JoinHandle{std::move(state)}); - } + std::optional operation_state{}; }; - template - auto connect(R&& receiver) && { - return OperationState{std::forward(receiver), std::move(sender)}; - } - - S sender; -}; + auto state = std::make_shared(); + state->operation_state.emplace(std::forward(sender).connect(typename StateImpl::Receiver{state})).start(); -template -SpawnSender spawn(S&& sender) { - return SpawnSender{std::forward(sender)}; + return JoinHandle{std::move(state)}; } } // namespace fastipc::co \ No newline at end of file diff --git a/src/tower.cxx b/src/tower.cxx index bb1fe74..cb1fce5 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -114,22 +114,19 @@ namespace { co::Co Tower::run(std::stop_token stop_token) { // NOLINTNEXTLINE(altera-unroll-loops) Service loops should not be unrolled - for (;;) { - auto expected_clientfd = co_await accept(m_sockfd); + for (; !stop_token.stop_requested();) { + auto expected_clientfd = co_await accept(m_sockfd, stop_token); if (!expected_clientfd.has_value()) { if (expected_clientfd.error() == std::errc::bad_file_descriptor) break; if (expected_clientfd.error() == std::errc::connection_aborted) continue; - } auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); - // by detaching we have no way of shutting clients down - static_cast(co_await co::spawn(serve(std::move(clientfd), stop_token))); - static_cast(clientfd); + static_cast(co::spawn(serve(std::move(clientfd), stop_token))); } co_return 0; // lazy void @@ -141,7 +138,8 @@ void Tower::shutdown() { co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { std::array buf{}; // NOLINT(*-magic-numbers) - const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}, stop_token), "failed to read from client"); + const auto bytes_read = + expect(co_await io::aread(clientfd, std::span{buf}, stop_token), "failed to read from client"); auto recvbuf = std::span{buf}.first(bytes_read); const auto request = expect(expect(readClientRequest(recvbuf), "invalid request"), "incomplete message"); @@ -196,6 +194,7 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { std::memcpy(CMSG_DATA(cmsg), &channel.memfd.fd(), sizeof(channel.memfd)); msg.msg_controllen = cmsg->cmsg_len; + // TODO: asyncify static_cast(expect(io::sysVal(::sendmsg(clientfd.fd(), &msg, 0)), "failed to send reply to client")); co_return 0; // too lazy for void diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index 68a3124..7d72571 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -33,7 +33,7 @@ fastipc::co::Co co_main() { auto tower = co_await fastipc::Tower::create("fastipcd"); std::stop_source stop_source{}; - auto handle = co_await fastipc::co::spawn(tower.run(stop_source.get_token())); + auto handle = fastipc::co::spawn(tower.run(stop_source.get_token())); auto test = std::jthread{[&] { std::println("starting test in thead"); From 74e36098dafba88b27e884331a7944851cd817fe Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 21:41:33 +0100 Subject: [PATCH 12/41] Implement block_on --- src/co/coroutine.hxx | 2 -- src/co/task.hxx | 2 +- src/io/context.hxx | 46 ++++++++++++------------------------------- src/main.cxx | 4 +--- test/intraprocess.cxx | 2 +- 5 files changed, 16 insertions(+), 40 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index 4d681d4..d054ceb 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -222,6 +222,4 @@ class [[nodiscard]] Co { Promise m_promise; }; -// auto block_on - } // namespace fastipc::co diff --git a/src/co/task.hxx b/src/co/task.hxx index be76cca..1ef1c57 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -61,6 +61,7 @@ struct State { template struct JoinHandle final { [[nodiscard]] bool completed() const noexcept { return state->received.has_value(); } + [[nodiscard]] T get() { return std::move(state->received).consume(); } using value_type = T; @@ -100,7 +101,6 @@ struct JoinHandle final { return OperationState{std::forward(receiver), std::move(state)}; } - // dtor slice? std::shared_ptr> state; }; diff --git a/src/io/context.hxx b/src/io/context.hxx index 3848e73..670780b 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -10,43 +10,23 @@ namespace fastipc::io { - -struct Receiver { - template - void set_value(T value) { - std::println("complete: {}", value); - } - void set_exception(std::exception_ptr exc) noexcept { std::rethrow_exception(std::move(exc)); } -}; - - -// so in p2300 it seems block_on will supply an in-place scheduler -// which we should probably use for the main function, but then we can introduce this i/o env with a simple receiver? - template -void context(F func); +auto block_on(F func) { + auto reactor = expect(Reactor::create()); + auto scheduler = co::Scheduler{&reactor}; + auto task = co::spawn(func()); -// template -// void context(F func) { -// auto reactor = expect(Reactor::create()); -// auto scheduler = co::Scheduler{&reactor}; - -// Env env{.scheduler = &scheduler, .reactor = &reactor, .stop_token = {}}; - -// auto op = func().connect(Receiver{env}); -// op.start(); - -// while (true) { -// while (scheduler.can_run()) { -// scheduler.run(); -// expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); -// } + while (!task.completed()) { + while (scheduler.can_run()) { + scheduler.run(); + expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + } -// expect(reactor.react({}), "failed to react to io events"); -// } + expect(reactor.react({}), "failed to react to io events"); + } -// static_cast(op); -// } + return task.get(); +} } // namespace fastipc::io \ No newline at end of file diff --git a/src/main.cxx b/src/main.cxx index 22ca97d..e84ce26 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -34,6 +34,4 @@ co::Co main() { } // namespace } // namespace fastipc -int main() { - fastipc::io::context(fastipc::main); -} +int main() { return fastipc::io::block_on(fastipc::main); } diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index 7d72571..ac99efe 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -80,4 +80,4 @@ fastipc::co::Co co_main() { } // namespace -int main() { fastipc::io::context(co_main); } +int main() { return fastipc::io::block_on(co_main); } From 4e9a4da00133724f143865b159fe2e02a7f09a08 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 22:03:08 +0100 Subject: [PATCH 13/41] Add runtime --- src/io/context.hxx | 57 +++++++++++++++++++++++++++++++++---------- src/io/polled_fd.hxx | 3 ++- src/main.cxx | 7 +++++- test/intraprocess.cxx | 8 ++++-- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/io/context.hxx b/src/io/context.hxx index 670780b..5408a31 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -6,27 +6,58 @@ #include "co/received.hxx" #include "co/scheduler.hxx" #include "co/task.hxx" +#include "io/result.hxx" #include "reactor.hxx" namespace fastipc::io { -template -auto block_on(F func) { - auto reactor = expect(Reactor::create()); - auto scheduler = co::Scheduler{&reactor}; +class Runtime final { + explicit Runtime(Reactor reactor) + : m_reactor{std::move(reactor)}, m_scheduler{std::make_unique(&m_reactor)} {} - auto task = co::spawn(func()); + public: + static expected create() noexcept { + auto reactor_res = Reactor::create(); - while (!task.completed()) { - while (scheduler.can_run()) { - scheduler.run(); - expect(reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + if (!reactor_res.has_value()) { + return unexpected{std::move(reactor_res).error()}; } - - expect(reactor.react({}), "failed to react to io events"); + + return expected{Runtime{std::move(reactor_res).value()}}; + } + + static Runtime& singleton() noexcept { return *active_singleton(); } + + template + auto block_on(F func) noexcept { + active_singleton() = this; + + auto task = co::spawn(func()); + + while (!task.completed()) { + while (m_scheduler->can_run()) { + m_scheduler->run(); + expect(m_reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + } + + expect(m_reactor.react({}), "failed to react to io events"); + } + + return task.get(); + } + + Reactor& reactor() noexcept { return m_reactor; } + co::Scheduler& scheduler() noexcept { return *m_scheduler; } + + private: + static Runtime*& active_singleton() { + static Runtime* runtime{}; + + return runtime; } - return task.get(); -} + Reactor m_reactor; + std::unique_ptr m_scheduler; +}; } // namespace fastipc::io \ No newline at end of file diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index b4360e6..70728be 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -24,6 +24,7 @@ #include #include #include "co/coroutine.hxx" +#include "io/context.hxx" #include "fd.hxx" #include "reactor.hxx" #include "result.hxx" @@ -50,7 +51,7 @@ class PolledFd final { } } - static co::Co> create(Fd fd) noexcept; + static co::Co> create(Fd fd) noexcept { return create(std::move(fd), Runtime::singleton().reactor()); } static co::Co> create(Fd fd, Reactor& reactor) noexcept { co_return setBlocking(fd, false) diff --git a/src/main.cxx b/src/main.cxx index e84ce26..c1ccf9d 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -18,6 +18,7 @@ #include #include "io/context.hxx" +#include "io/result.hxx" #include "tower.hxx" namespace fastipc { @@ -34,4 +35,8 @@ co::Co main() { } // namespace } // namespace fastipc -int main() { return fastipc::io::block_on(fastipc::main); } +int main() { + auto runtime = fastipc::expect(fastipc::io::Runtime::create()); + + return runtime.block_on(fastipc::main); +} diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index ac99efe..bf0c5ce 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -31,7 +31,7 @@ namespace { fastipc::co::Co co_main() { auto tower = co_await fastipc::Tower::create("fastipcd"); - + std::stop_source stop_source{}; auto handle = fastipc::co::spawn(tower.run(stop_source.get_token())); @@ -80,4 +80,8 @@ fastipc::co::Co co_main() { } // namespace -int main() { return fastipc::io::block_on(co_main); } +int main() { + auto runtime = fastipc::expect(fastipc::io::Runtime::create()); + + return runtime.block_on(co_main); +} From 2c5c7822f3278c20fe827b1f0112fd05fada250b Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 22:44:32 +0100 Subject: [PATCH 14/41] Fix runtime --- src/co/coroutine.hxx | 2 +- src/io/context.hxx | 14 ++++++-------- src/io/polled_fd.hxx | 6 ++++-- src/io/reactor.cxx | 1 - src/tower.cxx | 2 +- test/intraprocess.cxx | 9 +++++++-- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index d054ceb..46f4141 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -136,7 +136,7 @@ class SenderAwaiter { return std::noop_coroutine(); } - [[nodiscard]] value_type await_resume() noexcept { return std::move(received).consume(); } + [[nodiscard]] value_type await_resume() { return std::move(received).consume(); } private: class AwaiterReceiver { diff --git a/src/io/context.hxx b/src/io/context.hxx index 5408a31..7935c20 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,9 +1,6 @@ #pragma once -#include #include -#include #include -#include "co/received.hxx" #include "co/scheduler.hxx" #include "co/task.hxx" #include "io/result.hxx" @@ -13,7 +10,8 @@ namespace fastipc::io { class Runtime final { explicit Runtime(Reactor reactor) - : m_reactor{std::move(reactor)}, m_scheduler{std::make_unique(&m_reactor)} {} + : m_reactor{std::make_unique(std::move(reactor))}, + m_scheduler{std::make_unique(m_reactor.get())} {} public: static expected create() noexcept { @@ -37,16 +35,16 @@ class Runtime final { while (!task.completed()) { while (m_scheduler->can_run()) { m_scheduler->run(); - expect(m_reactor.react(std::chrono::milliseconds{0}), "failed to react to io events"); + expect(m_reactor->react(std::chrono::milliseconds{0}), "failed to react to io events"); } - expect(m_reactor.react({}), "failed to react to io events"); + expect(m_reactor->react({}), "failed to react to io events"); } return task.get(); } - Reactor& reactor() noexcept { return m_reactor; } + Reactor& reactor() noexcept { return *m_reactor; } co::Scheduler& scheduler() noexcept { return *m_scheduler; } private: @@ -56,7 +54,7 @@ class Runtime final { return runtime; } - Reactor m_reactor; + std::unique_ptr m_reactor; std::unique_ptr m_scheduler; }; diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 70728be..a212f2e 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -60,6 +60,8 @@ class PolledFd final { } [[nodiscard]] constexpr const int& fd() const noexcept { return m_fd.fd(); } + [[nodiscard]] Reactor& reactor() const noexcept { return *m_reactor; } + private: template @@ -195,11 +197,11 @@ inline co::Co> accept(PolledFd& fd, std::stop_token stop_toke co_return unexpected{accepted_fd_res.error()}; } - co_return co_await PolledFd::create(std::move(accepted_fd_res).value()); + co_return co_await PolledFd::create(std::move(accepted_fd_res).value(), fd.reactor()); } [[nodiscard]] inline co::Co> aread(PolledFd& fd, std::span buf, - std::stop_token stop_token = {}) noexcept { + std::stop_token stop_token = {}) { co_return co_await io::TryIoSender{fd, io::Direction::Read, std::move(stop_token), [&]() { return read(fd, buf); }}; } diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index 5c9b16a..9e6661f 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/src/tower.cxx b/src/tower.cxx index cb1fce5..b9e2167 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -52,7 +52,7 @@ namespace fastipc { namespace { -[[nodiscard]] io::expected> readClientRequest(std::span& obuf) noexcept { +[[nodiscard]] io::expected> readClientRequest(std::span& obuf) { constexpr static auto kMinSize = 10u; auto buf = obuf; diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index bf0c5ce..2e5affb 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -24,6 +24,7 @@ #include "fastipc.hxx" #include "tower.hxx" +#include "co/coroutine.hxx" #include "co/task.hxx" #include "io/context.hxx" @@ -68,10 +69,14 @@ fastipc::co::Co co_main() { // tower.shutdown(); std::println("test done. stopping handle"); - stop_source.request_stop(); + + fastipc::io::Runtime::singleton().scheduler().schedule([&]() { stop_source.request_stop(); }); }}; - static_cast(co_await std::move(handle)); + try { + static_cast(co_await std::move(handle)); + } catch (const fastipc::co::StoppedException&) { + } std::println("run done!"); From 568c8f6fb67ef32f0bbad9f4d0f360a4bc05b63f Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 22:57:09 +0100 Subject: [PATCH 15/41] Fix block_on to not use task --- src/io/context.hxx | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/io/context.hxx b/src/io/context.hxx index 7935c20..106cb96 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,6 +1,9 @@ #pragma once +#include #include +#include #include +#include "co/received.hxx" #include "co/scheduler.hxx" #include "co/task.hxx" #include "io/result.hxx" @@ -30,18 +33,43 @@ class Runtime final { auto block_on(F func) noexcept { active_singleton() = this; - auto task = co::spawn(func()); + using S = std::remove_cvref_t; - while (!task.completed()) { + co::Received received{}; + + struct Receiver { + co::Received* received; + Reactor* reactor; + + void set_value(typename S::value_type value) { + received->set_value(std::move(value)); + expect(reactor->interrupt()); + } + void set_exception(std::exception_ptr exc) { + received->set_exception(std::move(exc)); + expect(reactor->interrupt()); + } + }; + + auto op = func().connect(Receiver{&received, m_reactor.get()}); + op.start(); + + for (;;) { while (m_scheduler->can_run()) { m_scheduler->run(); expect(m_reactor->react(std::chrono::milliseconds{0}), "failed to react to io events"); + + if (received.has_value()) { + return std::move(received).consume(); + } + } + + if (received.has_value()) { + return std::move(received).consume(); } expect(m_reactor->react({}), "failed to react to io events"); } - - return task.get(); } Reactor& reactor() noexcept { return *m_reactor; } From e26fc4eb7cfc9c17169b8797ff81e267d8362f44 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 6 Mar 2026 23:01:41 +0100 Subject: [PATCH 16/41] Catch stopped exception in run() --- src/tower.cxx | 26 ++++++++++++++++---------- test/intraprocess.cxx | 5 +---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/tower.cxx b/src/tower.cxx index b9e2167..05e41df 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -113,23 +113,29 @@ namespace { } co::Co Tower::run(std::stop_token stop_token) { + // NOLINTNEXTLINE(altera-unroll-loops) Service loops should not be unrolled for (; !stop_token.stop_requested();) { - auto expected_clientfd = co_await accept(m_sockfd, stop_token); + try { + auto expected_clientfd = co_await accept(m_sockfd, stop_token); - if (!expected_clientfd.has_value()) { - if (expected_clientfd.error() == std::errc::bad_file_descriptor) - break; - if (expected_clientfd.error() == std::errc::connection_aborted) - continue; - } + if (!expected_clientfd.has_value()) { + if (expected_clientfd.error() == std::errc::bad_file_descriptor) + break; + if (expected_clientfd.error() == std::errc::connection_aborted) + continue; + } - auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); + auto clientfd = expect(std::move(expected_clientfd), "failed to accept incoming connection"); - static_cast(co::spawn(serve(std::move(clientfd), stop_token))); + static_cast(co::spawn(serve(std::move(clientfd), stop_token))); + + } catch (const fastipc::co::StoppedException&) { + break; + } } - co_return 0; // lazy void + co_return 0; } void Tower::shutdown() { diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index 2e5affb..e8307e5 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -73,10 +73,7 @@ fastipc::co::Co co_main() { fastipc::io::Runtime::singleton().scheduler().schedule([&]() { stop_source.request_stop(); }); }}; - try { - static_cast(co_await std::move(handle)); - } catch (const fastipc::co::StoppedException&) { - } + static_cast(co_await std::move(handle)); std::println("run done!"); From e54381b4745c5772b6ead62990c88ca6d39e719a Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sat, 7 Mar 2026 10:11:16 +0100 Subject: [PATCH 17/41] Add void support --- src/co/coroutine.hxx | 228 ++++++++++++++++++++++++++++--------------- src/co/task.hxx | 84 ++++++++++------ src/tower.cxx | 8 +- src/tower.hxx | 4 +- 4 files changed, 211 insertions(+), 113 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index 46f4141..270ca3e 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -54,41 +54,12 @@ struct AwaitedBy { }; template -class [[nodiscard]] Promise { - public: - class State { - - public: - Promise get_return_object() { return Promise{std::coroutine_handle::from_promise(*this)}; } - - std::suspend_always initial_suspend() noexcept { return {}; } - - struct FinalSuspend { - bool await_ready() noexcept { return false; } - void await_suspend(std::coroutine_handle h) noexcept { h.promise().complete(); } - void await_resume() noexcept {} - }; - - FinalSuspend final_suspend() noexcept { return {}; } - - void return_value(T value) { received.set_value(std::move(value)); } - void unhandled_exception() { received.set_exception(std::current_exception()); } - - template - AwaitedBy::State> await_transform(A&& awaitable) { - return {std::forward(awaitable)}; - } - - Receiver* receiver; - - private: - // sadly we need to buffer the received value to allow final_suspend to run - void complete() { std::move(received).forward(*receiver); } - - Received received; - }; +class PromiseState; - explicit Promise(std::coroutine_handle handle) : m_handle{std::move(handle)} {} +template +class [[nodiscard]] Promise final { + public: + explicit Promise(std::coroutine_handle> handle) : m_handle{std::move(handle)} {} Promise(Promise&) noexcept = delete; Promise& operator=(Promise&) noexcept = delete; @@ -108,15 +79,125 @@ class [[nodiscard]] Promise { } } - std::coroutine_handle handle() { return m_handle; } - std::coroutine_handle handle() const { return m_handle; } + std::coroutine_handle> handle() { return m_handle; } + std::coroutine_handle> handle() const { return m_handle; } + + private: + std::coroutine_handle> m_handle; +}; + +template +class PromiseState final { + public: + Promise get_return_object() { return Promise{std::coroutine_handle::from_promise(*this)}; } + + std::suspend_always initial_suspend() noexcept { return {}; } + + struct FinalSuspend final { + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle h) noexcept { h.promise().complete(); } + void await_resume() noexcept {} + }; + + FinalSuspend final_suspend() noexcept { return {}; } + + void return_value(T value) { received.set_value(std::move(value)); } + void unhandled_exception() { received.set_exception(std::current_exception()); } + + template + AwaitedBy await_transform(A&& awaitable) { + return {std::forward(awaitable)}; + } + + Receiver* receiver{}; + + private: + void complete() { std::move(received).forward(*receiver); } + + Received received; +}; + +template <> +class PromiseState final { + public: + Promise get_return_object() { + return Promise{std::coroutine_handle::from_promise(*this)}; + } + + std::suspend_always initial_suspend() noexcept { return {}; } + + struct FinalSuspend final { + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle h) noexcept { h.promise().complete(); } + void await_resume() noexcept {} + }; + + FinalSuspend final_suspend() noexcept { return {}; } + + void return_void() { received.set_value(); } + void unhandled_exception() { received.set_exception(std::current_exception()); } + + template + AwaitedBy await_transform(A&& awaitable) { + return {std::forward(awaitable)}; + } + + Receiver* receiver{}; + + private: + void complete() { std::move(received).forward(*receiver); } + + Received received; +}; + +template +class AwaiterReceiver final { + public: + AwaiterReceiver(Received& received, std::coroutine_handle

awaiter) + : m_received{&received}, m_awaiter{awaiter} {} + + void set_value(T value) { + m_received->set_value(std::move(value)); + + m_awaiter.resume(); + } + + void set_exception(std::exception_ptr ptr) { + m_received->set_exception(std::move(ptr)); + + m_awaiter.resume(); + } + + private: + Received* m_received; + std::coroutine_handle

m_awaiter; +}; + +template +class AwaiterReceiver final { + public: + AwaiterReceiver(Received& received, std::coroutine_handle

awaiter) + : m_received{&received}, m_awaiter{awaiter} {} + + void set_value() { + m_received->set_value(); + + m_awaiter.resume(); + } + + void set_exception(std::exception_ptr ptr) { + m_received->set_exception(std::move(ptr)); + + m_awaiter.resume(); + } private: - std::coroutine_handle m_handle; + Received* m_received; + std::coroutine_handle

m_awaiter; }; template -class SenderAwaiter { +class SenderAwaiter final { public: using sender_type = S; @@ -129,7 +210,7 @@ class SenderAwaiter { std::coroutine_handle<> await_suspend(std::coroutine_handle

cont) { // TODO: somehow operation state needs to be moveable.. auto& operation_state = state.template emplace( - std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); + std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); operation_state.start(); @@ -139,29 +220,7 @@ class SenderAwaiter { [[nodiscard]] value_type await_resume() { return std::move(received).consume(); } private: - class AwaiterReceiver { - public: - AwaiterReceiver(Received& received, std::coroutine_handle

awaiter) - : m_received{&received}, m_awaiter{awaiter} {} - - void set_value(value_type value) { - m_received->set_value(std::move(value)); - - m_awaiter.resume(); - } - - void set_exception(std::exception_ptr ptr) { - m_received->set_exception(ptr); - - m_awaiter.resume(); - } - - private: - Received* m_received; - std::coroutine_handle

m_awaiter; - }; - - using operation_state_type = decltype(std::declval().connect(std::declval())); + using operation_state_type = decltype(std::declval().connect(std::declval>())); std::variant state; Received received; @@ -173,12 +232,37 @@ SenderAwaiter operator co_await(AwaitedBy&& awaited_by) { return SenderAwaiter{std::move(awaited_by).value}; } -template -class [[nodiscard]] Co { +template +class PromiseReceiver : public Receiver { + + public: + explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} + + void set_value(T value) override { m_receiver.set_value(std::move(value)); } + void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } + + private: + R m_receiver; +}; + +template +class PromiseReceiver : public Receiver { public: - using promise_type = Promise::State; + explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} + + void set_value() override { m_receiver.set_value(); } + void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } + + private: + R m_receiver; +}; + +template +class [[nodiscard]] Co final { + public: + using promise_type = PromiseState; using value_type = T; explicit(false) Co(Promise promise) : m_promise{std::move(promise)} {} @@ -199,19 +283,7 @@ class [[nodiscard]] Co { } private: - class PromiseReceiver : public Receiver { - - public: - explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} - - void set_value(T value) override { m_receiver.set_value(std::move(value)); } - void set_exception(std::exception_ptr exc) override { m_receiver.set_exception(exc); } - - private: - R m_receiver; - }; - - PromiseReceiver m_receiver; + PromiseReceiver m_receiver; Promise m_promise; }; diff --git a/src/co/task.hxx b/src/co/task.hxx index 1ef1c57..4077dc8 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -104,44 +104,70 @@ struct JoinHandle final { std::shared_ptr> state; }; -template -JoinHandle spawn(S&& sender) { - struct StateImpl : State { - explicit StateImpl() : State{} {} +template +struct StateReceiver final { + std::shared_ptr> state; - StateImpl(const StateImpl&) = delete; - StateImpl& operator=(const StateImpl&) = delete; - StateImpl(StateImpl&&) = delete; - StateImpl& operator=(StateImpl&&) = delete; + void set_value(T value) { + state->received.set_value(std::move(value)); - ~StateImpl() override = default; + if (state->listener) { + state->listener->notify(); + } + } - struct Receiver { - std::shared_ptr state; + void set_exception(std::exception_ptr exc) { + state->received.set_exception(std::move(exc)); - void set_value(typename S::value_type value) { - state->received.set_value(std::move(value)); + if (state->listener) { + state->listener->notify(); + } + } +}; - if (state->listener) { - state->listener->notify(); - } - } - void set_exception(std::exception_ptr exc) { - state->received.set_exception(std::move(exc)); +template <> +struct StateReceiver final { + std::shared_ptr> state; - if (state->listener) { - state->listener->notify(); - } - } - }; + void set_value() { + state->received.set_value(); - using operation_state_type = decltype(std::declval().connect(std::declval())); + if (state->listener) { + state->listener->notify(); + } + } - std::optional operation_state{}; - }; + void set_exception(std::exception_ptr exc) { + state->received.set_exception(std::move(exc)); - auto state = std::make_shared(); - state->operation_state.emplace(std::forward(sender).connect(typename StateImpl::Receiver{state})).start(); + if (state->listener) { + state->listener->notify(); + } + } +}; + +template +struct StateImpl final : State { + explicit StateImpl() : State{} {} + + StateImpl(const StateImpl&) = delete; + StateImpl& operator=(const StateImpl&) = delete; + StateImpl(StateImpl&&) = delete; + StateImpl& operator=(StateImpl&&) = delete; + + ~StateImpl() override = default; + + using operation_state_type = + decltype(std::declval().connect(std::declval>())); + + std::optional operation_state{}; +}; + +template +JoinHandle spawn(S&& sender) { + auto state = std::make_shared>(); + state->operation_state.emplace(std::forward(sender).connect(StateReceiver{state})) + .start(); return JoinHandle{std::move(state)}; } diff --git a/src/tower.cxx b/src/tower.cxx index 05e41df..1fd55b2 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -112,7 +112,7 @@ namespace { co_return Tower{expect(co_await io::PolledFd::create(std::move(sockfd)), "failed to created polled fd")}; } -co::Co Tower::run(std::stop_token stop_token) { +co::Co Tower::run(std::stop_token stop_token) { // NOLINTNEXTLINE(altera-unroll-loops) Service loops should not be unrolled for (; !stop_token.stop_requested();) { @@ -135,14 +135,14 @@ co::Co Tower::run(std::stop_token stop_token) { } } - co_return 0; + co_return; } void Tower::shutdown() { // expect(io::sysCheck(::shutdown(m_sockfd.fd(), SHUT_RD)), "Failed to shutdown tower socket"); } -co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { +co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { std::array buf{}; // NOLINT(*-magic-numbers) const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}, stop_token), "failed to read from client"); @@ -203,7 +203,7 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { // TODO: asyncify static_cast(expect(io::sysVal(::sendmsg(clientfd.fd(), &msg, 0)), "failed to send reply to client")); - co_return 0; // too lazy for void + co_return; } } // namespace fastipc diff --git a/src/tower.hxx b/src/tower.hxx index f6a5c67..30acefc 100644 --- a/src/tower.hxx +++ b/src/tower.hxx @@ -32,7 +32,7 @@ class Tower final { public: [[nodiscard]] static co::Co create(std::string_view path); - co::Co run(std::stop_token stop_token); + co::Co run(std::stop_token stop_token); void shutdown(); private: @@ -45,7 +45,7 @@ class Tower final { explicit Tower(io::PolledFd sockfd) noexcept : m_sockfd{std::move(sockfd)} {} - co::Co serve(io::PolledFd clientfd, std::stop_token stop_token); + co::Co serve(io::PolledFd clientfd, std::stop_token stop_token); io::PolledFd m_sockfd; std::unordered_map m_channels; From 4e40369d05d8b67a58ed37b16e59b258563b9f6e Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sat, 7 Mar 2026 10:13:36 +0100 Subject: [PATCH 18/41] final --- src/co/coroutine.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index 270ca3e..eef2d84 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -233,7 +233,7 @@ SenderAwaiter operator co_await(AwaitedBy&& awaited_by) { } template -class PromiseReceiver : public Receiver { +class PromiseReceiver final : public Receiver { public: explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} @@ -246,7 +246,7 @@ class PromiseReceiver : public Receiver { }; template -class PromiseReceiver : public Receiver { +class PromiseReceiver final : public Receiver { public: explicit PromiseReceiver(R receiver) : Receiver{}, m_receiver{std::move(receiver)} {} From b0df815c22d369ac545e715b47d533870ee9543c Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 21:32:40 +0100 Subject: [PATCH 19/41] Add asendmsg --- src/io/polled_fd.hxx | 11 +++++++++-- src/tower.cxx | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index a212f2e..3f54773 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -51,7 +51,9 @@ class PolledFd final { } } - static co::Co> create(Fd fd) noexcept { return create(std::move(fd), Runtime::singleton().reactor()); } + static co::Co> create(Fd fd) noexcept { + return create(std::move(fd), Runtime::singleton().reactor()); + } static co::Co> create(Fd fd, Reactor& reactor) noexcept { co_return setBlocking(fd, false) @@ -62,7 +64,6 @@ class PolledFd final { [[nodiscard]] constexpr const int& fd() const noexcept { return m_fd.fd(); } [[nodiscard]] Reactor& reactor() const noexcept { return *m_reactor; } - private: template friend class TryIoSender; @@ -205,4 +206,10 @@ inline co::Co> accept(PolledFd& fd, std::stop_token stop_toke co_return co_await io::TryIoSender{fd, io::Direction::Read, std::move(stop_token), [&]() { return read(fd, buf); }}; } +[[nodiscard]] inline co::Co> asendmsg(PolledFd& fd, ::msghdr& buf, int flags, + std::stop_token stop_token = {}) { + co_return co_await io::TryIoSender{fd, io::Direction::Read, std::move(stop_token), + [&]() { return sysVal(::sendmsg(fd.fd(), &buf, flags)); }}; +} + } // namespace fastipc::io diff --git a/src/tower.cxx b/src/tower.cxx index 1fd55b2..10485d4 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -200,8 +200,7 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { std::memcpy(CMSG_DATA(cmsg), &channel.memfd.fd(), sizeof(channel.memfd)); msg.msg_controllen = cmsg->cmsg_len; - // TODO: asyncify - static_cast(expect(io::sysVal(::sendmsg(clientfd.fd(), &msg, 0)), "failed to send reply to client")); + static_cast(expect(co_await io::asendmsg(clientfd, msg, 0), "failed to send reply to client")); co_return; } From dcb7d8f93f8278bd0e15ac23cc989f195cffb663 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 21:34:37 +0100 Subject: [PATCH 20/41] Don't make PolledFd::create async --- src/io/polled_fd.hxx | 8 ++++---- src/tower.cxx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 3f54773..aea7ebb 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -51,12 +51,12 @@ class PolledFd final { } } - static co::Co> create(Fd fd) noexcept { + static expected create(Fd fd) noexcept { return create(std::move(fd), Runtime::singleton().reactor()); } - static co::Co> create(Fd fd, Reactor& reactor) noexcept { - co_return setBlocking(fd, false) + static expected create(Fd fd, Reactor& reactor) noexcept { + return setBlocking(fd, false) .and_then([&]() { return reactor.registerFd(fd); }) .transform([&](auto* registration) { return PolledFd{std::move(fd), registration, reactor}; }); } @@ -198,7 +198,7 @@ inline co::Co> accept(PolledFd& fd, std::stop_token stop_toke co_return unexpected{accepted_fd_res.error()}; } - co_return co_await PolledFd::create(std::move(accepted_fd_res).value(), fd.reactor()); + co_return PolledFd::create(std::move(accepted_fd_res).value(), fd.reactor()); } [[nodiscard]] inline co::Co> aread(PolledFd& fd, std::span buf, diff --git a/src/tower.cxx b/src/tower.cxx index 10485d4..a338ff2 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -109,7 +109,7 @@ namespace { constexpr int kListenQueueSize{128}; expect(io::sysCheck(::listen(sockfd.fd(), kListenQueueSize)), "failed to listen to tower socket"); - co_return Tower{expect(co_await io::PolledFd::create(std::move(sockfd)), "failed to created polled fd")}; + co_return Tower{expect(io::PolledFd::create(std::move(sockfd)), "failed to created polled fd")}; } co::Co Tower::run(std::stop_token stop_token) { From 14322f91aa33828adbcde988e5eee249a360354e Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 21:45:57 +0100 Subject: [PATCH 21/41] clang tidy/format --- src/co/coroutine.hxx | 5 ----- src/io/polled_fd.hxx | 7 ++++++- src/io/reactor.cxx | 11 +++++++---- src/io/reactor.hxx | 6 ++++-- src/main.cxx | 3 ++- src/tower.cxx | 6 ++++-- test/intraprocess.cxx | 3 +++ 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index eef2d84..d6123ff 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -9,11 +9,6 @@ namespace fastipc::co { -class StoppedException final : public std::exception { - public: - [[nodiscard]] const char* what() const noexcept override { return "operation stopped"; } -}; - template class Receiver { public: diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index aea7ebb..742db17 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -31,6 +31,11 @@ namespace fastipc::io { +class StoppedException final : public std::exception { + public: + [[nodiscard]] const char* what() const noexcept override { return "operation stopped"; } +}; + constexpr bool is_error_blocking(std::error_code error) noexcept { return error == std::errc::operation_would_block || error == std::errc::resource_unavailable_try_again; } @@ -150,7 +155,7 @@ class TryIoSender final { m_stop_fn.reset(); m_state = State::Stopped; - m_receiver.set_exception(std::make_exception_ptr(co::StoppedException{})); + m_receiver.set_exception(std::make_exception_ptr(StoppedException{})); } const io::PolledFd* m_fd; diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index 9e6661f..fae6c60 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -31,11 +31,12 @@ namespace fastipc::io { -Reactor::Reactor(Fd event_fd, Fd epoll_fd) : m_event_fd_{std::move(event_fd)}, m_epoll_fd_{std::move(epoll_fd)} { - m_events_buf_.resize(512); +Reactor::Reactor(Fd event_fd, Fd epoll_fd, std::size_t max_events) + : m_event_fd_{std::move(event_fd)}, m_epoll_fd_{std::move(epoll_fd)} { + m_events_buf_.resize(max_events); } -expected Reactor::create() noexcept { +expected Reactor::create(std::size_t max_events) noexcept { return adoptSysFd(::eventfd(0, EFD_CLOEXEC)) .and_then([](Fd event_fd) { return adoptSysFd(::epoll_create1(0)) @@ -48,7 +49,9 @@ expected Reactor::create() noexcept { }) .transform([&](Fd epoll_fd) { return std::pair{std::move(event_fd), std::move(epoll_fd)}; }); }) - .transform([](std::pair fds) { return Reactor{std::move(fds.first), std::move(fds.second)}; }); + .transform([max_events](std::pair fds) { + return Reactor{std::move(fds.first), std::move(fds.second), max_events}; + }); } expected Reactor::react(std::optional timeout) noexcept { diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index df8b4e2..d222531 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -56,6 +56,8 @@ class Reactor final { } }; + static constexpr auto kDefaultMaxEvents = 512ul; + Reactor(const Reactor&) noexcept = delete; Reactor& operator=(const Reactor&) noexcept = delete; @@ -64,7 +66,7 @@ class Reactor final { ~Reactor() = default; - static expected create() noexcept; + static expected create(std::size_t max_events = kDefaultMaxEvents) noexcept; expected react(std::optional timeout) noexcept; expected interrupt() noexcept; @@ -74,7 +76,7 @@ class Reactor final { expected unregister(Registration* registration) noexcept; private: - explicit Reactor(Fd event_fd, Fd epoll_fd); + explicit Reactor(Fd event_fd, Fd epoll_fd, std::size_t max_events); expected> wait(std::optional timeout) noexcept; void process(std::span<::epoll_event> events) noexcept; diff --git a/src/main.cxx b/src/main.cxx index c1ccf9d..80b1c4a 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -17,6 +17,7 @@ */ #include +#include "co/coroutine.hxx" #include "io/context.hxx" #include "io/result.hxx" #include "tower.hxx" @@ -26,7 +27,7 @@ namespace { co::Co main() { auto tower = co_await fastipc::Tower::create("fastipcd"); - std::stop_source stop_source{}; + const std::stop_source stop_source{}; static_cast(co_await tower.run(stop_source.get_token())); co_return 0; diff --git a/src/tower.cxx b/src/tower.cxx index a338ff2..c59089b 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -41,10 +41,11 @@ #include #include "co/task.hxx" +#include +#include "co/coroutine.hxx" #include "io/cursor.hxx" #include "io/fd.hxx" #include "io/polled_fd.hxx" -#include "io/reactor.hxx" #include "io/result.hxx" #include "channel.hxx" #include "local_proto.hxx" @@ -130,7 +131,7 @@ co::Co Tower::run(std::stop_token stop_token) { static_cast(co::spawn(serve(std::move(clientfd), stop_token))); - } catch (const fastipc::co::StoppedException&) { + } catch (const fastipc::io::StoppedException&) { break; } } @@ -166,6 +167,7 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { // NOLINTNEXTLINE(*-narrowing-conversions) expect(io::sysCheck(::ftruncate(channel.memfd.fd(), channel.total_size)), "failed to truncate channel memory"); + // NOLINTNEXTLINE(misc-const-correctness) void* ptr = expect( io::sysVal(::mmap(nullptr, channel.total_size, PROT_READ | PROT_WRITE, MAP_SHARED, channel.memfd.fd(), 0)), "failed to mmap channel memory"); diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index e8307e5..ac9d0bb 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -24,9 +24,12 @@ #include "fastipc.hxx" #include "tower.hxx" +#include +#include #include "co/coroutine.hxx" #include "co/task.hxx" #include "io/context.hxx" +#include "io/result.hxx" namespace { From 1d7d9526bf1ec74b7adaf65a55962165669f97d0 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:02:04 +0100 Subject: [PATCH 22/41] Remove todos --- src/co/coroutine.hxx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index d6123ff..9d4f82a 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -203,7 +203,6 @@ class SenderAwaiter final { bool await_ready() noexcept { return false; } std::coroutine_handle<> await_suspend(std::coroutine_handle

cont) { - // TODO: somehow operation state needs to be moveable.. auto& operation_state = state.template emplace( std::get(std::move(state)).connect(AwaiterReceiver{this->received, cont})); @@ -272,8 +271,6 @@ class [[nodiscard]] Co final { void start() { m_promise.handle().promise().receiver = &m_receiver; - - // TODO: schedule it m_promise.handle().resume(); } From 6a4c8b17b7634a656035452e506a650ab1685fe8 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:09:51 +0100 Subject: [PATCH 23/41] Update to clang 21 --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1e22a2..c19c877 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,8 +15,8 @@ jobs: ld: MOLD ldflags: - name: Clang/LLVM - cc: clang-20 - cxx: clang++-20 + cc: clang-21 + cxx: clang++-21 cxxflags: -stdlib=libc++ ld: LLD ldflags: -lllvmlibc @@ -28,8 +28,8 @@ jobs: if: matrix.toolchain.name == 'Clang/LLVM' run: | sudo wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key - sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-20 main" - sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-20 lld-20 libllvmlibc-20-dev libc++-20-dev libc++abi-20-dev clang-tidy-20 + sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-21 main" + sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-21 lld-21 libllvmlibc-21-dev libc++-21-dev libc++abi-21-dev clang-tidy-21 - name: Check deps run: | cmake --version @@ -56,5 +56,5 @@ jobs: - name: Lint if: matrix.toolchain.name == 'Clang/LLVM' env: - CLANG_TIDY: clang-tidy-20 + CLANG_TIDY: clang-tidy-21 run: git clang-tidy From 2ec52042660f3a6097c3a37482fe99328e9afa68 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:11:53 +0100 Subject: [PATCH 24/41] gcc :( --- src/tower.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tower.cxx b/src/tower.cxx index c59089b..09e0e74 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -202,7 +202,8 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { std::memcpy(CMSG_DATA(cmsg), &channel.memfd.fd(), sizeof(channel.memfd)); msg.msg_controllen = cmsg->cmsg_len; - static_cast(expect(co_await io::asendmsg(clientfd, msg, 0), "failed to send reply to client")); + auto const send_n = expect(co_await io::asendmsg(clientfd, msg, 0), "failed to send reply to client"); + static_cast(send_n); co_return; } From 53770b478fc97a581950ff031871fc35e0f9860a Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:16:14 +0100 Subject: [PATCH 25/41] Pin clang-format-21? --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 7040814..d98b86e 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -20,6 +20,6 @@ jobs: run: | sudo wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc) main" - sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-format + sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-format-21 - run: git config include.path ../.gitconfig - run: git clang-format From 30fa5f951e9f5bff5d0b2b967ba06091ec137300 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:18:23 +0100 Subject: [PATCH 26/41] try again --- .github/workflows/format.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index d98b86e..84ed13f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -19,7 +19,7 @@ jobs: - name: Fetch ClangFormat run: | sudo wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key - sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc) main" - sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-format-21 + sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-21 main" + sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-format - run: git config include.path ../.gitconfig - run: git clang-format From 4ff33c3b35d2ff2a6b4f0c7aee14df10f35b9727 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:19:51 +0100 Subject: [PATCH 27/41] Is 22 available? --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c19c877..e228a6a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,8 +15,8 @@ jobs: ld: MOLD ldflags: - name: Clang/LLVM - cc: clang-21 - cxx: clang++-21 + cc: clang-22 + cxx: clang++-22 cxxflags: -stdlib=libc++ ld: LLD ldflags: -lllvmlibc @@ -28,8 +28,8 @@ jobs: if: matrix.toolchain.name == 'Clang/LLVM' run: | sudo wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key - sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-21 main" - sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-21 lld-21 libllvmlibc-21-dev libc++-21-dev libc++abi-21-dev clang-tidy-21 + sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-22 main" + sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-22 lld-22 libllvmlibc-22-dev libc++-22-dev libc++abi-22-dev clang-tidy-22 - name: Check deps run: | cmake --version @@ -56,5 +56,5 @@ jobs: - name: Lint if: matrix.toolchain.name == 'Clang/LLVM' env: - CLANG_TIDY: clang-tidy-21 + CLANG_TIDY: clang-tidy-22 run: git clang-tidy From 047452d21dc6a21f01155dc56053edb7179e0b7e Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:22:33 +0100 Subject: [PATCH 28/41] Headers --- src/io/fd.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io/fd.hxx b/src/io/fd.hxx index 4a09357..f376511 100644 --- a/src/io/fd.hxx +++ b/src/io/fd.hxx @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "result.hxx" From aa65de07ae5cc662d7cb70a96ab327e1e86fd6d7 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:36:00 +0100 Subject: [PATCH 29/41] don't use move_only_function --- src/co/scheduler.hxx | 4 ++-- src/io/reactor.hxx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx index 958ce7d..cc38c5d 100644 --- a/src/co/scheduler.hxx +++ b/src/co/scheduler.hxx @@ -30,7 +30,7 @@ class Scheduler final { public: explicit Scheduler(io::Reactor* reactor = nullptr) : m_reactor(reactor) {} - void schedule(std::move_only_function fn) { + void schedule(std::function fn) { auto lock = std::scoped_lock{m_child_lock}; m_queue.push(std::move(fn)); @@ -60,7 +60,7 @@ class Scheduler final { private: mutable std::recursive_mutex m_child_lock; - std::queue> m_queue; + std::queue> m_queue; io::Reactor* m_reactor; }; diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index d222531..346d23b 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -43,10 +43,10 @@ class Reactor final { public: struct Registration { int fd; - std::move_only_function read_cb; - std::move_only_function write_cb; + std::function read_cb; + std::function write_cb; - void callback(io::Direction direction, std::move_only_function cb) noexcept { + void callback(io::Direction direction, std::function cb) noexcept { // could make thread safe if we want to interrupt from different threads... auto old = std::exchange(direction == io::Direction::Read ? read_cb : write_cb, std::move(cb)); From a14f965b04f902640e75e62f9748c72656f6e1a8 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Tue, 10 Mar 2026 22:36:37 +0100 Subject: [PATCH 30/41] Move back to llvm 21 --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e228a6a..c19c877 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,8 +15,8 @@ jobs: ld: MOLD ldflags: - name: Clang/LLVM - cc: clang-22 - cxx: clang++-22 + cc: clang-21 + cxx: clang++-21 cxxflags: -stdlib=libc++ ld: LLD ldflags: -lllvmlibc @@ -28,8 +28,8 @@ jobs: if: matrix.toolchain.name == 'Clang/LLVM' run: | sudo wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key - sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-22 main" - sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-22 lld-22 libllvmlibc-22-dev libc++-22-dev libc++abi-22-dev clang-tidy-22 + sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-21 main" + sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-21 lld-21 libllvmlibc-21-dev libc++-21-dev libc++abi-21-dev clang-tidy-21 - name: Check deps run: | cmake --version @@ -56,5 +56,5 @@ jobs: - name: Lint if: matrix.toolchain.name == 'Clang/LLVM' env: - CLANG_TIDY: clang-tidy-22 + CLANG_TIDY: clang-tidy-21 run: git clang-tidy From 134fdddba0e488f57c153d0d4d7dc7248a2c19c9 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Fri, 13 Mar 2026 23:27:28 +0100 Subject: [PATCH 31/41] Fix deadlock and yield --- src/co/scheduler.hxx | 1 + src/io/context.hxx | 26 ++++++++++++++++++++++++++ src/io/polled_fd.hxx | 2 +- src/io/reactor.hxx | 5 +---- src/tower.cxx | 3 +++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx index cc38c5d..fca9c28 100644 --- a/src/co/scheduler.hxx +++ b/src/co/scheduler.hxx @@ -30,6 +30,7 @@ class Scheduler final { public: explicit Scheduler(io::Reactor* reactor = nullptr) : m_reactor(reactor) {} + // optimize by making a custom callable interface void schedule(std::function fn) { auto lock = std::scoped_lock{m_child_lock}; diff --git a/src/io/context.hxx b/src/io/context.hxx index 106cb96..1e37217 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -86,4 +86,30 @@ class Runtime final { std::unique_ptr m_scheduler; }; +class YieldSender final { + public: + using value_type = void; + + explicit YieldSender(co::Scheduler& scheduler) : m_scheduler{&scheduler} {} + + template + auto connect(R&& receiver) { + struct OperationState final { + R receiver; + co::Scheduler* scheduler; + + void start() { + scheduler->schedule([this]() { receiver.set_value(); }); + } + }; + + return OperationState{std::forward(receiver), m_scheduler}; + } + + private: + co::Scheduler* m_scheduler; +}; + +inline YieldSender yield() { return YieldSender{Runtime::singleton().scheduler()}; } + } // namespace fastipc::io \ No newline at end of file diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 742db17..8230500 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -176,7 +176,7 @@ class TryIoSender final { struct StopFn final { OperationState* self; - void operator()() noexcept { self->m_fd->m_registration->callback(self->m_direction, {}); } + void operator()() noexcept { self->m_fd->m_registration->callback(self->m_direction, {}); self->poll(); } }; // cb is not moveable.. diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index 346d23b..b6078b2 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -47,12 +47,9 @@ class Reactor final { std::function write_cb; void callback(io::Direction direction, std::function cb) noexcept { - // could make thread safe if we want to interrupt from different threads... auto old = std::exchange(direction == io::Direction::Read ? read_cb : write_cb, std::move(cb)); - if (old) { - old(); - } + static_cast(old); } }; diff --git a/src/tower.cxx b/src/tower.cxx index 09e0e74..8e7e9f0 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -43,6 +43,7 @@ #include #include "co/coroutine.hxx" +#include "io/context.hxx" #include "io/cursor.hxx" #include "io/fd.hxx" #include "io/polled_fd.hxx" @@ -148,6 +149,8 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}, stop_token), "failed to read from client"); + co_await io::yield(); + auto recvbuf = std::span{buf}.first(bytes_read); const auto request = expect(expect(readClientRequest(recvbuf), "invalid request"), "incomplete message"); From 7d9a509e64a8978ce2c6638a4f0e0fab09cf3892 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 09:46:21 +0100 Subject: [PATCH 32/41] Review comments --- src/co/coroutine.hxx | 3 +-- src/io/reactor.hxx | 10 +--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index 9d4f82a..352a2d3 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -74,8 +74,7 @@ class [[nodiscard]] Promise final { } } - std::coroutine_handle> handle() { return m_handle; } - std::coroutine_handle> handle() const { return m_handle; } + [[nodiscard]] std::coroutine_handle> handle() const { return m_handle; } private: std::coroutine_handle> m_handle; diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index b6078b2..4ad7b42 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -53,15 +53,7 @@ class Reactor final { } }; - static constexpr auto kDefaultMaxEvents = 512ul; - - Reactor(const Reactor&) noexcept = delete; - Reactor& operator=(const Reactor&) noexcept = delete; - - Reactor(Reactor&&) noexcept = default; - Reactor& operator=(Reactor&&) noexcept = default; - - ~Reactor() = default; + static constexpr auto kDefaultMaxEvents = 512uz; static expected create(std::size_t max_events = kDefaultMaxEvents) noexcept; expected react(std::optional timeout) noexcept; From 8c80d6bc17a1dfe2c065d06c75853e066ef62242 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 10:08:25 +0100 Subject: [PATCH 33/41] Remove exposition only yield --- src/tower.cxx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tower.cxx b/src/tower.cxx index 8e7e9f0..8d49972 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -149,8 +149,6 @@ co::Co Tower::serve(io::PolledFd clientfd, std::stop_token stop_token) { const auto bytes_read = expect(co_await io::aread(clientfd, std::span{buf}, stop_token), "failed to read from client"); - co_await io::yield(); - auto recvbuf = std::span{buf}.first(bytes_read); const auto request = expect(expect(readClientRequest(recvbuf), "invalid request"), "incomplete message"); From 5f4fabd4ca44f10fca070bfe89fd9caad06f36bd Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 10:12:44 +0100 Subject: [PATCH 34/41] Copyright + EOF --- src/co/coroutine.hxx | 18 ++++++++++++++++++ src/co/received.hxx | 2 +- src/co/scheduler.hxx | 2 +- src/co/task.hxx | 2 +- src/io/context.hxx | 19 +++++++++++++++++++ src/visitor.hxx | 20 +++++++++++++++++++- 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/co/coroutine.hxx b/src/co/coroutine.hxx index 352a2d3..203a75b 100644 --- a/src/co/coroutine.hxx +++ b/src/co/coroutine.hxx @@ -1,3 +1,21 @@ +/* + * coroutine.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + #pragma once #include diff --git a/src/co/received.hxx b/src/co/received.hxx index 6abe36b..2785e6b 100644 --- a/src/co/received.hxx +++ b/src/co/received.hxx @@ -92,4 +92,4 @@ struct Received final { std::variant m_value; }; -} // namespace fastipc::co \ No newline at end of file +} // namespace fastipc::co diff --git a/src/co/scheduler.hxx b/src/co/scheduler.hxx index fca9c28..e55d15e 100644 --- a/src/co/scheduler.hxx +++ b/src/co/scheduler.hxx @@ -66,4 +66,4 @@ class Scheduler final { io::Reactor* m_reactor; }; -} // namespace fastipc::co \ No newline at end of file +} // namespace fastipc::co diff --git a/src/co/task.hxx b/src/co/task.hxx index 4077dc8..734ebcb 100644 --- a/src/co/task.hxx +++ b/src/co/task.hxx @@ -172,4 +172,4 @@ JoinHandle spawn(S&& sender) { return JoinHandle{std::move(state)}; } -} // namespace fastipc::co \ No newline at end of file +} // namespace fastipc::co diff --git a/src/io/context.hxx b/src/io/context.hxx index 1e37217..459722d 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -1,4 +1,23 @@ +/* + * context.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + #pragma once + #include #include #include diff --git a/src/visitor.hxx b/src/visitor.hxx index fc95a33..4ef8b81 100644 --- a/src/visitor.hxx +++ b/src/visitor.hxx @@ -1,3 +1,21 @@ +/* + * visitor.hxx + * Copyright 2025 ItJustWorksTM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + #pragma once #include @@ -15,4 +33,4 @@ decltype(auto) match(V&& variant, Ts&&... arms) { return std::visit(Visitor{std::forward(arms)...}, std::forward(variant)); } -} // namespace fastipc \ No newline at end of file +} // namespace fastipc From 89efa4955c8d66c48eb9850e3d6c9806cc6db5ee Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 10:14:00 +0100 Subject: [PATCH 35/41] Revert back to cxx17 on public interface --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45b6ae0..c854b3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ if (NOT DEFINED CMAKE_CXX_CLANG_TIDY OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Cla target_precompile_headers (fastipc PUBLIC include/fastipc.hxx) endif () -target_compile_features (fastipc INTERFACE cxx_std_23) +target_compile_features (fastipc INTERFACE cxx_std_17) target_compile_features (fastipc PRIVATE cxx_std_23) target_compile_options (fastipc PRIVATE ${FASTIPC_COMPILE_OPTIONS} -Wno-zero-length-array) target_include_directories (fastipc PUBLIC src) From 41b6d4591b90168b729f5a1ab538ac99f3ced6b3 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 10:17:49 +0100 Subject: [PATCH 36/41] More linting --- src/io/context.hxx | 3 +-- src/tower.cxx | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/io/context.hxx b/src/io/context.hxx index 459722d..84ee00c 100644 --- a/src/io/context.hxx +++ b/src/io/context.hxx @@ -24,7 +24,6 @@ #include #include "co/received.hxx" #include "co/scheduler.hxx" -#include "co/task.hxx" #include "io/result.hxx" #include "reactor.hxx" @@ -131,4 +130,4 @@ class YieldSender final { inline YieldSender yield() { return YieldSender{Runtime::singleton().scheduler()}; } -} // namespace fastipc::io \ No newline at end of file +} // namespace fastipc::io diff --git a/src/tower.cxx b/src/tower.cxx index 8d49972..09e0e74 100644 --- a/src/tower.cxx +++ b/src/tower.cxx @@ -43,7 +43,6 @@ #include #include "co/coroutine.hxx" -#include "io/context.hxx" #include "io/cursor.hxx" #include "io/fd.hxx" #include "io/polled_fd.hxx" From 07b4794d3a64c7f51a5bad61e72aa268a37d9f9b Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 10:36:30 +0100 Subject: [PATCH 37/41] Guard against interruptions --- src/io/polled_fd.hxx | 4 ++-- src/io/reactor.cxx | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index 8230500..b8f31e3 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -36,7 +36,7 @@ class StoppedException final : public std::exception { [[nodiscard]] const char* what() const noexcept override { return "operation stopped"; } }; -constexpr bool is_error_blocking(std::error_code error) noexcept { +constexpr bool isErrorBlocking(std::error_code error) noexcept { return error == std::errc::operation_would_block || error == std::errc::resource_unavailable_try_again; } @@ -135,7 +135,7 @@ class TryIoSender final { return; } - if (!is_error_blocking(res.error())) { + if (!isErrorBlocking(res.error())) { set_value(std::move(res)); return; } diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index fae6c60..b5191b0 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -68,6 +68,12 @@ expected> Reactor::wait(std::optional(m_events_buf_.size()), timeout_ms)); + if (!wait_res.has_value()) { + if (wait_res.error() == std::errc::interrupted) { + return {}; + } + } + return wait_res.transform([this](int n) { return std::span{m_events_buf_}.first(static_cast(n)); }); } From 0547c0cf15593497dab8e66c5e0d8207ed7e0a7e Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 10:43:20 +0100 Subject: [PATCH 38/41] More interruption guards --- src/io/polled_fd.hxx | 34 +++++++++++++++++++++------------- src/io/reactor.cxx | 1 + 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/io/polled_fd.hxx b/src/io/polled_fd.hxx index b8f31e3..1ef0d12 100644 --- a/src/io/polled_fd.hxx +++ b/src/io/polled_fd.hxx @@ -56,9 +56,7 @@ class PolledFd final { } } - static expected create(Fd fd) noexcept { - return create(std::move(fd), Runtime::singleton().reactor()); - } + static expected create(Fd fd) noexcept { return create(std::move(fd), Runtime::singleton().reactor()); } static expected create(Fd fd, Reactor& reactor) noexcept { return setBlocking(fd, false) @@ -128,20 +126,27 @@ class TryIoSender final { return; } - auto res = m_io(); + for (;;) { + auto res = m_io(); - if (res.has_value()) { - set_value(std::move(res)); - return; - } + if (res.has_value()) { + set_value(std::move(res)); + return; + } + + if (res.error() == std::errc::interrupted) { + continue; + } + + if (isErrorBlocking(res.error())) { + m_state = State::Blocked; + m_fd->m_registration->callback(m_direction, [this]() { poll(); }); + return; + } - if (!isErrorBlocking(res.error())) { set_value(std::move(res)); return; } - - m_state = State::Blocked; - m_fd->m_registration->callback(m_direction, [this]() { poll(); }); } void set_value(result_type value) { @@ -176,7 +181,10 @@ class TryIoSender final { struct StopFn final { OperationState* self; - void operator()() noexcept { self->m_fd->m_registration->callback(self->m_direction, {}); self->poll(); } + void operator()() noexcept { + self->m_fd->m_registration->callback(self->m_direction, {}); + self->poll(); + } }; // cb is not moveable.. diff --git a/src/io/reactor.cxx b/src/io/reactor.cxx index b5191b0..f98473e 100644 --- a/src/io/reactor.cxx +++ b/src/io/reactor.cxx @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From d1550e451eafc211af0a9b616545446d98aa87f7 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 13:21:21 +0100 Subject: [PATCH 39/41] Fix cmake --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c854b3a..443b10c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ endif () target_compile_features (fastipc INTERFACE cxx_std_17) target_compile_features (fastipc PRIVATE cxx_std_23) target_compile_options (fastipc PRIVATE ${FASTIPC_COMPILE_OPTIONS} -Wno-zero-length-array) -target_include_directories (fastipc PUBLIC src) +target_include_directories (fastipc PRIVATE src) add_library (tower OBJECT) target_include_directories (tower PUBLIC src) From 5bcd5cc70e83024283b25eefe5e945e9904fb688 Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 13:22:02 +0100 Subject: [PATCH 40/41] Remove void comment --- test/intraprocess.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/intraprocess.cxx b/test/intraprocess.cxx index ac9d0bb..1cdab05 100644 --- a/test/intraprocess.cxx +++ b/test/intraprocess.cxx @@ -80,7 +80,7 @@ fastipc::co::Co co_main() { std::println("run done!"); - co_return 0; // too lazy for void + co_return 0; } } // namespace From 073f1365d3ef17c2f2c79f8ff70f5417df35141f Mon Sep 17 00:00:00 2001 From: Ruthger Dijt Date: Sun, 15 Mar 2026 13:24:22 +0100 Subject: [PATCH 41/41] Cleanup registration callback --- src/io/reactor.hxx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/io/reactor.hxx b/src/io/reactor.hxx index 4ad7b42..4ffdfb8 100644 --- a/src/io/reactor.hxx +++ b/src/io/reactor.hxx @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -47,9 +46,8 @@ class Reactor final { std::function write_cb; void callback(io::Direction direction, std::function cb) noexcept { - auto old = std::exchange(direction == io::Direction::Read ? read_cb : write_cb, std::move(cb)); - - static_cast(old); + auto& rw_cb = direction == io::Direction::Read ? read_cb : write_cb; + rw_cb = std::move(cb); } };