Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r84311 - in trunk: boost/asio boost/asio/impl libs/asio/example/spawn
From: chris_at_[hidden]
Date: 2013-05-16 21:38:48


Author: chris_kohlhoff
Date: 2013-05-16 21:38:47 EDT (Thu, 16 May 2013)
New Revision: 84311
URL: http://svn.boost.org/trac/boost/changeset/84311

Log:
Add the asio::spawn() function, a high-level wrapper for running
stackful coroutines. It is based on the Boost.Coroutine library.

Here is an example of its use:

  asio::spawn(my_strand, do_echo);

  // ...

  void do_echo(asio::yield_context yield)
  {
    try
    {
      char data[128];
      for (;;)
      {
        std::size_t length =
          my_socket.async_read_some(
            asio::buffer(data), yield);

        asio::async_write(my_socket,
            asio::buffer(data, length), yield);
      }
    }
    catch (std::exception& e)
    {
      // ...
    }
  }

The first argument to asio::spawn() may be a strand, io_service or
completion handler. This argument determines the context in which the
coroutine is permitted to execute. For example, a server's per-client
object may consist of multiple coroutines; they should all run on the
same strand so that no explicit synchronisation is required.

The second argument is a function object with signature (**):

  void coroutine(asio::yield_context yield);

that specifies the code to be run as part of the coroutine. The
parameter yield may be passed to an asynchronous operation in place of
the completion handler, as in:

  std::size_t length =
    my_socket.async_read_some(
      asio::buffer(data), yield);

This starts the asynchronous operation and suspends the coroutine. The
coroutine will be resumed automatically when the asynchronous operation
completes.

Where a completion handler signature has the form:

  void handler(error_code ec, result_type result);

the initiating function returns the result_type. In the async_read_some
example above, this is std::size_t. If the asynchronous operation fails,
the error_code is converted into a system_error exception and thrown.

Where a completion handler signature has the form:

  void handler(error_code ec);

the initiating function returns void. As above, an error is passed back
to the coroutine as a system_error exception.

To collect the error_code from an operation, rather than have it throw
an exception, associate the output variable with the yield_context as
follows:

  error_code ec;
  std::size_t length =
    my_socket.async_read_some(
      asio::buffer(data), yield[ec]);

**Note: if asio::spawn() is used with a custom completion handler of
type Handler, the function object signature is actually:
  
  void coroutine(asio::basic_yield_context<Handler> yield);

Added:
   trunk/boost/asio/impl/spawn.hpp (contents, props changed)
   trunk/boost/asio/spawn.hpp (contents, props changed)
   trunk/libs/asio/example/spawn/
   trunk/libs/asio/example/spawn/Jamfile.v2 (contents, props changed)
   trunk/libs/asio/example/spawn/echo_server.cpp (contents, props changed)

