From 304a29a64c500e84459b2cb09cc86452cdba50a3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 29 Dec 2025 12:49:53 -0800 Subject: [PATCH] Modernize route_params async support - Fix spawn() to use capy::executor::post - Add capy::executor member to route_params - Add exception_ptr overload to resumer for proper error propagation - Remove post() and polling infrastructure in favor of executor --- .../boost/http_proto/server/route_handler.hpp | 137 +----------------- .../boost/http_proto/server/router_types.hpp | 23 ++- src/server/route_handler.cpp | 38 +++-- 3 files changed, 50 insertions(+), 148 deletions(-) diff --git a/include/boost/http_proto/server/route_handler.hpp b/include/boost/http_proto/server/route_handler.hpp index 1766e74b..391813b9 100644 --- a/include/boost/http_proto/server/route_handler.hpp +++ b/include/boost/http_proto/server/route_handler.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include // VFALCO forward declare? #include // VFALCO forward declare? @@ -81,6 +82,10 @@ struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE */ suspender suspend; + /** Executor associated with the session. + */ + capy::executor ex; + /** Destructor */ BOOST_HTTP_PROTO_DECL @@ -176,70 +181,13 @@ struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE from the route handler. */ BOOST_HTTP_PROTO_DECL - virtual auto spawn( + auto spawn( capy::task coro) -> route_result; #endif - // VFALCO this doc isn't quite right because it doesn't explain - // the possibility that post will return the final result immediately, - // and it needs to remind the user that calling a function which - // returns route_result means they have to return the value right away - // without doing anything else. - // - // VFALCO we have to detect calls to suspend inside `f` and throw - // - /** Submit cooperative work. - - This function suspend the current handler from the session, - and immediately invokes the specified function object @p f. - When the function returns normally, the function object is - placed into an implementation-defined work queue to be invoked - again. Otherwise, if the function calls `resume(rv)` then the - session is resumed and behaves as if the original route handler - had returned the value `rv`. - - When the function object is invoked, it runs in the same context - as the original handler invocation. If the function object - attempts to call @ref post again, or attempts to call @ref suspend - an exception is thrown. - - The function object @p f must have this equivalent signature: - @code - void ( resumer resume ); - @endcode - - @param f The function object to invoke. - @param c The continuation function to be invoked when f finishes. - */ - template - auto - post(F&& f) -> route_result; - protected: - /** A task to be invoked later - */ - struct task - { - virtual ~task() = default; - - /** Invoke the task. - - @return true if the task resumed the session. - */ - virtual bool invoke() = 0; - }; - - /** Post task_ to be invoked later - - Subclasses must schedule task_ to be invoked at an unspecified - point in the future. - */ - BOOST_HTTP_PROTO_DECL - virtual void do_post(); - - std::unique_ptr task_; std::function finish_; }; @@ -292,79 +240,6 @@ read_body( //----------------------------------------------- -template -auto -route_params:: -post(F&& f) -> route_result -{ - // task already posted - if(task_) - detail::throw_invalid_argument(); - - struct immediate : suspender::owner - { - route_result rv; - bool set = false; - void do_resume( - route_result const& rv_) override - { - rv = rv_; - set = true; - } - }; - - class model: public task, suspender::owner - { - public: - model(route_params& p, - F&& f, resumer resume) - : p_(p) - , f_(std::forward(f)) - , resume_(resume) - { - } - - bool invoke() override - { - resumed_ = false; - // VFALCO analyze exception safety - f_(resumer(*this)); - return resumed_; - } - - void do_resume( - route_result const& rv) override - { - resumed_ = true; - resumer resume(resume_); - p_.task_.reset(); // destroys *this - resume(rv); - } - - private: - route_params& p_; - typename std::decay::type f_; - resumer resume_; - bool resumed_; - }; - - // first call - immediate impl; - f(resumer(impl)); - if(impl.set) - return impl.rv; - - return suspend( - [&](resumer resume) - { - task_ = std::unique_ptr(new model( - *this, std::forward(f), resume)); - do_post(); - }); -} - -//----------------------------------------------- - #ifdef BOOST_HTTP_PROTO_HAS_CORO /** Create a route handler from a coroutine function diff --git a/include/boost/http_proto/server/router_types.hpp b/include/boost/http_proto/server/router_types.hpp index b52cb647..b9948750 100644 --- a/include/boost/http_proto/server/router_types.hpp +++ b/include/boost/http_proto/server/router_types.hpp @@ -167,8 +167,10 @@ class suspender struct BOOST_HTTP_PROTO_SYMBOL_VISIBLE owner { - BOOST_HTTP_PROTO_DECL virtual resumer do_suspend(); + BOOST_HTTP_PROTO_DECL + virtual resumer do_suspend(); virtual void do_resume(route_result const&) = 0; + virtual void do_resume(std::exception_ptr) = 0; }; suspender() = default; @@ -253,7 +255,7 @@ class resumer When a session is resumed, routing continues as if the handler had returned the @ref route_result contained in @p rv. - @param ec The error code to resume with. + @param rv The route result to resume with. @throw std::invalid_argument If the object is empty. */ @@ -265,6 +267,23 @@ class resumer p_->do_resume(rv); } + /** Resume the session with an exception + + When a session is resumed with an exception, the exception + is propagated through the router's error handling mechanism. + + @param ep The exception to propagate. + + @throw std::invalid_argument If the object is empty. + */ + void operator()( + std::exception_ptr ep) const + { + if(! p_) + detail::throw_invalid_argument(); + p_->do_resume(ep); + } + private: suspender::owner* p_ #if defined(__clang__) diff --git a/src/server/route_handler.cpp b/src/server/route_handler.cpp index 26275fbe..7e4f92ac 100644 --- a/src/server/route_handler.cpp +++ b/src/server/route_handler.cpp @@ -9,8 +9,6 @@ #include #include -#include -#include namespace boost { namespace http_proto { @@ -56,24 +54,34 @@ set_body(std::string s) auto route_params:: spawn( - capy::task) -> + capy::task t) -> route_result { - detail::throw_invalid_argument(); -} + return this->suspend( + [ex = this->ex, t = std::move(t)](resumer resume) mutable + { + auto h = t.release(); -#endif + h.promise().on_done = [resume, h]() + { + auto& r = h.promise().result; + if(r.index() == 2) + { + auto ep = std::get<2>(r); + h.destroy(); + resume(ep); + return; + } + auto rv = std::move(std::get<1>(r)); + h.destroy(); + resume(rv); + }; -void -route_params:: -do_post() -{ - BOOST_ASSERT(task_); - // invoke until task resumes - for(;;) - if(task_->invoke()) - break; + ex.post([h]() { h.resume(); }); + }); } +#endif + } // http_proto } // boost