examples added

This commit is contained in:
Oliver Kowalke
2015-09-29 17:55:46 +02:00
parent 4c791a3790
commit 253d981f52
22 changed files with 3932 additions and 0 deletions
+222
View File
@@ -0,0 +1,222 @@
// Copyright Nat Goodspeed 2015.
// 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/fiber/all.hpp>
#include <thread>
#include <chrono>
#include <iostream>
#include <sstream>
#include <exception>
#include <tuple> // std::tie()
#include <cassert>
/*****************************************************************************
* example async API
*****************************************************************************/
//[AsyncAPI
class AsyncAPI {
public:
// constructor acquires some resource that can be read and written
AsyncAPI();
// callbacks accept an int error code; 0 == success
typedef int errorcode;
// write callback only needs to indicate success or failure
void init_write( std::string const& data,
std::function< void( errorcode) > const& callback);
// read callback needs to accept both errorcode and data
void init_read( std::function< void( errorcode, std::string const&) > const&);
// ... other operations ...
//<-
void inject_error( errorcode ec);
private:
std::string data_;
errorcode injected_;
//->
};
//]
/*****************************************************************************
* fake AsyncAPI implementation... pay no attention to the little man behind
* the curtain...
*****************************************************************************/
AsyncAPI::AsyncAPI() :
injected_( 0) {
}
void AsyncAPI::inject_error( errorcode ec) {
injected_ = ec;
}
void AsyncAPI::init_write( std::string const& data,
std::function< void( errorcode) > const& callback) {
// make a local copy of injected_
errorcode injected( injected_);
// reset it synchronously with caller
injected_ = 0;
// update data_ (this might be an echo service)
if ( ! injected) {
data_ = data;
}
// Simulate an asynchronous I/O operation by launching a detached thread
// that sleeps a bit before calling completion callback. Echo back to
// caller any previously-injected errorcode.
std::thread( [injected, callback](){
std::this_thread::sleep_for( std::chrono::milliseconds(100) );
callback( injected);
}).detach();
}
void AsyncAPI::init_read( std::function< void( errorcode, std::string const&) > const& callback) {
// make a local copy of injected_
errorcode injected( injected_);
// reset it synchronously with caller
injected_ = 0;
// local copy of data_ so we can capture in lambda
std::string data( data_);
// Simulate an asynchronous I/O operation by launching a detached thread
// that sleeps a bit before calling completion callback. Echo back to
// caller any previously-injected errorcode.
std::thread( [injected, callback, data](){
std::this_thread::sleep_for( std::chrono::milliseconds(100) );
callback( injected, data);
}).detach();
}
/*****************************************************************************
* adapters
*****************************************************************************/
// helper function used in a couple of the adapters
std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode);
//[callbacks_write_ec
AsyncAPI::errorcode write_ec( AsyncAPI & api, std::string const& data) {
boost::fibers::promise< AsyncAPI::errorcode > promise;
boost::fibers::future< AsyncAPI::errorcode > future( promise.get_future() );
// We can confidently bind a reference to local variable 'promise' into
// the lambda callback because we know for a fact we're going to suspend
// (preserving the lifespan of both 'promise' and 'future') until the
// callback has fired.
api.init_write( data,
[&promise]( AsyncAPI::errorcode ec){
promise.set_value( ec);
});
return future.get();
}
//]
//[callbacks_write
void write( AsyncAPI & api, std::string const& data) {
AsyncAPI::errorcode ec = write_ec( api, data);
if ( ec) {
throw make_exception("write", ec);
}
}
//]
//[callbacks_read_ec
std::pair< AsyncAPI::errorcode, std::string > read_ec( AsyncAPI & api) {
typedef std::pair< AsyncAPI::errorcode, std::string > result_pair;
boost::fibers::promise< result_pair > promise;
boost::fibers::future< result_pair > future( promise.get_future() );
// We promise that both 'promise' and 'future' will survive until our
// lambda has been called.
api.init_read( [&promise]( AsyncAPI::errorcode ec, std::string const& data){
promise.set_value( result_pair( ec, data) );
});
return future.get();
}
//]
//[callbacks_read
std::string read( AsyncAPI & api) {
boost::fibers::promise< std::string > promise;
boost::fibers::future< std::string > future( promise.get_future() );
// Both 'promise' and 'future' will survive until our lambda has been
// called.
api.init_read( [&promise]( AsyncAPI::errorcode ec, std::string const& data){
if ( ! ec) {
promise.set_value( data);
} else {
promise.set_exception(
std::make_exception_ptr(
make_exception("read", ec) ) );
}
});
return future.get();
}
//]
/*****************************************************************************
* helpers
*****************************************************************************/
std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode ec) {
std::ostringstream buffer;
buffer << "Error in AsyncAPI::" << desc << "(): " << ec;
return std::runtime_error( buffer.str() );
}
/*****************************************************************************
* driving logic
*****************************************************************************/
int main( int argc, char *argv[]) {
AsyncAPI api;
// successful write(): prime AsyncAPI with some data
write( api, "abcd");
// successful read(): retrieve it
std::string data( read( api) );
assert( data == "abcd");
// successful write_ec()
AsyncAPI::errorcode ec( write_ec( api, "efgh") );
assert( ec == 0);
// write_ec() with error
api.inject_error(1);
ec = write_ec( api, "ijkl");
assert( ec == 1);
// write() with error
std::string thrown;
api.inject_error(2);
try {
write(api, "mnop");
} catch ( std::exception const& e) {
thrown = e.what();
}
assert( thrown == make_exception("write", 2).what() );
// successful read_ec()
//[callbacks_read_ec_call
std::tie( ec, data) = read_ec( api);
//]
assert( ! ec);
assert( data == "efgh"); // last successful write_ec()
// read_ec() with error
api.inject_error(3);
std::tie( ec, data) = read_ec( api);
assert( ec == 3);
// 'data' in unspecified state, don't test
// read() with error
thrown.clear();
api.inject_error(4);
try {
data = read(api);
} catch ( std::exception const& e) {
thrown = e.what();
}
assert( thrown == make_exception("read", 4).what() );
std::cout << "done." << std::endl;
return EXIT_SUCCESS;
}
+167
View File
@@ -0,0 +1,167 @@
// Copyright Nat Goodspeed 2015.
// 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/fiber/all.hpp>
#include <memory> // std::shared_ptr
#include <thread>
#include <chrono>
#include <iostream>
#include <sstream>
#include <exception>
#include <cassert>
/*****************************************************************************
* example async API
*****************************************************************************/
// introduce class-scope typedef
struct AsyncAPIBase {
// error callback accepts an int error code; 0 == success
typedef int errorcode;
};
//[Response
// every async operation receives a subclass instance of this abstract base
// class through which to communicate its result
struct Response {
typedef std::shared_ptr< Response > ptr;
// called if the operation succeeds
virtual void success( std::string const& data) = 0;
// called if the operation fails
virtual void error( AsyncAPIBase::errorcode ec) = 0;
};
//]
// the actual async API
class AsyncAPI: public AsyncAPIBase {
public:
// constructor acquires some resource that can be read
AsyncAPI( std::string const& data);
//[method_init_read
// derive Response subclass, instantiate, pass Response::ptr
void init_read( Response::ptr);
//]
// ... other operations ...
void inject_error( errorcode ec);
private:
std::string data_;
errorcode injected_;
};
/*****************************************************************************
* fake AsyncAPI implementation... pay no attention to the little man behind
* the curtain...
*****************************************************************************/
AsyncAPI::AsyncAPI( std::string const& data) :
data_( data),
injected_( 0) {
}
void AsyncAPI::inject_error( errorcode ec) {
injected_ = ec;
}
void AsyncAPI::init_read( Response::ptr response) {
// make a local copy of injected_
errorcode injected( injected_);
// reset it synchronously with caller
injected_ = 0;
// local copy of data_ so we can capture in lambda
std::string data( data_);
// Simulate an asynchronous I/O operation by launching a detached thread
// that sleeps a bit before calling either completion method.
std::thread( [injected, response, data](){
std::this_thread::sleep_for( std::chrono::milliseconds(100) );
if ( ! injected) {
// no error, call success()
response->success( data);
} else {
// injected error, call error()
response->error( injected);
}
}).detach();
}
/*****************************************************************************
* adapters
*****************************************************************************/
// helper function
std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode);
//[PromiseResponse
class PromiseResponse: public Response {
public:
// called if the operation succeeds
virtual void success( std::string const& data) {
promise_.set_value( data);
}
// called if the operation fails
virtual void error( AsyncAPIBase::errorcode ec) {
promise_.set_exception(
std::make_exception_ptr(
make_exception("read", ec) ) );
}
boost::fibers::future< std::string > get_future() {
return promise_.get_future();
}
private:
boost::fibers::promise< std::string > promise_;
};
//]
//[method_read
std::string read( AsyncAPI & api) {
// Because init_read() requires a shared_ptr, we must allocate our
// ResponsePromise on the heap, even though we know its lifespan.
auto promisep( std::make_shared< PromiseResponse >() );
boost::fibers::future< std::string > future( promisep->get_future() );
// Both 'promisep' and 'future' will survive until our lambda has been
// called.
api.init_read( promisep);
return future.get();
}
//]
/*****************************************************************************
* helpers
*****************************************************************************/
std::runtime_error make_exception( std::string const& desc, AsyncAPI::errorcode ec) {
std::ostringstream buffer;
buffer << "Error in AsyncAPI::" << desc << "(): " << ec;
return std::runtime_error( buffer.str() );
}
/*****************************************************************************
* driving logic
*****************************************************************************/
int main(int argc, char *argv[]) {
// prime AsyncAPI with some data
AsyncAPI api("abcd");
// successful read(): retrieve it
std::string data( read( api) );
assert(data == "abcd");
// read() with error
std::string thrown;
api.inject_error(1);
try {
data = read( api);
} catch ( std::exception const& e) {
thrown = e.what();
}
assert(thrown == make_exception("read", 1).what() );
std::cout << "done." << std::endl;
return EXIT_SUCCESS;
}
+207
View File
@@ -0,0 +1,207 @@
// Copyright Nat Goodspeed 2015.
// 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/fiber/all.hpp>
#include <iostream>
#include <sstream>
#include <exception>
#include <string>
#include <algorithm> // std::min()
#include <errno.h> // EWOULDBLOCK
#include <cassert>
/*****************************************************************************
* example nonblocking API
*****************************************************************************/
//[NonblockingAPI
class NonblockingAPI {
public:
NonblockingAPI();
// nonblocking operation: may return EWOULDBLOCK
int read( std::string & data, std::size_t desired);
/*= ...*/
//<-
// for simulating a real nonblocking API
void set_data( std::string const& data, std::size_t chunksize);
void inject_error( int ec);
private:
std::string data_;
int injected_;
unsigned tries_;
std::size_t chunksize_;
//->
};
//]
/*****************************************************************************
* fake NonblockingAPI implementation... pay no attention to the little man
* behind the curtain...
*****************************************************************************/
NonblockingAPI::NonblockingAPI() :
injected_( 0),
tries_( 0),
chunksize_( 9999) {
}
void NonblockingAPI::set_data( std::string const& data, std::size_t chunksize) {
data_ = data;
chunksize_ = chunksize;
// This delimits the start of a new test. Reset state.
injected_ = 0;
tries_ = 0;
}
void NonblockingAPI::inject_error( int ec) {
injected_ = ec;
}
int NonblockingAPI::read( std::string & data, std::size_t desired) {
// in case of error
data.clear();
if ( injected_) {
// copy injected_ because we're about to reset it
auto injected( injected_);
injected_ = 0;
// after an error situation, restart success count
tries_ = 0;
return injected;
}
if ( ++tries_ < 5) {
// no injected error, but the resource isn't yet ready
return EWOULDBLOCK;
}
// tell caller there's nothing left
if ( data_.empty() ) {
return EOF;
}
// okay, finally have some data
// but return minimum of desired and chunksize_
std::size_t size( ( std::min)( desired, chunksize_) );
data = data_.substr( 0, size);
// strip off what we just returned
data_ = data_.substr( size);
// reset I/O retries count for next time
tries_ = 0;
// success
return 0;
}
/*****************************************************************************
* adapters
*****************************************************************************/
//[nonblocking_read_chunk
// guaranteed not to return EWOULDBLOCK
int read_chunk( NonblockingAPI & api, std::string & data, std::size_t desired) {
int error;
while ( EWOULDBLOCK == ( error = api.read( data, desired) ) ) {
// not ready yet -- try again on the next iteration of the
// application's main loop
boost::this_fiber::yield();
}
return error;
}
//]
//[nonblocking_read_desired
// keep reading until desired length, EOF or error
// may return both partial data and nonzero error
int read_desired( NonblockingAPI & api, std::string & data, std::size_t desired) {
// we're going to accumulate results into 'data'
data.clear();
std::string chunk;
int error = 0;
while ( data.length() < desired &&
( error = read_chunk( api, chunk, desired - data.length() ) ) == 0) {
data.append( chunk);
}
return error;
}
//]
//[nonblocking_IncompleteRead
// exception class augmented with both partially-read data and errorcode
class IncompleteRead : public std::runtime_error {
public:
IncompleteRead( std::string const& what, std::string const& partial, int ec) :
std::runtime_error( what),
partial_( partial),
ec_( ec) {
}
std::string get_partial() const {
return partial_;
}
int get_errorcode() const {
return ec_;
}
private:
std::string partial_;
int ec_;
};
//]
//[nonblocking_read
// read all desired data or throw IncompleteRead
std::string read( NonblockingAPI & api, std::size_t desired) {
std::string data;
int ec( read_desired( api, data, desired) );
// for present purposes, EOF isn't a failure
if ( 0 == ec || EOF == ec) {
return data;
}
// oh oh, partial read
std::ostringstream msg;
msg << "NonblockingAPI::read() error " << ec << " after "
<< data.length() << " of " << desired << " characters";
throw IncompleteRead( msg.str(), data, ec);
}
//]
int main( int argc, char *argv[]) {
NonblockingAPI api;
const std::string sample_data("abcdefghijklmnopqrstuvwxyz");
// Try just reading directly from NonblockingAPI
api.set_data( sample_data, 5);
std::string data;
int ec = api.read( data, 13);
// whoops, underlying resource not ready
assert(ec == EWOULDBLOCK);
assert(data.empty());
// successful read()
api.set_data( sample_data, 5);
data = read( api, 13);
assert(data == "abcdefghijklm");
// read() with error
api.set_data( sample_data, 5);
// don't accidentally pick either EOF or EWOULDBLOCK
assert(EOF != 1);
assert(EWOULDBLOCK != 1);
api.inject_error(1);
int thrown = 0;
try {
data = read( api, 13);
} catch ( IncompleteRead const& e) {
thrown = e.get_errorcode();
}
assert(thrown == 1);
std::cout << "done." << std::endl;
return EXIT_SUCCESS;
}
+98
View File
@@ -0,0 +1,98 @@
//
// daytime_client.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/system/system_error.hpp>
#include <boost/fiber/all.hpp>
#include "loop.hpp"
#include "use_future.hpp"
using boost::asio::ip::udp;
void get_daytime(boost::asio::io_service& io_service, const char* hostname)
{
try
{
udp::resolver resolver(io_service);
boost::fibers::future<udp::resolver::iterator> iter =
resolver.async_resolve(
udp::resolver::query( udp::v4(), hostname, "daytime"),
boost::fibers::asio::use_future);
// The async_resolve operation above returns the endpoint iterator as a
// future value that is not retrieved ...
udp::socket socket(io_service, udp::v4());
boost::array<char, 1> send_buf = {{ 0 }};
boost::fibers::future<std::size_t> send_length =
socket.async_send_to(boost::asio::buffer(send_buf),
*iter.get(), // ... until here. This call may block.
boost::fibers::asio::use_future);
// Do other things here while the send completes.
send_length.get(); // Blocks until the send is complete. Throws any errors.
boost::array<char, 128> recv_buf;
udp::endpoint sender_endpoint;
boost::fibers::future<std::size_t> recv_length =
socket.async_receive_from(
boost::asio::buffer(recv_buf),
sender_endpoint,
boost::fibers::asio::use_future);
// Do other things here while the receive completes.
std::cout.write(
recv_buf.data(),
recv_length.get()); // Blocks until receive is complete.
}
catch (boost::system::system_error& e)
{
std::cerr << e.what() << std::endl;
}
io_service.stop();
}
int main( int argc, char* argv[])
{
boost::asio::io_service io_service;
try
{
if (argc != 2)
{
std::cerr << "Usage: daytime_client <host>" << std::endl;
return 1;
}
boost::fibers::fiber(
boost::bind( get_daytime,
boost::ref( io_service), argv[1]) ).detach();
boost::fibers::asio::run_service( io_service);
}
catch ( std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
+89
View File
@@ -0,0 +1,89 @@
//
// daytime_client.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/system/system_error.hpp>
#include <boost/fiber/all.hpp>
#include "loop.hpp"
#include "yield.hpp"
using boost::asio::ip::udp;
void get_daytime(boost::asio::io_service& io_service, const char* hostname)
{
try
{
udp::resolver resolver(io_service);
udp::resolver::iterator iter =
resolver.async_resolve(
udp::resolver::query( udp::v4(), hostname, "daytime"),
boost::fibers::asio::yield);
udp::socket socket(io_service, udp::v4());
boost::array<char, 1> send_buf = {{ 0 }};
std::size_t send_length =
socket.async_send_to(boost::asio::buffer(send_buf),
*iter, boost::fibers::asio::yield);
(void)send_length;
boost::array<char, 128> recv_buf;
udp::endpoint sender_endpoint;
std::size_t recv_length =
socket.async_receive_from(
boost::asio::buffer(recv_buf),
sender_endpoint,
boost::fibers::asio::yield);
std::cout.write(
recv_buf.data(),
recv_length);
}
catch (boost::system::system_error& e)
{
std::cerr << e.what() << std::endl;
}
io_service.stop();
}
int main( int argc, char* argv[])
{
boost::asio::io_service io_service;
try
{
if (argc != 2)
{
std::cerr << "Usage: daytime_client <host>" << std::endl;
return 1;
}
boost::fibers::fiber(
boost::bind( get_daytime,
boost::ref( io_service), argv[1]) ).detach();
boost::fibers::asio::run_service( io_service);
}
catch ( std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
+172
View File
@@ -0,0 +1,172 @@
//
// promise_handler.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke and Nat Goodspeed
//
#ifndef BOOST_FIBERS_ASIO_DETAIL_PROMISE_HANDLER_HPP
#define BOOST_FIBERS_ASIO_DETAIL_PROMISE_HANDLER_HPP
#include <exception>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/exception/all.hpp>
#include <boost/fiber/all.hpp>
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
namespace detail {
// Completion handler to adapt a promise as a completion handler.
//[fibers_asio_promise_handler_base
template< typename T >
class promise_handler_base {
public:
typedef std::shared_ptr< boost::fibers::promise< T > > promise_ptr;
// Construct from any promise_completion_token subclass special value.
template< typename Allocator >
promise_handler_base( boost::fibers::asio::promise_completion_token< Allocator > const& pct) :
promise_( std::make_shared< boost::fibers::promise< T > >(
std::allocator_arg, pct.get_allocator() ) )
//<-
, ecp_( pct.ec_)
//->
{}
bool should_set_value( boost::system::error_code const& ec) {
if ( ! ec) {
// whew, success
return true;
}
//<-
// ec indicates error
if ( ecp_) {
// promise_completion_token bound an error_code variable: set it
* ecp_ = ec;
// This is the odd case: although there's an error, user code
// expressly forbid us to call set_exception(). We've set the
// bound error code -- but future::get() will wait forever unless
// we kick the promise SOMEHOW. Tell subclass to call set_value()
// anyway.
return true;
}
//->
// no bound error_code: cause promise_ to throw an exception
promise_->set_exception(
std::make_exception_ptr(
boost::system::system_error( ec) ) );
// caller should NOT call set_value()
return false;
}
promise_ptr get_promise() const {
return promise_;
}
private:
promise_ptr promise_;
//<-
boost::system::error_code * ecp_;
//->
};
//]
// generic promise_handler for arbitrary value
//[fibers_asio_promise_handler
template< typename T >
class promise_handler : public promise_handler_base< T > {
private:
//<-
using promise_handler_base< T >::should_set_value;
//->
public:
// Construct from any promise_completion_token subclass special value.
template< typename Allocator >
promise_handler( boost::fibers::asio::promise_completion_token< Allocator > const& pct) :
promise_handler_base< T >( pct) {
}
//<-
void operator()( T t) {
get_promise()->set_value( t);
}
//->
void operator()( boost::system::error_code const& ec, T t) {
if ( should_set_value( ec) ) {
get_promise()->set_value( t);
}
}
//<-
using typename promise_handler_base< T >::promise_ptr;
using promise_handler_base< T >::get_promise;
//->
};
//]
// specialize promise_handler for void
template<>
class promise_handler< void > : public promise_handler_base< void > {
private:
using promise_handler_base< void >::should_set_value;
public:
// Construct from any promise_completion_token subclass special value.
template< typename Allocator >
promise_handler( boost::fibers::asio::promise_completion_token< Allocator > const& pct) :
promise_handler_base< void >( pct) {
}
void operator()() {
get_promise()->set_value();
}
void operator()( boost::system::error_code const& ec) {
if ( should_set_value( ec) ) {
get_promise()->set_value();
}
}
using promise_handler_base< void >::promise_ptr;
using promise_handler_base< void >::get_promise;
};
}}}
namespace asio {
namespace detail {
// Specialize asio_handler_invoke hook to ensure that any exceptions thrown
// from the handler are propagated back to the caller via the future.
template< typename Function, typename T >
void asio_handler_invoke( Function f, fibers::asio::detail::promise_handler< T > * h) {
typename fibers::asio::detail::promise_handler< T >::promise_ptr
p( h->get_promise() );
try {
f();
} catch (...) {
p->set_exception( std::current_exception() );
}
}
}}}
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
#endif // BOOST_FIBERS_ASIO_DETAIL_PROMISE_HANDLER_HPP
+99
View File
@@ -0,0 +1,99 @@
//
// use_future.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke and Nat Goodspeed
//
#ifndef BOOST_FIBERS_ASIO_DETAIL_USE_FUTURE_HPP
#define BOOST_FIBERS_ASIO_DETAIL_USE_FUTURE_HPP
#include <boost/asio/async_result.hpp>
#include <boost/asio/handler_type.hpp>
#include <boost/fiber/all.hpp>
#include "promise_handler.hpp"
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
namespace detail {
// use_future_handler is just an alias for promise_handler -- but we must
// distinguish this case to specialize async_result below.
template< typename T >
using use_future_handler = promise_handler< T >;
}}}
namespace asio {
// Handler traits specialisation for use_future_handler.
template< typename T >
class async_result< fibers::asio::detail::use_future_handler< T > > {
public:
// The initiating function will return a future.
typedef boost::fibers::future< T > type;
// Constructor creates a new promise for the async operation, and obtains the
// corresponding future.
explicit async_result( fibers::asio::detail::use_future_handler< T > & h) {
value_ = h.get_promise()->get_future();
}
// Obtain the future to be returned from the initiating function.
type get() {
return boost::move( value_);
}
private:
type value_;
};
// Handler type specialisation for use_future for a nullary callback.
template< typename Allocator, typename ReturnType >
struct handler_type< boost::fibers::asio::use_future_t< Allocator >, ReturnType() > {
typedef fibers::asio::detail::use_future_handler< void > type;
};
// Handler type specialisation for use_future for a single-argument callback.
template< typename Allocator, typename ReturnType, typename Arg1 >
struct handler_type< boost::fibers::asio::use_future_t< Allocator >, ReturnType( Arg1) > {
typedef fibers::asio::detail::use_future_handler< Arg1 > type;
};
// Handler type specialisation for use_future for a callback passed only
// boost::system::error_code. Note the use of use_future_handler<void>: an
// error_code indicating error will be conveyed to consumer code via
// set_exception().
template< typename Allocator, typename ReturnType >
struct handler_type< boost::fibers::asio::use_future_t< Allocator >, ReturnType( boost::system::error_code) > {
typedef fibers::asio::detail::use_future_handler< void > type;
};
// Handler type specialisation for use_future for a callback passed
// boost::system::error_code plus an arbitrary value. Note the use of a
// single-argument use_future_handler: an error_code indicating error will be
// conveyed to consumer code via set_exception().
template< typename Allocator, typename ReturnType, typename Arg2 >
struct handler_type< boost::fibers::asio::use_future_t< Allocator >, ReturnType( boost::system::error_code, Arg2) > {
typedef fibers::asio::detail::use_future_handler< Arg2 > type;
};
}}
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
#endif // BOOST_FIBERS_ASIO_DETAIL_USE_FUTURE_HPP
+109
View File
@@ -0,0 +1,109 @@
//
// yield.hpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke and Nat Goodspeed
//
#ifndef BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
#define BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
#include <boost/asio/async_result.hpp>
#include <boost/asio/handler_type.hpp>
#include <boost/fiber/all.hpp>
#include "promise_handler.hpp"
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
namespace detail {
// yield_handler is just an alias for promise_handler -- but we must
// distinguish this case to specialize async_result below.
//[fibers_asio_yield_handler
template< typename T >
using yield_handler = promise_handler< T >;
//]
}}}
namespace asio {
// Handler traits specialisation for yield_handler.
template< typename T >
class async_result< fibers::asio::detail::yield_handler< T > > {
public:
// The initiating function will return a value of type T.
typedef T type;
// Constructor creates a new promise for the async operation, and obtains the
// corresponding future.
explicit async_result( fibers::asio::detail::yield_handler< T > & h) {
future_ = h.get_promise()->get_future();
}
// This blocks the calling fiber until the handler sets either a value or
// an exception.
type get() {
return future_.get();
}
private:
fibers::future< T > future_;
};
// Handler type specialisation for yield for a nullary callback.
template< typename Allocator, typename ReturnType >
struct handler_type< boost::fibers::asio::yield_t< Allocator >,
ReturnType() > {
typedef boost::fibers::asio::detail::yield_handler< void > type;
};
// Handler type specialisation for yield for a single-argument callback.
template< typename Allocator, typename ReturnType, typename Arg1 >
struct handler_type< boost::fibers::asio::yield_t< Allocator >,
ReturnType( Arg1) > {
typedef fibers::asio::detail::yield_handler< Arg1 > type;
};
// Handler type specialisation for yield for a callback passed only
// boost::system::error_code. Note the use of yield_handler<void>: an
// error_code indicating error will be conveyed to consumer code via an
// exception. Normal return implies (! error_code).
template< typename Allocator, typename ReturnType >
struct handler_type< boost::fibers::asio::yield_t< Allocator >,
ReturnType( boost::system::error_code) > {
typedef fibers::asio::detail::yield_handler< void > type;
};
// Handler type specialisation for yield for a callback passed
// boost::system::error_code plus an arbitrary value. Note the use of a
// single-argument yield_handler: an error_code indicating error will be
// conveyed to consumer code via an exception. Normal return implies (!
// error_code).
//[asio_handler_type
template< typename Allocator, typename ReturnType, typename Arg2 >
struct handler_type< boost::fibers::asio::yield_t< Allocator >,
ReturnType( boost::system::error_code, Arg2) > {
typedef fibers::asio::detail::yield_handler< Arg2 > type;
};
//]
}}
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
#endif // BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
+63
View File
@@ -0,0 +1,63 @@
//
// echo_client.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// This source is effectively identical to
// http://www.boost.org/doc/libs/release/doc/html/boost_asio/example/cpp03/echo/blocking_tcp_echo_client.cpp
// It does not use Boost.Fiber. It is copied here only for completeness. A
// server needs a client.
//
// 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 <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: echo_client <host> <port>\n";
return 1;
}
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), argv[1], argv[2]);
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::socket s(io_service);
boost::asio::connect(s, iterator);
using namespace std; // For strlen.
std::cout << "Enter message: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_length = strlen(request);
boost::asio::write(s, boost::asio::buffer(request, request_length));
char reply[max_length];
size_t reply_length = boost::asio::read(s,
boost::asio::buffer(reply, request_length));
std::cout << "Reply is: ";
std::cout.write(reply, reply_length);
std::cout << "\n";
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
+68
View File
@@ -0,0 +1,68 @@
//
// echo_client2.cpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// This source is almost identical to
// http://www.boost.org/doc/libs/release/doc/html/boost_asio/example/cpp03/echo/blocking_tcp_echo_client.cpp
// save that it deliberately introduces a timeout.
// It does not use Boost.Fiber. It is copied here only for completeness. A
// server needs a client.
//
// 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 <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: echo_client2 <host> <port>\n";
return 1;
}
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), argv[1], argv[2]);
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::socket s(io_service);
boost::asio::connect(s, iterator);
using namespace std; // For strlen.
std::cout << "Enter message: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_length = strlen(request);
boost::asio::write(s, boost::asio::buffer(request, request_length));
char reply[max_length];
size_t reply_length = boost::asio::read(s,
boost::asio::buffer(reply, request_length));
std::cout << "Reply is: ";
std::cout.write(reply, reply_length);
std::cout << "\nWe block for 10 seconds in order to let session timeout\n";
boost::this_thread::sleep( boost::posix_time::seconds( 10) );
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
+101
View File
@@ -0,0 +1,101 @@
//
// echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// 2013 Oliver Kowalke
//
// 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 <cstdlib>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/fiber/all.hpp>
#include "loop.hpp" // run_service()
#include "yield.hpp"
using boost::asio::ip::tcp;
const int max_length = 1024;
typedef boost::shared_ptr< tcp::socket > socket_ptr;
void session( socket_ptr sock)
{
try
{
for (;;)
{
char data[max_length];
boost::system::error_code ec;
std::size_t length = sock->async_read_some(
boost::asio::buffer( data),
boost::fibers::asio::yield[ec]);
if ( ec == boost::asio::error::eof)
break; //connection closed cleanly by peer
else if ( ec)
throw boost::system::system_error( ec); //some other error
boost::asio::async_write(
* sock,
boost::asio::buffer( data, length),
boost::fibers::asio::yield[ec]);
if ( ec == boost::asio::error::eof)
break; //connection closed cleanly by peer
else if ( ec)
throw boost::system::system_error( ec); //some other error
}
}
catch ( std::exception const& e)
{ std::cerr << "Exception in fiber: " << e.what() << "\n"; }
}
void server( boost::asio::io_service & io_service, unsigned short port)
{
tcp::acceptor a( io_service, tcp::endpoint( tcp::v4(), port) );
for (;;)
{
socket_ptr socket( new tcp::socket( io_service) );
boost::system::error_code ec;
std::cout << "wait for accept" << std::endl;
a.async_accept(
* socket,
boost::fibers::asio::yield[ec]);
std::cout << "accepted" << std::endl;
if ( ! ec) {
boost::fibers::fiber(
boost::bind( session, socket) ).detach();
}
}
}
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::fibers::fiber(
boost::bind( server, boost::ref( io_service), std::atoi( argv[1]) ) ).detach();
boost::fibers::asio::run_service( io_service);
}
catch ( std::exception const& e)
{ std::cerr << "Exception: " << e.what() << "\n"; }
return 0;
}
+137
View File
@@ -0,0 +1,137 @@
//
// echo_server2.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// 2013 Oliver Kowalke
//
// 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 <cstdlib>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/fiber/all.hpp>
#include "loop.hpp"
#include "yield.hpp"
using boost::asio::ip::tcp;
const int max_length = 1024;
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::fibers::fiber(
boost::bind(&session::echo,
shared_from_this())).detach();
boost::fibers::fiber(
boost::bind(&session::timeout,
shared_from_this())).detach();
}
private:
void echo()
{
try
{
char data[max_length];
for (;;)
{
timer_.expires_from_now(
boost::posix_time::seconds( 3) );
std::size_t n = socket_.async_read_some(
boost::asio::buffer( data),
boost::fibers::asio::yield);
boost::asio::async_write(
socket_,
boost::asio::buffer( data, n),
boost::fibers::asio::yield);
}
}
catch ( std::exception const& e)
{
socket_.close();
timer_.cancel();
}
}
void timeout()
{
while ( socket_.is_open() )
{
boost::system::error_code ignored_ec;
timer_.async_wait( boost::fibers::asio::yield[ignored_ec]);
if ( timer_.expires_from_now() <= boost::posix_time::seconds( 0) ) {
std::cout << "session to " << socket_.remote_endpoint() << " timed out" << std::endl;
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)
{
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(),
boost::fibers::asio::yield[ec]);
if ( ! ec) {
boost::fibers::fiber( boost::bind( & session::go, new_session) ).detach();
}
}
}
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;
using namespace std; // For atoi.
boost::fibers::fiber(
boost::bind( do_accept,
boost::ref( io_service), atoi( argv[1])) ).detach();
boost::fibers::asio::run_service( io_service);
}
catch ( std::exception const& e)
{ std::cerr << "Exception: " << e.what() << "\n"; }
return 0;
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright Eugene Yakubovich 2014.
// 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 <chrono>
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <boost/config.hpp>
#include <boost/fiber/all.hpp>
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
//[timer_handler
inline void timer_handler( boost::asio::high_resolution_timer & timer) {
boost::this_fiber::yield();
timer.expires_from_now( boost::fibers::wait_interval() );
timer.async_wait( std::bind( timer_handler, std::ref( timer) ) );
}
//]
//[run_service
inline void run_service( boost::asio::io_service & io_service) {
boost::asio::high_resolution_timer timer( io_service, std::chrono::seconds(0) );
timer.async_wait( std::bind( timer_handler, std::ref( timer) ) );
io_service.run();
}
//]
}}}
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
@@ -0,0 +1,88 @@
//
// promise_completion_token.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke and Nat Goodspeed
//
#ifndef BOOST_FIBERS_ASIO_PROMISE_COMPLETION_TOKEN_HPP
#define BOOST_FIBERS_ASIO_PROMISE_COMPLETION_TOKEN_HPP
#include <boost/config.hpp>
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
/// Common base class for yield_t and use_future_t. See also yield.hpp and
/// use_future.hpp.
/**
* The awkward name of this class is because it's not intended to be used
* directly in user code: it's the common base class for a couple of user-
* facing placeholder classes <tt>yield_t</tt> and <tt>use_future_t</tt>. They
* share a common handler class <tt>promise_handler</tt>.
*
* Each subclass (e.g. <tt>use_future_t</tt>) has a canonical instance
* (<tt>use_future</tt>). These may be used in the following ways as a
* Boost.Asio asynchronous operation completion token:
*
* <dl>
* <dt><tt>boost::fibers::asio::use_future</tt></dt>
* <dd>This is the canonical instance of <tt>use_future_t</tt>, provided
* solely for convenience. It causes <tt>promise_handler</tt> to allocate its
* internal <tt>boost::fibers::promise</tt> using a default-constructed
* default allocator (<tt>std::allocator<void></tt>).</dd>
* <dt><tt>boost::fibers::asio::use_future::with(alloc_instance)</tt></dt>
* <dd>This usage specifies an alternate allocator instance
* <tt>alloc_instance</tt>. It causes <tt>promise_handler</tt> to allocate its
* internal <tt>boost::fibers::promise</tt> using the specified
* allocator.</dd>
* </dl>
*/
//[fibers_asio_promise_completion_token
template< typename Allocator >
class promise_completion_token {
public:
typedef Allocator allocator_type;
/// Construct using default-constructed allocator.
BOOST_CONSTEXPR promise_completion_token() :
ec_( nullptr) {
}
/// Construct using specified allocator.
explicit promise_completion_token( Allocator const& allocator) :
ec_( nullptr),
allocator_( allocator) {
}
/// Obtain allocator.
allocator_type get_allocator() const {
return allocator_;
}
//private:
// used by some subclasses to bind an error_code to suppress exceptions
boost::system::error_code * ec_;
private:
Allocator allocator_;
};
//]
}}}
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
#endif // BOOST_FIBERS_ASIO_PROMISE_COMPLETION_TOKEN_HPP
@@ -0,0 +1,62 @@
//
// blocking_tcp_echo_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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 <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: publisher <host> <channel>\n";
return 1;
}
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), argv[1], "9997");
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::socket s(io_service);
boost::asio::connect(s, iterator);
char msg[max_length];
std::string channel(argv[2]);
std::memset(msg, '\0', max_length);
std::memcpy(msg, channel.c_str(), channel.size() );
boost::asio::write(s, boost::asio::buffer(msg, max_length));
for (;;)
{
std::cout << "publish: ";
char request[max_length];
std::cin.getline(request, max_length);
boost::asio::write(s, boost::asio::buffer(request, max_length));
}
}
catch ( std::exception const& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
+421
View File
@@ -0,0 +1,421 @@
//
// server.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2013 Oliver Kowalke
//
// 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 <cstddef>
#include <cstdlib>
#include <map>
#include <set>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/ref.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility.hpp>
#include <boost/fiber/all.hpp>
#include "../loop.hpp"
#include "../yield.hpp"
using boost::asio::ip::tcp;
const std::size_t max_length = 1024;
class subscriber_session;
typedef boost::shared_ptr<subscriber_session> subscriber_session_ptr;
// a channel has n subscribers (subscriptions)
// this class holds a list of subcribers for one channel
class subscriptions
{
public:
~subscriptions();
// subscribe to this channel
void subscribe( subscriber_session_ptr const& s)
{ subscribers_.insert( s); }
// unsubscribe from this channel
void unsubscribe( subscriber_session_ptr const& s)
{ subscribers_.erase(s); }
// publish a message, e.g. push this message to all subscribers
void publish( std::string const& msg);
private:
// list of subscribers
std::set<subscriber_session_ptr> subscribers_;
};
// a class to register channels and to subsribe clients to this channels
class registry : private boost::noncopyable
{
private:
typedef std::map< std::string, boost::shared_ptr< subscriptions > > channels_cont;
typedef channels_cont::iterator channels_iter;
boost::fibers::mutex mtx_;
channels_cont channels_;
void register_channel_( std::string const& channel)
{
if ( channels_.end() != channels_.find( channel) )
throw std::runtime_error("channel already exists");
channels_[channel] = boost::make_shared< subscriptions >();
std::cout << "new channel '" << channel << "' registered" << std::endl;
}
void unregister_channel_( std::string const& channel)
{
channels_.erase( channel);
std::cout << "channel '" << channel << "' unregistered" << std::endl;
}
void subscribe_( std::string const& channel, subscriber_session_ptr s)
{
channels_iter iter = channels_.find( channel);
if ( channels_.end() == iter )
throw std::runtime_error("channel does not exist");
iter->second->subscribe( s);
std::cout << "new subscription to channel '" << channel << "'" << std::endl;
}
void unsubscribe_( std::string const& channel, subscriber_session_ptr s)
{
channels_iter iter = channels_.find( channel);
if ( channels_.end() != iter )
iter->second->unsubscribe( s);
}
void publish_( std::string const& channel, std::string const& msg)
{
channels_iter iter = channels_.find( channel);
if ( channels_.end() == iter )
throw std::runtime_error("channel does not exist");
iter->second->publish( msg);
std::cout << "message '" << msg << "' to publish on channel '" << channel << "'" << std::endl;
}
public:
// add a channel to registry
void register_channel( std::string const& channel)
{
std::unique_lock< boost::fibers::mutex > lk( mtx_);
register_channel_( channel);
}
// remove a channel from registry
void unregister_channel( std::string const& channel)
{
std::unique_lock< boost::fibers::mutex > lk( mtx_);
unregister_channel_( channel);
}
// subscribe to a channel
void subscribe( std::string const& channel, subscriber_session_ptr s)
{
std::unique_lock< boost::fibers::mutex > lk( mtx_);
subscribe_( channel, s);
}
// unsubscribe from a channel
void unsubscribe( std::string const& channel, subscriber_session_ptr s)
{
std::unique_lock< boost::fibers::mutex > lk( mtx_);
unsubscribe_( channel, s);
}
// publish a message to all subscribers registerd to the channel
void publish( std::string const& channel, std::string const& msg)
{
std::unique_lock< boost::fibers::mutex > lk( mtx_);
publish_( channel, msg);
}
};
// a subscriber subscribes to a given channel in order to receive messages published on this channel
class subscriber_session : public boost::enable_shared_from_this< subscriber_session >
{
public:
explicit subscriber_session( boost::asio::io_service & io_service, registry & reg) :
socket_( io_service),
reg_( reg)
{}
tcp::socket& socket()
{ return socket_; }
// this function is executed inside the fiber
void run()
{
std::string channel;
try
{
boost::system::error_code ec;
// read first message == channel name
// async_ready() returns if the the complete message is read
// until this the fiber is suspended until the complete message
// is read int the given buffer 'data'
boost::asio::async_read(
socket_,
boost::asio::buffer( data_),
boost::fibers::asio::yield[ec]);
if ( ec) throw std::runtime_error("no channel from subscriber");
// first message ist equal to the channel name the publisher
// publishes to
channel = data_;
// subscribe to new channel
reg_.subscribe( channel, shared_from_this() );
// read published messages
for (;;)
{
// wait for a conditon-variable for new messages
// the fiber will be suspended until the condtion
// becomes true and the fiber is resumed
// published message is stored in buffer 'data_'
std::unique_lock< boost::fibers::mutex > lk( mtx_);
cond_.wait( lk);
std::string data( data_);
lk.unlock();
std::cout << "subscriber::run(): '" << data << std::endl;
// message '<fini>' terminates subscription
if ( "<fini>" == data) break;
// async. write message to socket connected with
// subscriber
// async_write() returns if the complete message was writen
// the fiber is suspended in the meanwhile
boost::asio::async_write(
socket_,
boost::asio::buffer( data, data.size() ),
boost::fibers::asio::yield[ec]);
if ( ec == boost::asio::error::eof)
break; //connection closed cleanly by peer
else if ( ec)
throw boost::system::system_error( ec); //some other error
std::cout << "subscriber::run(): '" << data << " written" << std::endl;
}
}
catch ( std::exception const& e)
{ std::cerr << "subscriber [" << channel << "] failed: " << e.what() << std::endl; }
// close socket
socket_.close();
// unregister channel
reg_.unsubscribe( channel, shared_from_this() );
}
// called from publisher_session (running in other fiber)
void publish( std::string const& msg)
{
std::unique_lock< boost::fibers::mutex > lk( mtx_);
std::memset(data_, '\0', sizeof( data_));
std::memcpy(data_, msg.c_str(), (std::min)(max_length, msg.size()));
cond_.notify_one();
}
private:
tcp::socket socket_;
registry & reg_;
boost::fibers::mutex mtx_;
boost::fibers::condition_variable cond_;
// fixed size message
char data_[max_length];
};
subscriptions::~subscriptions()
{
BOOST_FOREACH( subscriber_session_ptr s, subscribers_)
{ s->publish("<fini>"); }
}
void
subscriptions::publish( std::string const& msg)
{
BOOST_FOREACH( subscriber_session_ptr s, subscribers_)
{ s->publish( msg); }
}
// a publisher publishes messages on its channel
// subscriber might register to this channel to get the published messages
class publisher_session : public boost::enable_shared_from_this< publisher_session >
{
public:
explicit publisher_session( boost::asio::io_service & io_service, registry & reg) :
socket_( io_service),
reg_( reg)
{}
tcp::socket& socket()
{ return socket_; }
// this function is executed inside the fiber
void run()
{
std::string channel;
try
{
boost::system::error_code ec;
// fixed size message
char data[max_length];
// read first message == channel name
// async_ready() returns if the the complete message is read
// until this the fiber is suspended until the complete message
// is read int the given buffer 'data'
boost::asio::async_read(
socket_,
boost::asio::buffer( data),
boost::fibers::asio::yield[ec]);
if ( ec) throw std::runtime_error("no channel from publisher");
// first message ist equal to the channel name the publisher
// publishes to
channel = data;
// register the new channel
reg_.register_channel( channel);
// start publishing messages
for (;;)
{
// read message from publisher asyncronous
// async_read() suspends this fiber until the complete emssage is read
// and stored in the given buffer 'data'
boost::asio::async_read(
socket_,
boost::asio::buffer( data),
boost::fibers::asio::yield[ec]);
if ( ec == boost::asio::error::eof)
break; //connection closed cleanly by peer
else if ( ec)
throw boost::system::system_error( ec); //some other error
// publish message to all subscribers
reg_.publish( channel, std::string( data) );
}
}
catch ( std::exception const& e)
{ std::cerr << "publisher [" << channel << "] failed: " << e.what() << std::endl; }
// close socket
socket_.close();
// unregister channel
reg_.unregister_channel( channel);
}
private:
tcp::socket socket_;
registry & reg_;
};
typedef boost::shared_ptr< publisher_session > publisher_session_ptr;
// function accepts connections requests from clients acting as a publisher
void accept_publisher( boost::asio::io_service& io_service,
unsigned short port,
registry & reg)
{
// create TCP-acceptor
tcp::acceptor acceptor( io_service, tcp::endpoint( tcp::v4(), port) );
// loop for accepting connection requests
for (;;)
{
boost::system::error_code ec;
// create new publisher-session
// this instance will be associated with one publisher
publisher_session_ptr new_publisher_session =
boost::make_shared<publisher_session>( boost::ref( io_service), boost::ref( reg) );
// async. accept of new connection request
// this function will suspend this execution context (fiber) until a
// connection was established, after returning from this function a new client (publisher)
// is connected
acceptor.async_accept(
new_publisher_session->socket(),
boost::fibers::asio::yield[ec]);
if ( ! ec) {
// run the new publisher in its own fiber (one fiber for one client)
boost::fibers::fiber(
boost::bind( & publisher_session::run, new_publisher_session) ).detach();
}
}
}
// function accepts connections requests from clients acting as a subscriber
void accept_subscriber( boost::asio::io_service& io_service,
unsigned short port,
registry & reg)
{
// create TCP-acceptor
tcp::acceptor acceptor( io_service, tcp::endpoint( tcp::v4(), port) );
// loop for accepting connection requests
for (;;)
{
boost::system::error_code ec;
// create new subscriber-session
// this instance will be associated with one subscriber
subscriber_session_ptr new_subscriber_session =
boost::make_shared<subscriber_session>( boost::ref( io_service), boost::ref( reg) );
// async. accept of new connection request
// this function will suspend this execution context (fiber) until a
// connection was established, after returning from this function a new client (subscriber)
// is connected
acceptor.async_accept(
new_subscriber_session->socket(),
boost::fibers::asio::yield[ec]);
if ( ! ec) {
// run the new subscriber in its own fiber (one fiber for one client)
boost::fibers::fiber(
boost::bind( & subscriber_session::run, new_subscriber_session) ).detach();
}
}
}
int main( int argc, char* argv[])
{
try
{
// create io_service for async. I/O
boost::asio::io_service io_service;
// registry for channels and its subscription
registry reg;
// create an acceptor for publishers, run it as fiber
boost::fibers::fiber(
boost::bind( accept_publisher,
boost::ref( io_service), 9997, boost::ref( reg)) ).detach();
// create an acceptor for subscribers, run it as fiber
boost::fibers::fiber(
boost::bind( accept_subscriber,
boost::ref( io_service), 9998, boost::ref( reg)) ).detach();
boost::fibers::asio::run_service( io_service);
}
catch ( std::exception const& e)
{ std::cerr << "Exception: " << e.what() << "\n"; }
return 0;
}
@@ -0,0 +1,64 @@
//
// blocking_tcp_echo_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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 <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
enum { max_length = 1024 };
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: subscriber <host> <channel>\n";
return 1;
}
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), argv[1], "9998");
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::socket s(io_service);
boost::asio::connect(s, iterator);
char msg[max_length];
std::string channel(argv[2]);
std::memset(msg, '\0', max_length);
std::memcpy(msg, channel.c_str(), channel.size() );
boost::asio::write(s, boost::asio::buffer(msg, max_length));
for (;;)
{
char reply[max_length];
size_t reply_length = s.read_some(
boost::asio::buffer(reply, max_length));
std::cout << "published: ";
std::cout.write(reply, reply_length);
std::cout << std::endl;
}
}
catch (std::exception const& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
+74
View File
@@ -0,0 +1,74 @@
//
// use_future.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke and Nat Goodspeed
//
#ifndef BOOST_FIBERS_ASIO_USE_FUTURE_HPP
#define BOOST_FIBERS_ASIO_USE_FUTURE_HPP
#include <memory> // std::allocator
#include <boost/config.hpp>
#include "promise_completion_token.hpp"
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
/// Class used to specify that a Boost.Asio asynchronous operation should
/// return a future.
/**
* The use_future_t class is used to indicate that a Boost.Asio asynchronous
* operation should return a boost::fibers::future object. A use_future_t
* object may be passed as a handler to an asynchronous operation, typically
* using the special value @c boost::fibers::asio::use_future. For example:
*
* @code boost::fibers::future<std::size_t> my_future
* = my_socket.async_read_some(my_buffer, boost::fibers::asio::use_future); @endcode
*
* The initiating function (async_read_some in the above example) returns a
* future that will receive the result of the operation. If the operation
* completes with an error_code indicating failure, it is converted into a
* system_error and passed back to the caller via the future.
*/
template< typename Allocator = std::allocator< void > >
class use_future_t : public promise_completion_token< Allocator > {
public:
/// Construct using default-constructed allocator.
BOOST_CONSTEXPR use_future_t() {
}
/// Construct using specified allocator.
explicit use_future_t( Allocator const& allocator) :
promise_completion_token<Allocator>( allocator) {
}
/// Specify an alternate allocator.
template< typename OtherAllocator >
use_future_t< OtherAllocator > operator[]( OtherAllocator const& allocator) const {
return use_future_t< OtherAllocator >( allocator);
}
};
/// A special value, similar to std::nothrow.
BOOST_CONSTEXPR_OR_CONST use_future_t<> use_future;
}}} // namespace asio
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
#include "detail/use_future.hpp"
#endif // BOOST_FIBERS_ASIO_USE_FUTURE_HPP
+127
View File
@@ -0,0 +1,127 @@
//
// yield.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2013 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)
//
// modified by Oliver Kowalke and Nat Goodspeed
//
#ifndef BOOST_FIBERS_ASIO_YIELD_HPP
#define BOOST_FIBERS_ASIO_YIELD_HPP
#include <memory> // std::allocator
#include <boost/config.hpp>
#include "promise_completion_token.hpp"
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_PREFIX
#endif
namespace boost {
namespace fibers {
namespace asio {
/// Class used to specify that a Boost.Asio asynchronous operation should
/// suspend the calling fiber until completion.
/**
* The yield_t class is used to indicate that a Boost.Asio asynchronous
* operation should suspend the calling fiber until its completion. The
* asynchronous function will either return a suitable value, or will throw an
* exception indicating the error. A yield_t object may be passed as a handler
* to an asynchronous operation, typically using the special value @c
* boost::fibers::asio::yield. For example:
*
* @code std::size_t length_read
* = my_socket.async_read_some(my_buffer, boost::fibers::asio::yield); @endcode
*
* The initiating function (async_read_some in the above example) does not
* return to the calling fiber until the asynchronous read has completed. Like
* its synchronous counterpart, it returns the result of the operation. If the
* operation completes with an error_code indicating failure, it is converted
* into a system_error and thrown as an exception.
*
* To suppress a possible error exception:
* @code
* boost::system::error_code ec;
* std::size_t length_read =
* my_socket.async_read_some(my_buffer, boost::fibers::asio::yield[ec]);
* // test ec for success
* @endcode
*
* The crucial distinction between
* @code
* std::size_t length_read = my_socket.read_some(my_buffer);
* @endcode
* and
* @code
* std::size_t length_read =
* my_socket.async_read_some(my_buffer, boost::fibers::asio::yield);
* @code
* is that <tt>read_some()</tt> blocks the entire calling @em thread, whereas
* <tt>async_read_some(..., boost::fibers::asio::yield)</tt> blocks only the
* calling @em fiber, permitting other fibers on the same thread to continue
* running.
*
* To specify an alternate allocator for the internal
* <tt>boost::fibers::promise</tt>:
* @code
* boost::fibers::asio::yield.with(alloc_instance)
* @endcode
*
* To bind a <tt>boost::system::error_code</tt> @a ec as well as using an
* alternate allocator:
* @code
* boost::fibers::asio::yield.with(alloc_instance)[ec]
* @endcode
*/
//[fibers_asio_yield_t
template< typename Allocator = std::allocator< void > >
class yield_t : public promise_completion_token< Allocator > {
public:
/// Construct with default-constructed allocator.
BOOST_CONSTEXPR yield_t() {
}
/*= // ... ways to use an alternate allocator or bind an error_code ...*/
/*=};*/
//]
/// Construct using specified allocator.
explicit yield_t( Allocator const& allocator) :
promise_completion_token< Allocator >( allocator) {
}
/// Specify an alternate allocator.
template< typename OtherAllocator >
yield_t< OtherAllocator > with( OtherAllocator const& allocator) const {
return yield_t< OtherAllocator >( allocator);
}
/// Bind an error_code to suppress error exception.
yield_t operator[]( boost::system::error_code & ec) const {
// Return a copy because typical usage will be on our canonical
// instance. Don't leave the canonical instance with a dangling
// binding to a transient error_code!
yield_t tmp;
tmp.ec_ = & ec;
return tmp;
}
};
//[fibers_asio_yield
/// A special value, similar to std::nothrow.
BOOST_CONSTEXPR_OR_CONST yield_t<> yield;
//]
}}}
#ifdef BOOST_HAS_ABI_HEADERS
# include BOOST_ABI_SUFFIX
#endif
#include "detail/yield.hpp"
#endif // BOOST_FIBERS_ASIO_YIELD_HPP
+337
View File
@@ -0,0 +1,337 @@
// Copyright Nat Goodspeed 2014.
// 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 <iostream>
#include <boost/fiber/all.hpp>
#include <boost/fiber/scheduler.hpp>
#include <boost/noncopyable.hpp>
class Verbose: public boost::noncopyable {
public:
Verbose( std::string const& d, std::string const& s="stop") :
desc( d),
stop( s) {
std::cout << desc << " start" << std::endl;
}
~Verbose() {
std::cout << desc << ' ' << stop << std::endl;
}
private:
std::string desc;
std::string stop;
};
//[priority_props
class priority_props : public boost::fibers::fiber_properties {
public:
priority_props( boost::fibers::context * ctx):
fiber_properties( ctx), /*< Your subclass constructor must accept a
[^[class_link context]*] and pass it to
the `fiber_properties` constructor. >*/
priority_( 0) {
}
int get_priority() const {
return priority_; /*< Provide read access methods at your own discretion. >*/
}
// Call this method to alter priority, because we must notify
// priority_scheduler of any change.
void set_priority( int p) {
/*< It's important to call notify() on any
change in a property that can affect the
scheduler's behavior. Therefore, such
modifications should only be performed
through an access method. >*/
// Of course, it's only worth reshuffling the queue and all if we're
// actually changing the priority.
if ( p != priority_) {
priority_ = p;
notify();
}
}
// The fiber name of course is solely for purposes of this example
// program; it has nothing to do with implementing scheduler priority.
// This is a public data member -- not requiring set/get access methods --
// because we need not inform the scheduler of any change.
std::string name; /*< A property that does not affect the scheduler does
not need access methods. >*/
private:
int priority_;
};
//]
//[priority_scheduler
class priority_scheduler : public boost::fibers::sched_algorithm_with_properties< priority_props > {
private:
typedef boost::fibers::scheduler::ready_queue_t rqueue_t;
rqueue_t rqueue_;
public:
priority_scheduler() :
rqueue_() {
}
// For a subclass of sched_algorithm_with_properties<>, it's important to
// override the correct awakened() overload.
/*<< You must override the [member_link sched_algorithm_with_properties..awakened]
method. This is how your scheduler receives notification of a
fiber that has become ready to run. >>*/
virtual void awakened( boost::fibers::context * ctx, priority_props & props) {
int f_priority = props.get_priority(); /*< `props` is the instance of
priority_props associated
with the passed fiber `f`. >*/
// With this scheduler, fibers with higher priority values are
// preferred over fibers with lower priority values. But fibers with
// equal priority values are processed in round-robin fashion. So when
// we're handed a new context*, put it at the end of the fibers
// with that same priority. In other words: search for the first fiber
// in the queue with LOWER priority, and insert before that one.
rqueue_t::iterator i( rqueue_.begin() ), e( rqueue_.end() );
for ( ; i != e; ++i) {
if ( properties( & ( * i) ).get_priority() < f_priority) {
break;
}
}
// Now, whether or not we found a fiber with lower priority,
// insert this new fiber here.
rqueue_.insert( i, * ctx);
//<-
std::cout << "awakened(" << props.name << "): ";
describe_ready_queue();
//->
}
/*<< You must override the [member_link sched_algorithm_with_properties..pick_next]
method. This is how your scheduler actually advises the fiber manager
of the next fiber to run. >>*/
virtual boost::fibers::context * pick_next() {
// if ready queue is empty, just tell caller
if ( rqueue_.empty() ) {
return nullptr;
}
boost::fibers::context * ctx( & rqueue_.front() );
rqueue_.pop_front();
//<-
std::cout << "pick_next() resuming " << properties( ctx).name << ": ";
describe_ready_queue();
//->
return ctx;
}
/*<< You must override [member_link sched_algorithm_with_properties..ready_fibers]
to inform the fiber manager of the size of your ready queue. >>*/
virtual bool has_ready_fibers() const noexcept {
return ! rqueue_.empty();
}
/*<< Overriding [member_link sched_algorithm_with_properties..property_change]
is optional. This override handles the case in which the running
fiber changes the priority of another ready fiber: a fiber already in
our queue. In that case, move the updated fiber within the queue. >>*/
virtual void property_change( boost::fibers::context * ctx, priority_props & props) {
// Although our priority_props class defines multiple properties, only
// one of them (priority) actually calls notify() when changed. The
// point of a property_change() override is to reshuffle the ready
// queue according to the updated priority value.
//<-
std::cout << "property_change(" << props.name << '(' << props.get_priority()
<< ")): ";
//->
// Find 'f' in the queue. Note that it might not be in our queue at
// all, if caller is changing the priority of (say) the running fiber.
bool found = false;
rqueue_t::iterator e( rqueue_.end() );
for ( rqueue_t::iterator i( rqueue_.begin() ); i != e; ++i) {
if ( & ( * i) == ctx) {
// found the passed fiber in our list -- unlink it
found = true;
rqueue_.erase( i);
break;
}
}
// It's possible to get a property_change() call for a fiber that is
// not on our ready queue. If it's not there, no need to move it:
// we'll handle it next time it hits awakened().
if ( ! found) {
/*< Your `property_change()` override must be able to
handle the case in which the passed `f` is not in
your ready queue. It might be running, or it might be
blocked. >*/
//<-
// hopefully user will distinguish this case by noticing that
// the fiber with which we were called does not appear in the
// ready queue at all
describe_ready_queue();
//->
return;
}
// Here we know that f was in our ready queue, but we've unlinked it.
// We happen to have a method that will (re-)add a context* to
// the ready queue.
awakened( ctx, props);
}
//<-
void describe_ready_queue() {
if ( rqueue_.empty() ) {
std::cout << "[empty]";
} else {
const char * delim = "";
for ( boost::fibers::context & f : rqueue_) {
priority_props & props( properties( & f) );
std::cout << delim << props.name << '(' << props.get_priority() << ')';
delim = ", ";
}
}
std::cout << std::endl;
}
//->
};
//]
//[launch
template< typename Fn >
boost::fibers::fiber launch( Fn && func, std::string const& name, int priority) {
boost::fibers::fiber fiber( func);
priority_props & props( fiber.properties< priority_props >() );
props.name = name;
props.set_priority( priority);
return fiber;
}
//]
void yield_fn() {
std::string name( boost::this_fiber::properties< priority_props >().name);
Verbose v( std::string("fiber ") + name);
for ( int i = 0; i < 3; ++i) {
std::cout << "fiber " << name << " yielding" << std::endl;
boost::this_fiber::yield();
}
}
void barrier_fn( boost::fibers::barrier & barrier) {
std::string name( boost::this_fiber::properties< priority_props >().name);
Verbose v( std::string("fiber ") + name);
std::cout << "fiber " << name << " waiting on barrier" << std::endl;
barrier.wait();
std::cout << "fiber " << name << " yielding" << std::endl;
boost::this_fiber::yield();
}
//[change_fn
void change_fn( boost::fibers::fiber & other,
int other_priority,
boost::fibers::barrier& barrier) {
std::string name( boost::this_fiber::properties< priority_props >().name);
Verbose v( std::string("fiber ") + name);
//<-
std::cout << "fiber " << name << " waiting on barrier" << std::endl;
//->
barrier.wait();
// We assume a couple things about 'other':
// - that it was also waiting on the same barrier
// - that it has lower priority than this fiber.
// If both are true, 'other' is now ready to run but is sitting in
// priority_scheduler's ready queue. Change its priority.
priority_props & other_props(
other.properties< priority_props >() );
//<-
std::cout << "fiber " << name << " changing priority of " << other_props.name
<< " to " << other_priority << std::endl;
//->
other_props.set_priority( other_priority);
}
//]
//[main
int main( int argc, char *argv[]) {
// make sure we use our priority_scheduler rather than default round_robin
boost::fibers::use_scheduling_algorithm< priority_scheduler >();
/*= ...*/
/*=}*/
//]
Verbose v("main()");
// for clarity
std::cout << "main() setting name" << std::endl;
//[main_name
boost::this_fiber::properties< priority_props >().name = "main";
//]
std::cout << "main() running tests" << std::endl;
{
Verbose v("high-priority first", "stop\n");
// verify that high-priority fiber always gets scheduled first
boost::fibers::fiber low( launch( yield_fn, "low", 1) );
boost::fibers::fiber med( launch( yield_fn, "medium", 2) );
boost::fibers::fiber hi( launch( yield_fn, "high", 3) );
std::cout << "main: high.join()" << std::endl;
hi.join();
std::cout << "main: medium.join()" << std::endl;
med.join();
std::cout << "main: low.join()" << std::endl;
low.join();
}
{
Verbose v("same priority round-robin", "stop\n");
// fibers of same priority are scheduled in round-robin order
boost::fibers::fiber a( launch( yield_fn, "a", 0) );
boost::fibers::fiber b( launch( yield_fn, "b", 0) );
boost::fibers::fiber c( launch( yield_fn, "c", 0) );
std::cout << "main: a.join()" << std::endl;
a.join();
std::cout << "main: b.join()" << std::endl;
b.join();
std::cout << "main: c.join()" << std::endl;
c.join();
}
{
Verbose v("barrier wakes up all", "stop\n");
// using a barrier wakes up all waiting fibers at the same time
boost::fibers::barrier barrier( 3);
boost::fibers::fiber low( launch( [&barrier](){ barrier_fn( barrier); }, "low", 1) );
boost::fibers::fiber med( launch( [&barrier](){ barrier_fn( barrier); }, "medium", 2) );
boost::fibers::fiber hi( launch( [&barrier](){ barrier_fn( barrier); }, "high", 3) );
std::cout << "main: low.join()" << std::endl;
low.join();
std::cout << "main: medium.join()" << std::endl;
med.join();
std::cout << "main: high.join()" << std::endl;
hi.join();
}
{
Verbose v("change priority", "stop\n");
// change priority of a fiber in priority_scheduler's ready queue
boost::fibers::barrier barrier( 3);
boost::fibers::fiber c( launch( [&barrier](){ barrier_fn( barrier); }, "c", 1) );
boost::fibers::fiber a( launch( [&c,&barrier]() { change_fn( c, 3, barrier); }, "a", 3) );
boost::fibers::fiber b( launch( [&barrier](){ barrier_fn( barrier); }, "b", 2) );
std::cout << "main: a.join()" << std::endl;
std::cout << "main: a.join()" << std::endl;
a.join();
std::cout << "main: b.join()" << std::endl;
b.join();
std::cout << "main: c.join()" << std::endl;
c.join();
}
std::cout << "done." << std::endl;
return EXIT_SUCCESS;
}
File diff suppressed because it is too large Load Diff
+178
View File
@@ -0,0 +1,178 @@
// Copyright Nat Goodspeed 2015.
// 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/fiber/all.hpp>
#include <boost/assert.hpp>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <thread>
#include <mutex>
#include <queue>
#include <string>
/*****************************************************************************
* shared_ready_queue scheduler
*****************************************************************************/
// This simple scheduler is like round_robin, except that it shares a common
// ready queue among all participating threads. A thread participates in this
// pool by executing use_scheduling_algorithm<shared_ready_queue>() before any
// other Boost.Fiber operation.
class shared_ready_queue : public boost::fibers::sched_algorithm {
private:
typedef std::unique_lock< std::mutex > lock_t;
typedef std::queue< boost::fibers::context * > rqueue_t;
// The important point about this ready queue is that it's a class static,
// common to all instances of shared_ready_queue.
static rqueue_t rqueue_;
// so is this mutex
static std::mutex mtx_;
// Reserve a separate, scheduler-specific slot for this thread's main
// fiber. When we're passed the main fiber, stash it there instead of in
// the shared queue: it would be Bad News for thread B to retrieve and
// attempt to execute thread A's main fiber. This slot might be empty
// (nullptr) or full: pick_next() must only return the main fiber's
// context* after it has been passed to awakened().
boost::fibers::context * main_ctx_;
boost::fibers::context * dispatcher_ctx_;
public:
shared_ready_queue() :
main_ctx_( nullptr),
dispatcher_ctx_( nullptr) {
}
virtual void awakened( boost::fibers::context * ctx) {
BOOST_ASSERT( nullptr != ctx);
// recognize when we're passed this thread's main fiber
if ( ctx->is_main_context() ) {
// never put this thread's main fiber on the queue
// stash it in separate slot
main_ctx_ = ctx;
// recognize when we're passed this thread's dispatcher fiber
} else if ( ctx->is_dispatcher_context() ) {
// never put this thread's main fiber on the queue
// stash it in separate slot
dispatcher_ctx_ = ctx;
} else {
ctx->worker_unlink();
ctx->set_scheduler( nullptr);
// ordinary fiber, enqueue on shared queue
lock_t lock( mtx_);
rqueue_.push( ctx);
}
}
virtual boost::fibers::context * pick_next() {
lock_t lock( mtx_);
boost::fibers::context * victim( nullptr);
if ( ! rqueue_.empty() ) {
// good, we have an item in the ready queue, pop it
victim = rqueue_.front();
rqueue_.pop();
BOOST_ASSERT( nullptr != victim);
} else if ( nullptr != main_ctx_) {
// nothing in the ready queue, return main_ctx_
victim = main_ctx_;
// once we've returned main_ctx_, clear the slot
main_ctx_ = nullptr;
} else if ( nullptr != dispatcher_ctx_) {
// nothing in the ready queue, return dispatcher_ctx_
victim = dispatcher_ctx_;
// once we've returned dispatcher_ctx_, clear the slot
dispatcher_ctx_ = nullptr;
}
return victim;
}
virtual bool has_ready_fibers() const noexcept {
lock_t lock( mtx_);
return ! rqueue_.empty() || nullptr != main_ctx_;
}
};
shared_ready_queue::rqueue_t shared_ready_queue::rqueue_;
std::mutex shared_ready_queue::mtx_;
/*****************************************************************************
* example fiber function
*****************************************************************************/
void whatevah( char me) {
std::thread::id my_thread = std::this_thread::get_id();
{
std::ostringstream buffer;
//buffer << "fiber " << me << " started on thread " << my_thread << '\n';
std::cout << buffer.str() << std::flush;
}
for ( unsigned i = 0; i < 10; ++i) {
boost::this_fiber::yield();
std::thread::id new_thread = std::this_thread::get_id();
if ( new_thread != my_thread) {
my_thread = new_thread;
std::ostringstream buffer;
//buffer << "fiber " << me << " switched to thread " << my_thread << '\n';
std::cout << buffer.str() << std::flush;
}
}
}
/*****************************************************************************
* example thread function
*****************************************************************************/
// Wait until all running fibers have completed. This works because we happen
// to know that all example fibers use yield(), which leaves them in ready
// state. A fiber blocked on a synchronization object is invisible to
// ready_fibers().
void drain() {
// THIS fiber is running, so won't be counted among "ready" fibers
while ( boost::fibers::has_ready_fibers() ) {
boost::this_fiber::yield();
}
}
void thread() {
boost::fibers::use_scheduling_algorithm< shared_ready_queue >();
drain();
}
/*****************************************************************************
* main()
*****************************************************************************/
int main( int argc, char *argv[]) {
// use shared_ready_queue for main thread too, so we launch new fibers
// into shared pool
boost::fibers::use_scheduling_algorithm< shared_ready_queue >();
for ( int i = 0; i < 10; ++i) {
// launch a number of fibers
for ( char c : std::string("abcdefghijklmnopqrstuvwxyz")) {
boost::fibers::fiber([c](){ whatevah( c); }).detach();
}
// launch a couple threads to help process them
std::thread threads[] = {
std::thread( thread),
std::thread( thread),
std::thread( thread),
std::thread( thread),
std::thread( thread)
};
// drain running fibers
drain();
// wait for threads to terminate
for ( std::thread & t : threads) {
t.join();
}
}
return EXIT_SUCCESS;
}