Added: trunk/boost/asio/impl/spawn.hpp
==============================================================================
--- (empty file)
+++ trunk/boost/asio/impl/spawn.hpp 2013-05-16 21:38:47 EDT (Thu, 16 May 2013)
@@ -0,0 +1,323 @@
+//
+// impl/spawn.hpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_ASIO_IMPL_SPAWN_HPP
+#define BOOST_ASIO_IMPL_SPAWN_HPP
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1200)
+# pragma once
+#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
+
+#include <boost/asio/detail/config.hpp>
+#include <boost/asio/async_result.hpp>
+#include <boost/asio/detail/handler_alloc_helpers.hpp>
+#include <boost/asio/detail/handler_invoke_helpers.hpp>
+#include <boost/asio/detail/noncopyable.hpp>
+#include <boost/asio/detail/shared_ptr.hpp>
+#include <boost/asio/handler_type.hpp>
+
+#include <boost/asio/detail/push_options.hpp>
+
+namespace boost {
+namespace asio {
+namespace detail {
+
+ template <typename Handler, typename T>
+ class coro_handler
+ {
+ public:
+ coro_handler(basic_yield_context<Handler> ctx)
+ : coro_(ctx.coro_.lock()),
+ ca_(ctx.ca_),
+ handler_(ctx.handler_),
+ ec_(ctx.ec_),
+ value_(0)
+ {
+ }
+
+ void operator()(T value)
+ {
+ *ec_ = boost::system::error_code();
+ *value_ = value;
+ (*coro_)();
+ }
+
+ void operator()(boost::system::error_code ec, T value)
+ {
+ *ec_ = ec;
+ *value_ = value;
+ (*coro_)();
+ }
+
+ //private:
+ detail::shared_ptr<boost::coroutines::coroutine<void()> > coro_;
+ boost::coroutines::coroutine<void()>::caller_type& ca_;
+ Handler& handler_;
+ boost::system::error_code* ec_;
+ T* value_;
+ };
+
+ template <typename Handler>
+ class coro_handler<Handler, void>
+ {
+ public:
+ coro_handler(basic_yield_context<Handler> ctx)
+ : coro_(ctx.coro_.lock()),
+ ca_(ctx.ca_),
+ handler_(ctx.handler_),
+ ec_(ctx.ec_)
+ {
+ }
+
+ void operator()()
+ {
+ *ec_ = boost::system::error_code();
+ (*coro_)();
+ }
+
+ void operator()(boost::system::error_code ec)
+ {
+ *ec_ = ec;
+ (*coro_)();
+ }
+
+ //private:
+ detail::shared_ptr<boost::coroutines::coroutine<void()> > coro_;
+ boost::coroutines::coroutine<void()>::caller_type& ca_;
+ Handler& handler_;
+ boost::system::error_code* ec_;
+ };
+
+ template <typename Handler, typename T>
+ inline void* asio_handler_allocate(std::size_t size,
+ coro_handler<Handler, T>* this_handler)
+ {
+ return boost_asio_handler_alloc_helpers::allocate(
+ size, this_handler->handler_);
+ }
+
+ template <typename Handler, typename T>
+ inline void asio_handler_deallocate(void* pointer, std::size_t size,
+ coro_handler<Handler, T>* this_handler)
+ {
+ boost_asio_handler_alloc_helpers::deallocate(
+ pointer, size, this_handler->handler_);
+ }
+
+ template <typename Function, typename Handler, typename T>
+ inline void asio_handler_invoke(Function& function,
+ coro_handler<Handler, T>* this_handler)
+ {
+ boost_asio_handler_invoke_helpers::invoke(
+ function, this_handler->handler_);
+ }
+
+ template <typename Function, typename Handler, typename T>
+ inline void asio_handler_invoke(const Function& function,
+ coro_handler<Handler, T>* this_handler)
+ {
+ boost_asio_handler_invoke_helpers::invoke(
+ function, this_handler->handler_);
+ }
+
+} // namespace detail
+
+#if !defined(GENERATING_DOCUMENTATION)
+
+template <typename Handler, typename ReturnType>
+struct handler_type<basic_yield_context<Handler>, ReturnType()>
+{
+ typedef detail::coro_handler<Handler, void> type;
+};
+
+template <typename Handler, typename ReturnType, typename Arg1>
+struct handler_type<basic_yield_context<Handler>, ReturnType(Arg1)>
+{
+ typedef detail::coro_handler<Handler, Arg1> type;
+};
+
+template <typename Handler, typename ReturnType>
+struct handler_type<basic_yield_context<Handler>,
+ ReturnType(boost::system::error_code)>
+{
+ typedef detail::coro_handler<Handler, void> type;
+};
+
+template <typename Handler, typename ReturnType, typename Arg2>
+struct handler_type<basic_yield_context<Handler>,
+ ReturnType(boost::system::error_code, Arg2)>
+{
+ typedef detail::coro_handler<Handler, Arg2> type;
+};
+
+template <typename Handler, typename T>
+class async_result<detail::coro_handler<Handler, T> >
+{
+public:
+ typedef T type;
+
+ explicit async_result(detail::coro_handler<Handler, T>& h)
+ : ca_(h.ca_)
+ {
+ out_ec_ = h.ec_;
+ if (!out_ec_) h.ec_ = &ec_;
+ h.value_ = &value_;
+ }
+
+ type get()
+ {
+ ca_();
+ if (!out_ec_ && ec_) throw boost::system::system_error(ec_);
+ return value_;
+ }
+
+private:
+ boost::coroutines::coroutine<void()>::caller_type& ca_;
+ boost::system::error_code* out_ec_;
+ boost::system::error_code ec_;
+ type value_;
+};
+
+template <typename Handler>
+class async_result<detail::coro_handler<Handler, void> >
+{
+public:
+ typedef void type;
+
+ explicit async_result(detail::coro_handler<Handler, void>& h)
+ : ca_(h.ca_)
+ {
+ out_ec_ = h.ec_;
+ if (!out_ec_) h.ec_ = &ec_;
+ }
+
+ void get()
+ {
+ ca_();
+ if (!out_ec_ && ec_) throw boost::system::system_error(ec_);
+ }
+
+private:
+ boost::coroutines::coroutine<void()>::caller_type& ca_;
+ boost::system::error_code* out_ec_;
+ boost::system::error_code ec_;
+};
+
+namespace detail {
+
+ template <typename Handler, typename Function>
+ struct spawn_data : private noncopyable
+ {
+ spawn_data(BOOST_ASIO_MOVE_ARG(Handler) handler,
+ bool call_handler, BOOST_ASIO_MOVE_ARG(Function) function)
+ : handler_(BOOST_ASIO_MOVE_CAST(Handler)(handler)),
+ call_handler_(call_handler),
+ function_(BOOST_ASIO_MOVE_CAST(Function)(function))
+ {
+ }
+
+ weak_ptr<boost::coroutines::coroutine<void()> > coro_;
+ Handler handler_;
+ bool call_handler_;
+ Function function_;
+ };
+
+ template <typename Handler, typename Function>
+ struct coro_entry_point
+ {
+ void operator()(boost::coroutines::coroutine<void()>::caller_type& ca)
+ {
+ shared_ptr<spawn_data<Handler, Function> > data(data_);
+ ca(); // Yield until coroutine pointer has been initialised.
+ const basic_yield_context<Handler> yield(
+ data->coro_, ca, data->handler_);
+ (data->function_)(yield);
+ if (data->call_handler_)
+ (data->handler_)();
+ }
+
+ shared_ptr<spawn_data<Handler, Function> > data_;
+ };
+
+ template <typename Handler, typename Function>
+ struct spawn_helper
+ {
+ void operator()()
+ {
+ coro_entry_point<Handler, Function> entry_point = { data_ };
+ shared_ptr<boost::coroutines::coroutine<void()> > coro(
+ new boost::coroutines::coroutine<void()>(entry_point, attributes_));
+ data_->coro_ = coro;
+ (*coro)();
+ }
+
+ shared_ptr<spawn_data<Handler, Function> > data_;
+ boost::coroutines::attributes attributes_;
+ };
+
+ inline void default_spawn_handler() {}
+
+} // namespace detail
+
+template <typename Handler, typename Function>
+void spawn(BOOST_ASIO_MOVE_ARG(Handler) handler,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes)
+{
+ detail::spawn_helper<Handler, Function> helper;
+ helper.data_.reset(
+ new detail::spawn_data<Handler, Function>(
+ BOOST_ASIO_MOVE_CAST(Handler)(handler), true,
+ BOOST_ASIO_MOVE_CAST(Function)(function)));
+ helper.attributes_ = attributes;
+ boost_asio_handler_invoke_helpers::invoke(helper, helper.data_->handler_);
+}
+
+template <typename Handler, typename Function>
+void spawn(basic_yield_context<Handler> ctx,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes)
+{
+ Handler handler(ctx.handler_); // Explicit copy that might be moved from.
+ detail::spawn_helper<Handler, Function> helper;
+ helper.data_.reset(
+ new detail::spawn_data<Handler, Function>(
+ BOOST_ASIO_MOVE_CAST(Handler)(handler), false,
+ BOOST_ASIO_MOVE_CAST(Function)(function)));
+ helper.attributes_ = attributes;
+ boost_asio_handler_invoke_helpers::invoke(helper, helper.data_->handler_);
+}
+
+template <typename Function>
+void spawn(boost::asio::io_service::strand strand,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes)
+{
+ boost::asio::spawn(strand.wrap(&detail::default_spawn_handler),
+ BOOST_ASIO_MOVE_CAST(Function)(function), attributes);
+}
+
+template <typename Function>
+void spawn(boost::asio::io_service& io_service,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes)
+{
+ boost::asio::spawn(boost::asio::io_service::strand(io_service),
+ BOOST_ASIO_MOVE_CAST(Function)(function), attributes);
+}
+
+#endif // !defined(GENERATING_DOCUMENTATION)
+
+} // namespace asio
+} // namespace boost
+
+#include <boost/asio/detail/pop_options.hpp>
+
+#endif // BOOST_ASIO_IMPL_SPAWN_HPP

