Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 6 additions & 131 deletions include/boost/http_proto/server/route_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <boost/http_proto/detail/config.hpp>
#include <boost/http_proto/server/router_types.hpp>
#include <boost/capy/datastore.hpp>
#include <boost/capy/executor.hpp>
#include <boost/capy/task.hpp>
#include <boost/http_proto/request.hpp> // VFALCO forward declare?
#include <boost/http_proto/request_parser.hpp> // VFALCO forward declare?
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<route_result> 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<class F>
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> task_;
std::function<void(void)> finish_;
};

Expand Down Expand Up @@ -292,79 +240,6 @@ read_body(

//-----------------------------------------------

template<class F>
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>(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<F>::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<task>(new model(
*this, std::forward<F>(f), resume));
do_post();
});
}

//-----------------------------------------------

#ifdef BOOST_HTTP_PROTO_HAS_CORO

/** Create a route handler from a coroutine function
Expand Down
23 changes: 21 additions & 2 deletions include/boost/http_proto/server/router_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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__)
Expand Down
38 changes: 23 additions & 15 deletions src/server/route_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

#include <boost/http_proto/server/route_handler.hpp>
#include <boost/http_proto/string_body.hpp>
#include <boost/assert.hpp>
#include <cstring>

namespace boost {
namespace http_proto {
Expand Down Expand Up @@ -56,24 +54,34 @@ set_body(std::string s)
auto
route_params::
spawn(
capy::task<route_result>) ->
capy::task<route_result> 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
Loading