New Context object for composed operations and continuation hooks

This commit is contained in:
Vinnie Falco
2013-08-18 00:12:14 -07:00
parent d0e4352582
commit 04b5c7f447
7 changed files with 954 additions and 216 deletions

View File

@@ -302,6 +302,12 @@
<ClInclude Include="BeastConfig.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\modules\beast_asio\basics\beast_HandlerCall.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\beast_asio\basics\beast_PeerRole.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>

View File

@@ -1315,6 +1315,9 @@
<ClCompile Include="..\..\modules\beast_asio\handshake\beast_HandshakeDetectLogicPROXY.cpp">
<Filter>beast_asio\handshake</Filter>
</ClCompile>
<ClCompile Include="..\..\modules\beast_asio\basics\beast_HandlerCall.cpp">
<Filter>beast_asio\basics</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Text Include="..\..\TODO.txt" />

View File

@@ -0,0 +1,353 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
//
// Context
//
//------------------------------------------------------------------------------
HandlerCall::Context::Context (Call* call) noexcept
: m_call (call)
{
bassert (m_call != nullptr);
}
HandlerCall::Context::Context () noexcept
: m_call (nullptr)
{
}
HandlerCall::Context::Context (Context const& other) noexcept
: m_call (other.m_call)
{
}
HandlerCall::Context::Context (HandlerCall const& handler) noexcept
: m_call (handler.m_call.get ())
{
}
HandlerCall::Context& HandlerCall::Context::operator= (Context other) noexcept
{
m_call = other.m_call;
return *this;
}
bool HandlerCall::Context::operator== (Call const* call) const noexcept
{
return m_call == call;
}
bool HandlerCall::Context::operator!= (Call const* call) const noexcept
{
return m_call != call;
}
bool HandlerCall::Context::isComposed () const noexcept
{
return m_call->is_continuation ();
}
bool HandlerCall::Context::isNull () const noexcept
{
return m_call == nullptr;
}
bool HandlerCall::Context::isNotNull () const noexcept
{
return m_call != nullptr;
}
bool HandlerCall::Context::operator== (Context other) const noexcept
{
return m_call == other.m_call;
}
bool HandlerCall::Context::operator!= (Context other) const noexcept
{
return m_call != other.m_call;
}
void* HandlerCall::Context::allocate (std::size_t size) const
{
return m_call->allocate (size);
}
void HandlerCall::Context::deallocate (void* p, std::size_t size) const
{
m_call->deallocate (p, size);
}
//------------------------------------------------------------------------------
//
// Call
//
//------------------------------------------------------------------------------
HandlerCall::Call::Call (Context context) noexcept
: m_context (context.isNull () ? Context (this) : context)
, m_is_continuation (false)
, m_is_final_continuation (false)
{
}
HandlerCall::Call::~Call ()
{
}
HandlerCall::Context HandlerCall::Call::getContext () const noexcept
{
return m_context;
}
bool HandlerCall::Call::is_continuation () const noexcept
{
// If this goes off it means someone isn't calling getContext()!
bassert (m_context == this);
return m_is_continuation;
}
void HandlerCall::Call::set_continuation () noexcept
{
// Setting it twice means some code is sloppy!
bassert (! m_is_continuation);
m_is_continuation = true;
}
void HandlerCall::Call::set_final_continuation () noexcept
{
// Soemone called endComposed without calling beginComposed!
bassert (m_is_continuation);
// When true, we will clear
// m_is_continuation on our next completion
m_is_final_continuation = true;
}
void HandlerCall::Call::check_continuation () noexcept
{
if (m_is_final_continuation)
{
bassert (m_is_continuation);
m_is_continuation = false;
m_is_final_continuation = false;
}
}
void HandlerCall::Call::operator() ()
{
check_continuation ();
dispatch ();
}
void HandlerCall::Call::operator() (error_code const& ec)
{
check_continuation ();
dispatch (ec);
}
void HandlerCall::Call::operator() (error_code const& ec, std::size_t bytes_transferred)
{
check_continuation ();
dispatch (ec, bytes_transferred);
}
void HandlerCall::Call::dispatch ()
{
pure_virtual_called ();
}
void HandlerCall::Call::dispatch (error_code const&)
{
pure_virtual_called ();
}
void HandlerCall::Call::dispatch (error_code const&, std::size_t)
{
pure_virtual_called ();
}
void* HandlerCall::Call::pure_virtual_called ()
{
// These shouldn't be getting called. But since the object returned
// by most implementations of bind have operator() up to high arity
// levels, it is not generally possible to write a traits test that
// works in all scenarios for detecting a particular signature of a
// handler.
//
fatal_error ("pure virtual called");
return nullptr;
}
//------------------------------------------------------------------------------
//
// HandlerCall
//
//------------------------------------------------------------------------------
HandlerCall::HandlerCall () noexcept
{
}
HandlerCall::HandlerCall (HandlerCall const& other) noexcept
: m_call (other.m_call)
{
}
HandlerCall& HandlerCall::operator= (HandlerCall const& other) noexcept
{
m_call = other.m_call;
return *this;
}
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
HandlerCall::HandlerCall (HandlerCall&& other) noexcept
: m_call (other.m_call)
{
other.m_call = nullptr;
}
HandlerCall& HandlerCall::operator= (HandlerCall&& other) noexcept
{
m_call = other.m_call;
other.m_call = nullptr;
return *this;
}
#endif
bool HandlerCall::isNull () const noexcept
{
return m_call == nullptr;
}
bool HandlerCall::isNotNull () const noexcept
{
return m_call != nullptr;
}
HandlerCall::Context HandlerCall::getContext () const noexcept
{
bassert (m_call != nullptr);
return m_call->getContext ();
}
bool HandlerCall::isFinal () const noexcept
{
return m_call->getContext () == m_call.get ();
}
HandlerCall& HandlerCall::beginComposed () noexcept
{
// If this goes off it means that your handler is
// already sharing a context with another handler!
// You have to call beginComposed on the original handler.
//
bassert (isFinal ());
m_call->set_continuation ();
return *this;
}
HandlerCall& HandlerCall::endComposed () noexcept
{
// If this goes off it means that your handler is
// already sharing a context with another handler!
// You have to call beginComposed on the original handler.
//
bassert (isFinal ());
m_call->set_final_continuation ();
return *this;
}
void HandlerCall::operator() ()
{
(*m_call)();
}
void HandlerCall::operator() (error_code const& ec)
{
(*m_call)(ec);
}
void HandlerCall::operator() (error_code const& ec, std::size_t bytes_transferred)
{
(*m_call)(ec, bytes_transferred);
}
//------------------------------------------------------------------------------
//
// Specializations
//
//------------------------------------------------------------------------------
void* asio_handler_allocate (std::size_t size, HandlerCall* call)
{
// Always go through the call's context.
return call->getContext ().allocate (size);
}
void* asio_handler_allocate (std::size_t size, HandlerCall::Call* call)
{
// Always go through the call's context.
return call->getContext ().allocate (size);
}
void* asio_handler_allocate (std::size_t size, HandlerCall::Context* context)
{
return context->allocate (size);
}
//------------------------------------------------------------------------------
void asio_handler_deallocate (void* p, std::size_t size, HandlerCall* call)
{
// Always go through the call's context.
call->getContext ().deallocate (p, size);
}
void asio_handler_deallocate (void* p, std::size_t size, HandlerCall::Call* call)
{
// Always go through the call's context.
call->getContext ().deallocate (p, size);
}
void asio_handler_deallocate (void* p, std::size_t size, HandlerCall::Context* context)
{
context->deallocate (p, size);
}
//------------------------------------------------------------------------------
bool asio_handler_is_continuation (HandlerCall* call)
{
return call->getContext().isComposed ();
}
bool asio_handler_is_continuation (HandlerCall::Call* call)
{
return call->getContext().isComposed ();
}
bool asio_handler_is_continuation (HandlerCall::Context*)
{
// Something is horribly wrong if we're trying to
// use a Context as a completion handler?
//
fatal_error ("A function was unexpectedly called.");
return false;
}

View File

@@ -43,22 +43,56 @@ class HandlerCall
{
private:
typedef boost::system::error_code error_code;
typedef boost::function <void(void)> invoked_type;
// Forward declarations needed for friendship
template <typename>
struct CallType;
struct Call;
public:
typedef void result_type;
// Really there are only 3 kings of functions.
// Except for the composed connect, which we haven't done yet.
struct Context;
//--------------------------------------------------------------------------
/** HandlerCall construction tags.
These tags are used at the end of HandlerCall constructor parameter
lists to tell it what kind of Handler you are passing. For example:
@code
struct MyClass
{
void on_connect (error_code const& ec);
void connect (Address address)
{
HandlerCall myHandler (
bind (&MyClass::foo, this),
Connect ());
socket.async_connect (address, myHandler);
}
};
@endcode
It would be nice if we could deduce the type of handler from the template
argument alone, but the return value of most implementations of bind seem
to satisfy the traits of ANY handler so that was scrapped.
*/
/** @{ */
// These are the three basic supported function signatures
//
// ComposedConnectHandler is to-do
//
struct Post { }; // void()
struct Error { }; // void(error_code)
struct Transfer { }; // void(error_code, std::size_t)
// These tags tell us what kind of Handler we have.
// It would be nice if we could deduce this, but the
// return value of a bind() seems to satisfy the
// requirements of ANY handler so that was scrapped.
// CompletionHandler
//
// http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/CompletionHandler.html
@@ -99,177 +133,454 @@ public:
// http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/BufferedHandshakeHandler.html
//
typedef Transfer BufferedHandshake;
/** @} */
//--------------------------------------------------------------------------
HandlerCall () noexcept
{
}
/** Construct a null HandlerCall.
A default constructed handler has no associated call. Passing
it as a handler to an asynchronous operation will result in
undefined behavior. This constructor exists so that you can do
things like have a class member "originalHandler" which starts
out as null, and then later assign it. Routines are provided to
test the handler against null.
*/
HandlerCall () noexcept;
/** Construct a HandlerCall.
Handler must meet this requirement:
CompletionHandler
HandlerCall will meet this requirement:
CompletionHandler
*/
template <typename Handler>
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Completion)
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Completion,
Context context = Context ())
: m_call (construct <PostCallType> (
BOOST_ASIO_MOVE_CAST(Handler)(handler)))
BOOST_ASIO_MOVE_CAST(Handler)(handler),
context))
{
}
/** Construct a HandlerCall with one bound parameter.
Produce a CompletionHandler that includes one bound parameter.
This can be useful if you want to call io_service::post() on
the handler and you need to give it an already existing value,
like an error_code.
Invoking operator() on the HandlerCall will be the same as:
@code
handler (arg1);
@endcode
Handler must meet one of these requirements:
AcceptHandler
ConnectHandler
ShutdownHandler
HandshakeHandler
HandlerCall will meet this requirement:
CompletionHandler
*/
template <typename Handler, typename Arg1>
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1, Completion)
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1, Completion,
Context context = Context ())
: m_call (construct <PostCallType1> (
BOOST_ASIO_MOVE_CAST(Handler)(handler), arg1))
BOOST_ASIO_MOVE_CAST(Handler)(handler),
context, arg1))
{
}
/** Construct a HandlerCall with two bound parameters.
Produce a CompletionHandler that includes two bound parameters.
This can be useful if you want to call io_service::post() on
the handler and you need to give it two already existing values,
like an error_code and bytes_transferred.
Invoking operator() on the HandlerCall will be the same as:
@code
handler (arg1, arg2);
@endcode
Handler must meet one of these requirements:
ReadHandler
WriteHandler
BufferedHandshakeHandler
The HandlerCall will meet these requirements:
CompletionHandler
*/
template <typename Handler, typename Arg1, typename Arg2>
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1, Arg2 arg2, Completion)
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler,
Arg1 arg1, Arg2 arg2, Completion, Context context = Context ())
: m_call (construct <PostCallType2> (
BOOST_ASIO_MOVE_CAST(Handler)(handler), arg1, arg2))
BOOST_ASIO_MOVE_CAST(Handler)(handler),
context, arg1, arg2))
{
}
/** Construct a HandlerCall from a handler that takes an error_code
Handler must meet one of these requirements:
AcceptHandler
ConnectHandler
ShutdownHandler
HandshakeHandler
The HandlerCall will meet these requirements:
AcceptHandler
ConnectHandler
ShutdownHandler
HandshakeHandler
*/
template <typename Handler>
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Error)
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Error,
Context context = Context ())
: m_call (construct <ErrorCallType> (
BOOST_ASIO_MOVE_CAST(Handler)(handler)))
BOOST_ASIO_MOVE_CAST(Handler)(handler),
context))
{
}
/** Construct a HandlerCall from a handler that takes an error_code and std::size
Handler must meet one of these requirements:
ReadHandler
WriteHandler
BufferedHandshakeHandler
The HandlerCall will meet these requirements:
ReadHandler
WriteHandler
BufferedHandshakeHandler
*/
template <typename Handler>
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Transfer)
HandlerCall (BOOST_ASIO_MOVE_ARG(Handler) handler, Transfer,
Context context = Context ())
: m_call (construct <TransferCallType> (
BOOST_ASIO_MOVE_CAST(Handler)(handler)))
BOOST_ASIO_MOVE_CAST(Handler)(handler),
context))
{
}
inline HandlerCall (HandlerCall const& other) noexcept
: m_call (other.m_call)
{
}
/** Copy construction and assignment.
inline HandlerCall& operator= (HandlerCall const& other) noexcept
{
m_call = other.m_call;
return *this;
}
HandlerCall is a very lightweight object that holds a shared
pointer to the wrapped handler. It is cheap to copy and pass
around.
Construction and assignment from other HandlerCall objects
executes in constant time, does not allocate or free memory,
and invokes no methods calls on the wrapped handler, except
for the case where the last reference on an existing handler
is removed.
*/
/** @{ */
HandlerCall (HandlerCall const& other) noexcept;
HandlerCall& operator= (HandlerCall const& other) noexcept;
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
inline HandlerCall (HandlerCall && other) noexcept
: m_call (other.m_call)
{
other.m_call = nullptr;
}
inline HandlerCall& operator= (HandlerCall&& other) noexcept
{
m_call = other.m_call;
other.m_call = nullptr;
return *this;
}
HandlerCall (HandlerCall&& other) noexcept;
HandlerCall& operator= (HandlerCall&& other) noexcept;
#endif
/** @} */
inline bool isNull () const noexcept
{
return m_call == nullptr;
}
//--------------------------------------------------------------------------
inline bool isNotNull () const noexcept
{
return m_call != nullptr;
}
/** Returns true if the HandlerCall is null.
inline void operator() ()
{
(*m_call)();
}
A null HandlerCall object may not be passed to
an asynchronous completion function.
*/
bool isNull () const noexcept;
inline void operator() (error_code const& ec)
{
(*m_call)(ec);
}
/** Returns true if the HandlerCall is not null. */
bool isNotNull () const noexcept;
inline void operator() (error_code const& ec, std::size_t bytes_transferred)
/** Retrieve the context associated with a handler.
The context will only be valid while the handler exists. In particular,
if the handler is dispatched all references to its context will become
invalid, and undefined behavior will result.
The typical use case is to acquire the context from a caller provided
completion handler, and use the context in a series of composed
operations. At the conclusion of the composed operations, the original
handler is invoked and the context is no longer needed.
Various methods are provided for easily creating your own completion
handlers that are associated with an existing context.
*/
Context getContext () const noexcept;
/** Determine if this handler is the final handler in a composed chain.
A Context is usually shared during a composed operation.
Normally you won't need to call this but it's useful for diagnostics.
Really what this means it that the context is its own wrapped handler.
*/
bool isFinal () const noexcept;
/** Mark this handler as part of a composed operation.
This is only valid to call if the handler is not already sharing
the context of another handler (i.e. it is already part of a
composed operation).
Any handlers that share the same context will result in true being
passed to the asio_is_continuation_hook. When you are ready to
issue the final call to the original handler (which will also
destroy the context), call endComposed on the original
handler.
To be clear, beginComposed and endComposed are called on the same
HandlerCall object, and that object was not constructed from another
context.
@see isComposed, endComposed
*/
HandlerCall& beginComposed () noexcept;
/** Indicate the end of a composed operation.
A composed operation starts with a handler that uses itself as its own
context. The composed operation issues new asynchronous calls with its
own callback, using the original handler's context. To optimize the
strategy for calling completion handlers, call beginComposed.
*/
HandlerCall& endComposed () noexcept;
/** Invoke the wrapped handler.
Normally you will not need to call this yourself. Since this is a
polymorphic wrapper, any attempt to use an operator that doesn't
correspond to the signature of the wrapped handler (taking into
account arguments bound at construction), will result in a fatal
error at run-time.
*/
/** @{ */
void operator() ();
void operator() (error_code const& ec);
void operator() (error_code const& ec, std::size_t bytes_transferred);
/** @} */
//--------------------------------------------------------------------------
/** The context of execution of a particular handler.
When writing composed operations (a sequence of asynchronous function
calls), it is important that the intermediate handlers run in the same
context as the handler originally provided to signal the end of the
composed operation.
An object of this type abstracts the execution context of any handler.
You can extract the context from an existing handler and associate
new handlers you create with that context. This allows composed
operations to be written easily, lifting the burden of meeting the
composed operation requirements.
In all cases, the Context will only be valid while the original handler
exists. It is the caller's responsibility to manage the usage and
lifetimes of these objects.
Context objects are lightweight and just hold a reference to the
underlying context. They are cheap to copy and pass around.
Supports these concepts:
DefaultConstructible
CopyConstructible
CopyAssignable
Destructible
*/
struct Context
{
(*m_call)(ec, bytes_transferred);
}
/** Construct a null Context.
When a null Context is specified as the Context to use when
creating a HandlerCall, it means to use the wrapped handler
as its own context. This is the default behavior.
*/
Context () noexcept;
/** Construct a Context from another Context. */
Context (Context const& other) noexcept;
/** Construct a Context from an existing handler's Context. */
Context (HandlerCall const& handler) noexcept;
/** Assign this Context from another Context. */
Context& operator= (Context other) noexcept;
/** Determine if this context is a composed asynchronous operation.
When a handler begins a composed operation it becomes its own
context (it is not constructed with a specified context). When
a composed operation starts, this will return true for all
handlers which share the context including the original handler.
You have to indicate that a composed operation is starting by
calling beginComposed on the original handler, performing
your operations using its context, and then call endComposed
before calling the original handler.
@see beginComposed, endComposed
*/
bool isComposed () const noexcept;
/** Determine whether or not this Context is a null Context.
Note that a non-null Context is no guarantee of
the validity of the context.
*/
/** @{ */
bool isNull () const noexcept;
bool isNotNull () const noexcept;
/** @} */
/** Compare two contexts.
This determines if the two contexts refer to the same underlying
completion handler and would have the same execution guarantees.
The behavior is undefined if either of the contexts have been
destroyed.
*/
/** @{ */
bool operator== (Context other) const noexcept;
bool operator!= (Context other) const noexcept;
/** @} */
/** Allocate and deallocate memory.
This takes into account any hooks installed by the context.
Normally you wont need to call this unless you are writing your
own asio_handler_invoke hook, or creating wrapped handlers.
*/
/** @{ */
void* allocate (std::size_t size) const;
void deallocate (void* p, std::size_t size) const;
/** @} */
/** Invoke the specified Function on the context.
This is equivalent to the following:
@code
template <typename Function>
void callOnContext (Function f, Context& context)
{
using boost_asio_handler_invoke_helpers;
invoke <Function, Context> (f, context);
}
@endcode
Usually you won't need to call this unless you
are writing your own asio_handler_invoke hook.
The boost::function object is created partially on the
stack, using a custom Allocator which calls into the handler's
context to perform allocation and deallocation. We take ownership
of the passed Function object.
All of the safety guarantees of the original context are preserved
when going through this invoke function.
*/
template <typename Function>
void invoke (BOOST_ASIO_MOVE_ARG(Function) f)
{
invoked_type invoked (BOOST_ASIO_MOVE_CAST(Function)(f),
Allocator <invoked_type> (*this));
m_call->invoke (invoked);
}
bool operator== (Call const* call) const noexcept;
bool operator!= (Call const* call) const noexcept;
private:
template <typename Handler>
friend struct CallType;
friend struct Call;
Context (Call* call) noexcept;
// Note that we only store a pointer here. If the original
// Call is destroyed, the context will become invalid.
//
Call* m_call;
};
private:
//--------------------------------------------------------------------------
//
// Implementation
//
//--------------------------------------------------------------------------
struct Call;
// These construct the reference counted polymorphic wrapper (Call)
// around the handler. We use MoveAssignable (rvalue-references)
// These construct the Call, a reference counted polymorphic wrapper around
// the handler and its context. We use MoveAssignable (rvalue-references)
// assignments to take ownership of the object and bring it to our stack.
// From there, we use the context's hooked allocation function to create
// a single piece of memory to hold our wrapper. Then we use placement
// new to construct the wrapper. The wrapper uses rvalue assignment
// to take ownership of the handler from the stack.
// From there, we use the context's hooked allocation function to create a
// single piece of memory to hold our wrapper. Then we use placement new to
// construct the wrapper. The wrapper uses rvalue assignment to take
// ownership of the handler from the stack.
template <template <typename> class Container, typename Handler>
static Call* construct (BOOST_ASIO_MOVE_ARG(Handler) handler)
static Call* construct (BOOST_ASIO_MOVE_ARG(Handler) handler,
Context context)
{
typedef Container <Handler> ContainerType;
Handler local (BOOST_ASIO_MOVE_CAST(Handler)(handler)); // move to stack
Handler local (BOOST_ASIO_MOVE_CAST(Handler)(handler));
std::size_t const size (sizeof (ContainerType));
void* const p = boost_asio_handler_alloc_helpers::
allocate <Handler> (size, local);
return ::new (p) ContainerType (
BOOST_ASIO_MOVE_CAST(Handler)(local), size);
BOOST_ASIO_MOVE_CAST(Handler)(local), size, context);
}
template <template <typename, typename> class Container,
typename Handler, typename Arg1>
static Call* construct (BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1)
static Call* construct (BOOST_ASIO_MOVE_ARG(Handler) handler,
Context context, Arg1 arg1)
{
typedef Container <Handler, Arg1> ContainerType;
Handler local (BOOST_ASIO_MOVE_CAST(Handler)(handler)); // move to stack
Handler local (BOOST_ASIO_MOVE_CAST(Handler)(handler));
std::size_t const size (sizeof (ContainerType));
void* const p = boost_asio_handler_alloc_helpers::
allocate <Handler> (size, local);
return ::new (p) ContainerType (
BOOST_ASIO_MOVE_CAST(Handler)(local), size, arg1);
BOOST_ASIO_MOVE_CAST(Handler)(local),
size, context, arg1);
}
template <template <typename, typename, typename> class Container,
typename Handler, typename Arg1, typename Arg2>
static Call* construct (BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1, Arg2 arg2)
static Call* construct (BOOST_ASIO_MOVE_ARG(Handler) handler,
Context context, Arg1 arg1, Arg2 arg2)
{
typedef Container <Handler, Arg1, Arg2> ContainerType;
Handler local (BOOST_ASIO_MOVE_CAST(Handler)(handler)); // move to stack
Handler local (BOOST_ASIO_MOVE_CAST(Handler)(handler));
std::size_t const size (sizeof (ContainerType));
void* const p = boost_asio_handler_alloc_helpers::
allocate <Handler> (size, local);
return ::new (p) ContainerType (
BOOST_ASIO_MOVE_CAST(Handler)(local), size, arg1, arg2);
}
inline void* allocate (std::size_t size)
{
return m_call->allocate (size);
}
inline void deallocate (void* p, std::size_t size)
{
m_call->deallocate (p, size);
}
inline void destroy ()
{
m_call->destroy ();
BOOST_ASIO_MOVE_CAST(Handler)(local),
size, context, arg1, arg2);
}
//--------------------------------------------------------------------------
//
// Custom Allocator compatible with std::allocator and boost::function
// which uses the underlying context to allocate memory. This is
// vastly more efficient in a variety of situations especially during
// an upcall.
// which uses the underlying context to allocate memory. This is vastly
// more efficient in a variety of situations especially during an upcall.
//
// The context must be valid for the duration of the invocation.
//
template <typename T>
struct Allocator
@@ -282,14 +593,16 @@ private:
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
explicit Allocator (SharedObjectPtr <Call> const& call)
: m_call (call)
// Make sure this is the right context!
//
explicit Allocator (Context context)
: m_context (context)
{
}
template <typename U>
Allocator (Allocator <U> const& other)
: m_call (other.m_call)
: m_context (other.m_context)
{
}
@@ -312,13 +625,13 @@ private:
pointer allocate (size_type n) const
{
size_type const bytes = n * sizeof (value_type);
return static_cast <pointer> (m_call->allocate (bytes));
return static_cast <pointer> (m_context.allocate (bytes));
}
void deallocate (pointer p, size_type n) const
{
size_type const bytes = n * sizeof (value_type);
m_call->deallocate (p, bytes);
m_context.deallocate (p, bytes);
}
size_type max_size () const noexcept
@@ -340,83 +653,61 @@ private:
template <class>
friend struct Allocator;
// The wrapped handler is stored in a reference counted
// container, so copies and pass by value is very cheap.
// The context must remain valid for the lifetime of the allocator.
//
SharedObjectPtr <Call> m_call;
Context m_context;
};
//--------------------------------------------------------------------------
// We use a boost::function to hold the Function from asio_handler_invoke.
//
typedef boost::function <void(void)> invoked_type;
// Invoke the specified Function object. The boost::function object is
// created partially on the stack, using a custom Allocator which calls
// into the handler's context to perform allocation and deallocation.
// We take ownership of the passed Function object, and then ownership
//
// All of the safety guarantees of the original context are preserved
// when going through this invoke function.
//
template <typename Function>
void invoke (BOOST_ASIO_MOVE_ARG(Function) f)
{
invoked_type invoked (BOOST_ASIO_MOVE_CAST(Function)(f),
Allocator <invoked_type> (m_call));
m_call->invoke (invoked);
}
//--------------------------------------------------------------------------
//
// Abstract handler wrapper
//
struct Call : public SharedObject
{
Call ()
{
}
public:
explicit Call (Context context) noexcept;
~Call ();
~Call ()
{
Context getContext () const noexcept;
}
// Determine if the completion handler is a continuation
// of a composed operation. It's generally not possible
// to know this from here, so there's an interface to
// set the flag.
//
// Our asio_handler_is_continuation hook calls this.
//
bool is_continuation () const noexcept;
void set_continuation () noexcept;
void set_final_continuation () noexcept;
virtual void operator() ()
{
pure_virtual_called ();
}
virtual void operator() (error_code const&)
{
pure_virtual_called ();
}
virtual void operator() (error_code const&, std::size_t)
{
pure_virtual_called ();
}
void operator() ();
void operator() (error_code const&);
void operator() (error_code const&, std::size_t);
virtual void* allocate (std::size_t) = 0;
virtual void deallocate (void*, std::size_t) = 0;
virtual void destroy () = 0;
virtual void invoke (invoked_type&) = 0;
//----------------------------------------------------------------------
virtual void destroy () = 0;
protected:
void check_continuation () noexcept;
static void* pure_virtual_called ()
{
// These shouldn't be getting called. But since the object returned
// by most implementations of bind have operator() up to high arity
// levels, it is not generally possible to write a traits test that
// works in all scenarios for detecting a particular signature of a
// handler.
//
fatal_error ("pure virtual called");
return nullptr;
}
virtual void dispatch ();
virtual void dispatch (error_code const&);
virtual void dispatch (error_code const&, std::size_t);
static void* pure_virtual_called ();
Context const m_context;
bool m_is_continuation;
bool m_is_final_continuation;
private:
// called by Context
friend struct Context;
virtual void invoke (invoked_type&) = 0;
};
// Required for gaining access to our hooks
@@ -436,9 +727,18 @@ private:
// The size parameter corresponds to how much we allocated using
// the custom allocator, and is required for deallocation.
//
CallType (BOOST_ASIO_MOVE_ARG(Handler) handler, std::size_t size)
: m_handler (BOOST_ASIO_MOVE_CAST(Handler)(handler))
// If the passed context is null, we will use the handler itself
// as the context (this is what normally happens when you pass a
// handler into an asynchronous function operation).
//
// Context must have Call as a base or this will result in
// undefined behavior.
//
CallType (BOOST_ASIO_MOVE_ARG(Handler) handler,
std::size_t size, Context context)
: Call (context)
, m_size (size)
, m_handler (BOOST_ASIO_MOVE_CAST(Handler)(handler))
{
}
@@ -447,10 +747,12 @@ private:
}
// Allocate using the handler's context.
// Allocate using the original handler as the context.
//
void* allocate (std::size_t bytes)
{
// If this goes off someone didn't call getContext()!
bassert (m_context == this);
return boost_asio_handler_alloc_helpers::
allocate <Handler> (bytes, m_handler);
}
@@ -460,6 +762,8 @@ private:
//
void deallocate (void* p, std::size_t size)
{
// If this goes off someone didn't call getContext()!
bassert (m_context == this);
boost_asio_handler_alloc_helpers::
deallocate <Handler> (p, size, m_handler);
}
@@ -469,6 +773,8 @@ private:
//
void invoke (invoked_type& invoked)
{
// If this goes off someone didn't call getContext()!
bassert (m_context == this);
boost_asio_handler_invoke_helpers::
invoke <invoked_type, Handler> (invoked, m_handler);
}
@@ -515,8 +821,10 @@ private:
pure_virtual_called ();
}
protected:
private:
std::size_t const m_size;
protected:
Handler m_handler;
};
@@ -525,12 +833,14 @@ private:
template <typename Handler>
struct PostCallType : CallType <Handler>
{
PostCallType (BOOST_ASIO_MOVE_ARG(Handler) handler, std::size_t size)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler), size)
PostCallType (BOOST_ASIO_MOVE_ARG(Handler) handler,
std::size_t size, Context context)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler),
size, context)
{
}
void operator() ()
void dispatch ()
{
this->m_handler ();
}
@@ -539,13 +849,15 @@ private:
template <typename Handler, typename Arg1>
struct PostCallType1 : CallType <Handler>
{
PostCallType1 (BOOST_ASIO_MOVE_ARG(Handler) handler, std::size_t size, Arg1 arg1)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler), size)
PostCallType1 (BOOST_ASIO_MOVE_ARG(Handler) handler,
std::size_t size, Context context, Arg1 arg1)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler),
size, context)
, m_arg1 (arg1)
{
}
void operator() ()
void dispatch ()
{
this->m_handler (m_arg1);
}
@@ -556,14 +868,16 @@ private:
template <typename Handler, typename Arg1, typename Arg2>
struct PostCallType2 : CallType <Handler>
{
PostCallType2 (BOOST_ASIO_MOVE_ARG(Handler) handler, std::size_t size, Arg1 arg1, Arg2 arg2)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler), size)
PostCallType2 (BOOST_ASIO_MOVE_ARG(Handler) handler,
std::size_t size, Context context, Arg1 arg1, Arg2 arg2)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler),
size, context)
, m_arg1 (arg1)
, m_arg2 (arg2)
{
}
void operator() ()
void dispatch ()
{
this->m_handler (m_arg1, m_arg2);
}
@@ -575,12 +889,14 @@ private:
template <typename Handler>
struct ErrorCallType : CallType <Handler>
{
ErrorCallType (BOOST_ASIO_MOVE_ARG(Handler) handler, std::size_t size)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler), size)
ErrorCallType (BOOST_ASIO_MOVE_ARG(Handler) handler,
std::size_t size, Context context)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler),
size, context)
{
}
void operator() (error_code const& ec)
void dispatch (error_code const& ec)
{
this->m_handler (ec);
}
@@ -589,12 +905,14 @@ private:
template <typename Handler>
struct TransferCallType : CallType <Handler>
{
TransferCallType (BOOST_ASIO_MOVE_ARG(Handler) handler, std::size_t size)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler), size)
TransferCallType (BOOST_ASIO_MOVE_ARG(Handler) handler,
std::size_t size, Context context)
: CallType <Handler> (BOOST_ASIO_MOVE_CAST(Handler)(handler),
size, context)
{
}
void operator() (error_code const& ec, std::size_t bytes_transferred)
void dispatch (error_code const& ec, std::size_t bytes_transferred)
{
this->m_handler (ec, bytes_transferred);
}
@@ -606,6 +924,15 @@ private:
friend void* asio_handler_allocate (std::size_t, HandlerCall*);
friend void asio_handler_deallocate (void*, std::size_t, HandlerCall*);
template <class Function>
friend void asio_handler_invoke (BOOST_ASIO_MOVE_ARG(Function), Call*);
friend void* asio_handler_allocate (std::size_t, Call*);
friend void asio_handler_deallocate (void*, std::size_t, Call*);
friend bool asio_handler_is_continuation (HandlerCall* call);
friend bool asio_handler_is_continuation (HandlerCall::Call* call);
friend bool asio_handler_is_continuation (HandlerCall::Context* context);
SharedObjectPtr <Call> m_call;
};
@@ -672,32 +999,56 @@ public:
//
// Specializations
//
// asio_handler_invoke, asio_handler_allocate, asio_handler_deallocate
// ContainerDeletePolicy
// asio_handler_invoke
// asio_handler_allocate
// asio_handler_deallocate
//
template <class Function>
void asio_handler_invoke (BOOST_ASIO_MOVE_ARG(Function) f, HandlerCall* call)
{
call->invoke (BOOST_ASIO_MOVE_CAST(Function)(f));
}
inline void* asio_handler_allocate (std::size_t size, HandlerCall* call)
{
return call->allocate (size);
}
inline void asio_handler_deallocate (void* p, std::size_t size, HandlerCall* call)
{
call->deallocate (p, size);
}
template <>
struct ContainerDeletePolicy <HandlerCall::Call>
{
// SharedObjectPtr will use this when
// the reference count drops to zero.
//
static void destroy (HandlerCall::Call* call)
{
call->destroy ();
}
};
template <class Function>
void asio_handler_invoke (BOOST_ASIO_MOVE_ARG(Function) f, HandlerCall::Context* context)
{
context->invoke (BOOST_ASIO_MOVE_CAST(Function)(f));
}
template <class Function>
void asio_handler_invoke (BOOST_ASIO_MOVE_ARG(Function) f, HandlerCall* call)
{
// Always go through the call's context.
call->getContext().invoke (BOOST_ASIO_MOVE_CAST(Function)(f));
}
template <class Function>
void asio_handler_invoke (BOOST_ASIO_MOVE_ARG(Function) f, HandlerCall::Call* call)
{
// Always go through the call's context.
call->getContext().invoke (BOOST_ASIO_MOVE_CAST(Function)(f));
}
void* asio_handler_allocate (std::size_t size, HandlerCall* call);
void* asio_handler_allocate (std::size_t size, HandlerCall::Call* call);
void* asio_handler_allocate (std::size_t size, HandlerCall::Context* context);
void asio_handler_deallocate (void* p, std::size_t size, HandlerCall* call);
void asio_handler_deallocate (void* p, std::size_t size, HandlerCall::Call* call);
void asio_handler_deallocate (void* p, std::size_t size, HandlerCall::Context* context);
bool asio_handler_is_continuation (HandlerCall* call);
bool asio_handler_is_continuation (HandlerCall::Call* call);
bool asio_handler_is_continuation (HandlerCall::Context* context);
//------------------------------------------------------------------------------
#endif

View File

@@ -24,6 +24,7 @@
namespace beast
{
#include "basics/beast_HandlerCall.cpp"
#include "basics/beast_PeerRole.cpp"
#include "sockets/beast_SocketBase.cpp"

View File

@@ -152,13 +152,11 @@ public:
m_origHandler = ErrorCall (
BOOST_ASIO_MOVE_CAST(HandshakeHandler)
(HandshakeHandler(init.handler)));
bassert (m_origBufferedHandler.isNull ());
async_do_handshake (type, ConstBuffers ());
return init.result.get();
#else
m_origHandler = ErrorCall (
BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler));
bassert (m_origBufferedHandler.isNull ());
async_do_handshake (type, ConstBuffers ());
#endif
}
@@ -184,13 +182,11 @@ public:
m_origBufferedHandler = TransferCall (
BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler)
(BufferedHandshakeHandler(init.handler)));
bassert (m_origHandler.isNull ());
async_do_handshake (type, ConstBuffers (buffers));
return init.result.get();
#else
m_origBufferedHandler = TransferCall (
BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler(handler)));
bassert (m_origHandler.isNull ());
async_do_handshake (type, ConstBuffers (buffers));
#endif
}
@@ -245,6 +241,22 @@ public:
void async_do_handshake (handshake_type type, ConstBuffers const& buffers)
{
// Get the execution context from the original handler
// and signal the beginning of our composed operation.
//
if (m_origHandler.isNotNull ())
{
m_origHandler.beginComposed ();
m_context = m_origHandler.getContext ();
}
else
{
m_origBufferedHandler.beginComposed ();
m_context = m_origBufferedHandler.getContext ();
}
bassert (m_context.isNotNull ());
// Transfer caller data to our buffer.
// We commit the bytes in on_async_read_some.
//
@@ -280,7 +292,11 @@ public:
if (! m_origBufferedHandler.isNull ())
{
bassert (m_origHandler.isNull ());
// continuation?
// The composed operation has completed and
// the original handler will eventually get called.
//
m_origBufferedHandler.endComposed ();
m_callback->on_async_detect (m_logic.get (), ec,
ConstBuffers (m_buffer.data ()), m_origBufferedHandler);
@@ -288,11 +304,12 @@ public:
}
#endif
bassert (! m_origHandler.isNull ())
// continuation?
// The composed operation has completed and
// the original handler will eventually get called.
//
m_origHandler.endComposed ();
m_callback->on_async_detect (m_logic.get (), ec,
ConstBuffers (m_buffer.data ()), m_origHandler);
ConstBuffers (m_buffer.data ()), m_origHandler);
return;
}
@@ -302,27 +319,33 @@ public:
buffer_type::mutable_buffers_type buffers (m_buffer.prepare (
needed - available));
// need a continuation hook here?
m_next_layer.async_read_some (buffers, boost::bind (
&this_type::on_async_read_some, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
// Perform the asynchronous operation using the context
// of the original handler. This ensures that we meet the
// execution safety requirements of the handler.
//
HandlerCall handler (boost::bind (
&this_type::on_async_read_some, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred),
HandlerCall::Read (), m_context);
m_next_layer.async_read_some (buffers, handler);
return;
}
// Error condition
#if BEAST_ASIO_HAS_BUFFEREDHANDSHAKE
if (! m_origBufferedHandler.isNull ())
{
bassert (m_origHandler.isNull ());
// continuation?
m_origBufferedHandler.endComposed ();
m_callback->on_async_detect (m_logic.get (), ec,
ConstBuffers (m_buffer.data ()), m_origBufferedHandler);
return;
}
#endif
bassert (! m_origHandler.isNull ())
// continuation?
m_origBufferedHandler.endComposed ();
m_callback->on_async_detect (m_logic.get (), ec,
ConstBuffers (m_buffer.data ()), m_origHandler);
}
@@ -335,6 +358,7 @@ private:
HandshakeDetectLogicType <Logic> m_logic;
ErrorCall m_origHandler;
TransferCall m_origBufferedHandler;
HandlerCall::Context m_context;
};
/** @} */