Added: trunk/boost/asio/spawn.hpp
==============================================================================
--- (empty file)
+++ trunk/boost/asio/spawn.hpp 2013-05-16 21:38:47 EDT (Thu, 16 May 2013)
@@ -0,0 +1,99 @@
+//
+// spawn.hpp
+// ~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef BOOST_ASIO_SPAWN_HPP
+#define BOOST_ASIO_SPAWN_HPP
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1200)
+# pragma once
+#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
+
+#include <boost/asio/detail/config.hpp>
+#include <boost/coroutine/coroutine.hpp>
+#include <boost/asio/detail/weak_ptr.hpp>
+#include <boost/asio/detail/wrapped_handler.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/strand.hpp>
+
+#include <boost/asio/detail/push_options.hpp>
+
+namespace boost {
+namespace asio {
+
+template <typename Handler>
+class basic_yield_context
+{
+public:
+ basic_yield_context(
+ const detail::weak_ptr<boost::coroutines::coroutine<void()> >& coro,
+ boost::coroutines::coroutine<void()>::caller_type& ca, Handler& handler)
+ : coro_(coro),
+ ca_(ca),
+ handler_(handler),
+ ec_(0)
+ {
+ }
+
+ basic_yield_context operator[](boost::system::error_code& ec)
+ {
+ basic_yield_context tmp(*this);
+ tmp.ec_ = &ec;
+ return tmp;
+ }
+
+#if defined(GENERATING_DOCUMENTATION)
+private:
+#endif // defined(GENERATING_DOCUMENTATION)
+ detail::weak_ptr<boost::coroutines::coroutine<void()> > coro_;
+ boost::coroutines::coroutine<void()>::caller_type& ca_;
+ Handler& handler_;
+ boost::system::error_code* ec_;
+};
+
+#if defined(GENERATING_DOCUMENTATION)
+typedef basic_yield_context<unspecified> yield_context;
+#else // defined(GENERATING_DOCUMENTATION)
+typedef basic_yield_context<
+ detail::wrapped_handler<
+ io_service::strand, void(*)()> > yield_context;
+#endif // defined(GENERATING_DOCUMENTATION)
+
+template <typename Handler, typename Function>
+void spawn(BOOST_ASIO_MOVE_ARG(Handler) handler,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes
+ = boost::coroutines::attributes());
+
+template <typename Handler, typename Function>
+void spawn(basic_yield_context<Handler> ctx,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes
+ = boost::coroutines::attributes());
+
+template <typename Function>
+void spawn(boost::asio::io_service::strand strand,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes
+ = boost::coroutines::attributes());
+
+template <typename Function>
+void spawn(boost::asio::io_service& io_service,
+ BOOST_ASIO_MOVE_ARG(Function) function,
+ const boost::coroutines::attributes& attributes
+ = boost::coroutines::attributes());
+
+} // namespace asio
+} // namespace boost
+
+#include <boost/asio/detail/pop_options.hpp>
+
+#include <boost/asio/impl/spawn.hpp>
+
+#endif // BOOST_ASIO_SPAWN_HPP

