//------------------------------------------------------------------------------ /* This file is part of Beast: https://github.com/vinniefalco/Beast Copyright 2013, Vinnie Falco 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. */ //============================================================================== #ifndef BEAST_HANDLERCALL_H_INCLUDED #define BEAST_HANDLERCALL_H_INCLUDED /** A polymorphic Handler that can wrap any other handler. This is very lightweight container that just holds a shared pointer to the actual handler. This means it can be copied cheaply. The allocation and deallocation of the handler is performed according to the requirements of asio_handler_allocate and asio_handler_delete. All calls also satisfy the safety guarantees of asio_handler_invoke. All constructors will take ownership of the passed in handler if your compiler has MoveConstructible and MoveAssignable support enabled. Supports these concepts: DefaultConstructible CopyConstructible CopyAssignable Destructible */ class HandlerCall { private: typedef boost::system::error_code error_code; typedef boost::function invoked_type; // Forward declarations needed for friendship template struct CallType; struct Call; public: typedef void result_type; 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) // CompletionHandler // // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/CompletionHandler.html // typedef Post Completion; // AcceptHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/AcceptHandler.html // typedef Error Accept; // ConnectHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/ConnectHandler.html // typedef Error Connect; // ShutdownHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/ShutdownHandler.html // typedef Error Shutdown; // HandshakeHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/HandshakeHandler.html // typedef Error Handshake; // ReadHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/ReadHandler.html // typedef Transfer Read; // WriteHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/WriteHandler.html // typedef Transfer Write; // BufferedHandshakeHandler // http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/BufferedHandshakeHandler.html // typedef Transfer BufferedHandshake; /** @} */ //-------------------------------------------------------------------------- /** 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 HandlerCall (Post, BOOST_ASIO_MOVE_ARG(Handler) handler) : m_call (construct (Context (), BOOST_ASIO_MOVE_CAST(Handler)(handler))) { } template HandlerCall (Post, Context context, BOOST_ASIO_MOVE_ARG(Handler) handler) : m_call (construct (context, BOOST_ASIO_MOVE_CAST(Handler)(handler))) { } /** @} */ /** 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 HandlerCall (Post, BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1) : m_call (construct (Context (), BOOST_ASIO_MOVE_CAST(Handler)(handler), arg1)) { } template HandlerCall (Post, Context context, BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1) : m_call (construct (context, BOOST_ASIO_MOVE_CAST(Handler)(handler), 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 HandlerCall (Post, BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1, Arg2 arg2) : m_call (construct (Context (), BOOST_ASIO_MOVE_CAST(Handler)(handler), arg1, arg2)) { } template HandlerCall (Post, Context context, BOOST_ASIO_MOVE_ARG(Handler) handler, Arg1 arg1, Arg2 arg2) : m_call (construct (context, BOOST_ASIO_MOVE_CAST(Handler)(handler), 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 HandlerCall (Error, BOOST_ASIO_MOVE_ARG(Handler) handler) : m_call (construct (Context (), BOOST_ASIO_MOVE_CAST(Handler)(handler))) { } template HandlerCall (Error, Context context, BOOST_ASIO_MOVE_ARG(Handler) handler) : m_call (construct (context, BOOST_ASIO_MOVE_CAST(Handler)(handler))) { } /** @} */ /** 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 HandlerCall (Transfer, BOOST_ASIO_MOVE_ARG(Handler) handler) : m_call (construct (Context (), BOOST_ASIO_MOVE_CAST(Handler)(handler))) { } template HandlerCall (Transfer, Context context, BOOST_ASIO_MOVE_ARG(Handler) handler) : m_call (construct (context, BOOST_ASIO_MOVE_CAST(Handler)(handler))) { } /** @} */ /** Copy construction and assignment. 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; /** @} */ //-------------------------------------------------------------------------- /** Returns true if the HandlerCall is null. A null HandlerCall object may not be passed to an asynchronous completion function. */ bool isNull () const noexcept; /** Returns true if the HandlerCall is not null. */ bool isNotNull () const noexcept; /** 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 const& beginComposed () const 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 const& endComposed () const 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() () const; void operator() (error_code const& ec) const; void operator() (error_code const& ec, std::size_t bytes_transferred) const; /** @} */ //-------------------------------------------------------------------------- /** 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 { /** 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 void callOnContext (Function f, Context& context) { using boost_asio_handler_invoke_helpers; invoke (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 void invoke (BOOST_ASIO_MOVE_ARG(Function) f) { invoked_type invoked (BOOST_ASIO_MOVE_CAST(Function)(f), Allocator (*this)); m_call->invoke (invoked); } bool operator== (Call const* call) const noexcept; bool operator!= (Call const* call) const noexcept; private: template 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 // //-------------------------------------------------------------------------- // 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. template