Added: trunk/libs/asio/example/spawn/Jamfile.v2
==============================================================================
--- (empty file)
+++ trunk/libs/asio/example/spawn/Jamfile.v2 2013-05-16 21:38:47 EDT (Thu, 16 May 2013)
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+#
+# Distributed under the Boost Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#
+
+import os ;
+
+if [ os.name ] = SOLARIS
+{
+ lib socket ;
+ lib nsl ;
+}
+else if [ os.name ] = NT
+{
+ lib ws2_32 ;
+ lib mswsock ;
+}
+else if [ os.name ] = HPUX
+{
+ lib ipv6 ;
+}
+
+exe server
+ : echo_server.cpp
+ /boost/context//boost_context
+ /boost/coroutine//boost_coroutine
+ /boost/system//boost_system
+ : <define>BOOST_ALL_NO_LIB=1
+ <threading>multi
+ <os>SOLARIS:<library>socket
+ <os>SOLARIS:<library>nsl
+ <os>NT:<define>_WIN32_WINNT=0x0501
+ <os>NT,<toolset>gcc:<library>ws2_32
+ <os>NT,<toolset>gcc:<library>mswsock
+ <os>NT,<toolset>gcc-cygwin:<define>__USE_W32_SOCKETS
+ <os>HPUX,<toolset>gcc:<define>_XOPEN_SOURCE_EXTENDED
+ <os>HPUX:<library>ipv6
+ ;

Added: trunk/libs/asio/example/spawn/echo_server.cpp
==============================================================================
--- (empty file)
+++ trunk/libs/asio/example/spawn/echo_server.cpp 2013-05-16 21:38:47 EDT (Thu, 16 May 2013)
@@ -0,0 +1,122 @@
+//
+// echo_server.cpp
+// ~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <boost/asio/deadline_timer.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/spawn.hpp>
+#include <boost/asio/write.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <iostream>
+
+using boost::asio::ip::tcp;
+
+class session : public boost::enable_shared_from_this<session>
+{
+public:
+ explicit session(boost::asio::io_service& io_service)
+ : strand_(io_service),
+ socket_(io_service),
+ timer_(io_service)
+ {
+ }
+
+ tcp::socket& socket()
+ {
+ return socket_;
+ }
+
+ void go()
+ {
+ boost::asio::spawn(strand_,
+ boost::bind(&session::echo,
+ shared_from_this(), _1));
+ boost::asio::spawn(strand_,
+ boost::bind(&session::timeout,
+ shared_from_this(), _1));
+ }
+
+private:
+ void echo(boost::asio::yield_context yield)
+ {
+ try
+ {
+ char data[128];
+ for (;;)
+ {
+ timer_.expires_from_now(boost::posix_time::seconds(10));
+ std::size_t n = socket_.async_read_some(boost::asio::buffer(data), yield);
+ boost::asio::async_write(socket_, boost::asio::buffer(data, n), yield);
+ }
+ }
+ catch (std::exception& e)
+ {
+ socket_.close();
+ timer_.cancel();
+ }
+ }
+
+ void timeout(boost::asio::yield_context yield)
+ {
+ while (socket_.is_open())
+ {
+ boost::system::error_code ignored_ec;
+ timer_.async_wait(yield[ignored_ec]);
+ if (timer_.expires_from_now() <= boost::posix_time::seconds(0))
+ socket_.close();
+ }
+ }
+
+ boost::asio::io_service::strand strand_;
+ tcp::socket socket_;
+ boost::asio::deadline_timer timer_;
+};
+
+void do_accept(boost::asio::io_service& io_service,
+ unsigned short port, boost::asio::yield_context yield)
+{
+ tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port));
+
+ for (;;)
+ {
+ boost::system::error_code ec;
+ boost::shared_ptr<session> new_session(new session(io_service));
+ acceptor.async_accept(new_session->socket(), yield[ec]);
+ if (!ec) new_session->go();
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ try
+ {
+ if (argc != 2)
+ {
+ std::cerr << "Usage: echo_server <port>\n";
+ return 1;
+ }
+
+ boost::asio::io_service io_service;
+
+ boost::asio::spawn(io_service,
+ boost::bind(do_accept,
+ boost::ref(io_service), atoi(argv[1]), _1));
+
+ io_service.run();
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Exception: " << e.what() << "\n";
+ }
+
+ return 0;
+}


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk