
All - Today (Dec 10th) is the start of the format review of the Asynchronous I/O library (asio) library by Christopher Kohlhoff. The review will run until Friday December 23rd. I will be serving as review manager. From the library synopsis: Boost.Asio is a cross-platform C++ library for network programming that provides developers with a consistent asynchronous I/O model using a modern C++ approach. Downloads of the library as well as online documentation can be found at: http://asio.sourceforge.net/ (please refresh the page if you don't see "Boost Review Material" at the top) http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/ As usual, please state in review comments how you reviewed the library and whether the you think the library should be accepted into Boost. Further guidelines for writing reviews can be found on the website at: http://www.boost.org/more/formal_review_process.htm#Comments Please review early and often! Thanks, Jeff ******************************************************* A Few Library Details: ******************************************************* Supported Platforms The following platforms and compilers have been tested: * Win32 using Visual C++ 7.1 and Visual C++ 8.0. * Win32 using Borland C++Builder 6 patch 4. * Win32 using MinGW. * Linux (2.4 or 2.6 kernels) using g++ 3.3 or later. * Solaris using g++ 3.3 or later. * Mac OS X 10.4 using g++ 3.3 or later. Rationale The Boost.Asio library is intended for programmers using C++ for systems programming, where access to operating system functionality such as networking is often required. In particular, Boost.Asio attempts to address the following goals: * Portability. The library should support, and provide consistent behaviour across, a range of commonly used operating systems. * Scalability. The library should allow, and indeed encourage, the development of network applications that scale to hundreds or thousands of concurrent connections. The library implementation for each operating system should use the mechanism that best enables this scalability. * Efficiency. The library should support techniques such as scatter-gather I/O, and allow protocol implementations that minimise data copying. * Model Berkeley sockets. The Berkeley sockets API is widely implemented and understood, as well as being covered in much literature. Other programming languages often use a similar interface for networking APIs. * Ease of use. Lower the entry barrier for new users by taking a toolkit, rather than framework, approach. That is, try to minimise the up-front investment in time to just learning a few basic rules and guidelines. After that, a library user should only need to understand the specific functions that are being used. * Basis for further abstraction. The library should permit the development of other libraries that provide higher levels of abstraction. For example, implementations of commonly used protocols such as HTTP. Although the current incarnation of Boost.Asio focuses primarily on networking, its concepts of asynchronous I/O can be extended to include other operating system resources such as files.

Hi Christopher, Attempting to compile the synchronous timer example. I get the message xx\boost\asio\detail\socket_types.hpp "sys/ioctl.h" no such file or directory. compiler VC7.1 in Windows XP. Any thoughts? regards Andy Little -- Wake Up to Global Warming

Hi Andy, --- Andy Little <andy@servocomm.freeserve.co.uk> wrote:
Attempting to compile the synchronous timer example. I get the message xx\boost\asio\detail\socket_types.hpp "sys/ioctl.h" no such file or directory. compiler VC7.1 in Windows XP. Any thoughts?
It can only get into the #else branch that includes sys/ioctl.h if BOOST_WINDOWS is not defined. This would occur if BOOST_DISABLE_WIN32 is being defined somewhere, such as when compiling with the /Za option. If you are please try removing it. Cheers, Chris

"Christopher Kohlhoff" wrote
Hi Andy,
--- Andy Little wrote:
Attempting to compile the synchronous timer example. I get the message xx\boost\asio\detail\socket_types.hpp "sys/ioctl.h" no such file or directory. compiler VC7.1 in Windows XP. Any thoughts?
It can only get into the #else branch that includes sys/ioctl.h if BOOST_WINDOWS is not defined. This would occur if BOOST_DISABLE_WIN32 is being defined somewhere, such as when compiling with the /Za option. If you are please try removing it.
That worked! :-) I am currently looking at the asynchromous timer tutorial example. I'm not clear where the timer thread starts. Does it start with the call to t.async_wait(print); in the code below? #include <iostream> #include <boost/asio.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/timer.hpp> void print(const boost::asio::error& /*e*/); // some thing to keep the main thread busy void long_main_process_function(); int main() { boost::asio::demuxer d; boost::asio::deadline_timer t(d, boost::posix_time::seconds(5)); // Is the timer thread starting in this call? t.async_wait(print); long_main_process_function(); d.run(); } void long_main_process_function() { std::cout << "main process long function beginning\n"; boost::timer t1; int temp = 1; while(t1.elapsed() < 3){ if ( t1.elapsed() >= temp){ std::cout << " tick " << temp << " s\n"; temp += 1; } } std::cout << "main process long function done\n"; } void print(const boost::asio::error& /*e*/) { std::cout << "thread finished\n"; }

--- Andy Little <andy@servocomm.freeserve.co.uk> wrote:
I am currently looking at the asynchromous timer tutorial example. I'm not clear where the timer thread starts. Does it start with the call to t.async_wait(print);
No, the thread is started in the deadline_timer's constructor. To be more specific, it is started in the first deadline_timer constructed on a given demuxer, by the deadline_timer_service that they all share, and the thread is shared between all timers created on that demuxer (as well as currently being used for any asynchronous socket connect operations too). Note that this info is for Win32 only. Please see this page for details: http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/design/d... (You can find it in your local copy of the documentation by clicking "Design" in the top right hand corner and then clicking "Platform-Specific Implementation" from the list.) Cheers, Chris

Hi Cristopher, Some impressions of the Asio library, bearing in mind that I am a complete novice in this area, so the impressions are written from an ignorant viewpoint! Introduction----------- I'm confused as to why the library is called after asynchronous input/output. Should it not rather be called an asynchronous demultiplexer? That would make clearer what the library is designed for. I would also like to address the claims regarding scaleability. Perfect scaleability would enable me to create and use a single connection without requiring to set up the demultiplexer. As I understand it this is not the case. Simple jobs are affected by the need for high performance in large volumes of connections. The library seems to be about providing high volume network data services rather than about asynchronous I/O in the abstract. I feel that the name is confusing therefore. I would like an example useage of the library in the introduction that shows off the library in the large scale application its designed for because that would help to set the scene for the rest of the documentation and help to put right my incorrect expectations of what the library is about and who it is targetted at.. Examples might be a network server or a router maybe ( If these are valid applications of course) ... whatever.. something to help me to set the scene as a beginner. Examples--------- The first example I would like to see would involve showing how to save a file asynchronously. This would IMO be a good example because it should allow anyone to get a result without requiring the additional complexities of a network. I presume one would have to invoke the demultiplexer for a single file, but at least the demultiplexers raison d'aitre could be introduced here.. The two initial tutorial examples discuss timers with timeout handlers. It is difficult for me to see how these timers relate to asynchronous I/O. Why do I need the demultiplexer to set up a timer anyway. Where is it leading? Why do Ihave a multiplexer.run() function when the timer seems to have already started?.I need more context for what this tutorial is telling me. The mechanical structureof the library could be better explained before launching into the tutorial. e.g "The asio library is centered around a demultiplexer which controls the lifetime of asynchronous processes attached to it. An asynchronous process is governed by a timer and a completion event , which signals the end of its lifetime". Or whatever, but again a more detailed explanation of what I am dealing with would be helpful. As it stands I am presented with a tutorial where I use a demultiplexer without really having much idea why. As far as the networking examples go, I would prefer to have an example set of data provided so that I could get the examples to work immediately with out requiring any knowledege of the protocol. Is it not possible for example to download a sample web page from the asio doc site or something? As it stands I get a cryptic message "The requested name is valid and was found in the database, but it does not have the correct data being resolved for". (Even if I put complete rubbish in). The"useage client <host>" error message on not providing input parameters is equally cryptic, but I cant find any more detailed information. BTW In the synchronous TCP daytime client example why does the host resolver depend on the demultiplexerer? I feel that the design using the demultiplexer is quite monolithic. Rationale--------- It seems from other comments, to be the case that the interface ( function names etc) is based on existing networking libraries. If this is so then it ought to be discussed in the documentation. A section on the features and issue with other networking libraries and why their features were accepted/ rejected for asio design would be nice, a rationale for the design decisions made. Conclusions-------------- My impressions are based on a very incomplete look at the library and little experience of networking applications. Apologies for anything incorrect or unintentionally abrupt in my review. Its obvious from other reviews that the library is highly regarded by those experienced in the field. For myself, I suspect the design used is rather monolithic and therefore not scaleable very well in the down direction, however having little experience in this area I'm not really in a position to suggest improvements. I'll be grateful for what I can get therefore. Vote---- I vote Yes to accept the Asio librray into boost. Thanks to Christopher for the time and effort he has put into it. regards Andy Little

Andy Little wrote:
Hi Cristopher,
Some impressions of the Asio library, bearing in mind that I am a complete novice in this area, so the impressions are written from an ignorant viewpoint!
For what it's worth, I must concur with Andy on this one ... I too find myself a novice in this domain, and while I do follow the examples, I find that there is a disturbing lack of motivation in the documentation as to why we do this or that. That being said, I realize that we are speaking of a discipline that is learnt with study and a great deal of practise, and so, I also don't expect the documentation to be a substitute for much of that either. Nonetheless, I reach very much the same conclusions as Andy had done, and for what it is worth I also vote Yes, for inclusion of Asio into Boost - if only on the back of others praise for the work. I think Christopher has done an admirable job on this work, and it still amazes me that Asio remains a header only implementation - it certainly is a most comendable effort. ... I certainly like to think that I'll use this library in future, even if only for my own erudition in the subject. Great job Chris!
[snip ... pertinent dialogue of Andy's]
Cheers, -- Manfred Doudar MetOcean Engineers www.metoceanengineers.com

At 20:08 2005-12-09, Jeff Garland wrote:
All -
Today (Dec 10th) is the start of the format review of the Asynchronous I/O library (asio) library by Christopher Kohlhoff. The review will run until Friday December 23rd.
I think we _could_ have picked a worse time to have a review if we'd only tried a little harder. How about delaying it a week and _really_ hitting the holidays... le't's see, we could close the formal review at the end of the Rose Bowl game just for pizazz. Sorry guys, I think this is one of the worst times to try to get volunteers to do anything, let alone a serious job of reviewing a new library. IMO, it doesn't matter if the library is perfect, there are enough of us< who understand the problems thoroughly, who just won't be able to cobble together the time to do an honest review. So, given that I can't review it, I'll vote NO now (I can't vote YES on anything I haven't read (in stark contrast to the entire House of Representatives in the U.S. Congress)). I do appeal to delay the actual review until AFTER New Years Day.
I will be serving as review manager.
From the library synopsis:
Boost.Asio is a cross-platform C++ library for network programming that provides developers with a consistent asynchronous I/O model using a modern C++ approach.
Downloads of the library as well as online documentation can be found at:
http://asio.sourceforge.net/ (please refresh the page if you don't see "Boost Review Material" at the top) http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/
As usual, please state in review comments how you reviewed the library and whether the you think the library should be accepted into Boost. Further guidelines for writing reviews can be found on the website at:
http://www.boost.org/more/formal_review_process.htm#Comments
Please review early and often!
Thanks,
Jeff
******************************************************* A Few Library Details: ******************************************************* Supported Platforms
The following platforms and compilers have been tested:
* Win32 using Visual C++ 7.1 and Visual C++ 8.0. * Win32 using Borland C++Builder 6 patch 4. * Win32 using MinGW. * Linux (2.4 or 2.6 kernels) using g++ 3.3 or later. * Solaris using g++ 3.3 or later. * Mac OS X 10.4 using g++ 3.3 or later.
Rationale
The Boost.Asio library is intended for programmers using C++ for systems programming, where access to operating system functionality such as networking is often required. In particular, Boost.Asio attempts to address the following goals:
* Portability. The library should support, and provide consistent behaviour across, a range of commonly used operating systems. * Scalability. The library should allow, and indeed encourage, the development of network applications that scale to hundreds or thousands of concurrent connections. The library implementation for each operating system should use the mechanism that best enables this scalability. * Efficiency. The library should support techniques such as scatter-gather I/O, and allow protocol implementations that minimise data copying. * Model Berkeley sockets. The Berkeley sockets API is widely implemented and understood, as well as being covered in much literature. Other programming languages often use a similar interface for networking APIs. * Ease of use. Lower the entry barrier for new users by taking a toolkit, rather than framework, approach. That is, try to minimise the up-front investment in time to just learning a few basic rules and guidelines. After that, a library user should only need to understand the specific functions that are being used. * Basis for further abstraction. The library should permit the development of other libraries that provide higher levels of abstraction. For example, implementations of commonly used protocols such as HTTP.
Although the current incarnation of Boost.Asio focuses primarily on networking, its concepts of asynchronous I/O can be extended to include other operating system resources such as files.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
Victor A. Wagner Jr. http://rudbek.com The five most dangerous words in the English language: "There oughta be a law"

Victor A. Wagner Jr. wrote:
I think we _could_ have picked a worse time to have a review if we'd only tried a little harder. How about delaying it a week and _really_ hitting the holidays... le't's see, we could close the formal review at the end of the Rose Bowl game just for pizazz.
Sorry guys, I think this is one of the worst times to try to get volunteers to do anything, let alone a serious job of reviewing a new library. IMO, it doesn't matter if the library is perfect, there are enough of us< who understand the problems thoroughly, who just won't be able to cobble together the time to do an honest review.
So, given that I can't review it, I'll vote NO now (I can't vote YES on anything I haven't read (in stark contrast to the entire House of Representatives in the U.S. Congress)).
I do appeal to delay the actual review until AFTER New Years Day.
I understand it is a bad time. So is the whole summer when people are off on vacations. In short, it's always a bad time for someone. Personally I think one hour should be enough to do a basic review focused on one aspect -- say documentation -- or examples. I think 3 hours should be enough for an in-depth review. Also, I've done some off-list recruiting for review submissions and I'm hopeful that we will have good coverage. Finally, if the end of the review is reached and it is insufficient then the library will not be accepted and we'll have to work something else out. Jeff

"Victor A. Wagner Jr." wrote [...]
Sorry guys, I think this is one of the worst times to try to get volunteers to do anything, let alone a serious job of reviewing a new library. IMO, it doesn't matter if the library is perfect, there are enough of us< who understand the problems thoroughly, who just won't be able to cobble together the time to do an honest review.
So, given that I can't review it, I'll vote NO now (I can't vote YES on anything I haven't read (in stark contrast to the entire House of Representatives in the U.S. Congress)).
Its always a bad time for someone though isnt it? I feel quite strongly that an abstention would surely be more appropriate in that case, especially given the fact that the number of reviewers for the last few reviews have been in single figures, so your No vote might add up to 25% of the total vote. Therefore I hope you will reconsider that decision. I sincerely believe that boost should be bending over backwards now to persuade knowledgeable boost memebers to contribute to these reviews. (If the level of interest does not increase then I reckon that The writing is on the wall for C++ in the long term) I have spent some short time looking at the documentation for Boost.Asio and though I dont know much about the subject, it seems to me that a large amount of work has gone into this library. Networking is one of the areas in which C++ is well behind other languages. From my own experience of writing libraries I know that feedback good or bad is often what makes the difference between life and death for a library. I think it is now widely acknowledged that C++ main weakness compared with Java is its lack of standard libraries. Please therefore find the time to do a short review and help make the difference. regards Andy Little -- Wake Up to Global Warming

On 12/10/05, Victor A. Wagner Jr. <vawjr@rudbek.com> wrote:
Sorry guys, I think this is one of the worst times to try to get volunteers to do anything, let alone a serious job of reviewing a new library. IMO, it doesn't matter if the library is perfect, there are enough of us< who understand the problems thoroughly, who just won't be able to cobble together the time to do an honest review.
So, given that I can't review it, I'll vote NO now (I can't vote YES on anything I haven't read (in stark contrast to the entire House of Representatives in the U.S. Congress)).
This is a complete cop-out and an insult to the people who *do* plan to spend time reviewing and using this library, not to mention its author. I personally plan to write a review of asio and have been spending time using it in advance of this. A modern C++ networking library is one of the most fundamental pieces of functionality from Standard C++. Reviewing this library is therefore worth *anyones* time, particularly someone who "understand the problems thoroughly". Perhaps you are unable to devote the necessary time to write a formal review, but to then cast a backhanded vote rejecting it without any analysis is offensive and spiteful. As suchm I don't think this vote should be counted towards any formal tallys. I am sure there will be enough thoughtful reviews and criticism from other Boosters who can and will spend the time necessary, that this formal review will be completed before the Grinch hits town. -- Caleb Epstein caleb dot epstein at gmail dot com

Jeff Garland wrote:
All -
Today (Dec 10th) is the start of the format review of the Asynchronous I/O library (asio) library by Christopher Kohlhoff. The review will run until Friday December 23rd. I will be serving as review manager.
I am not that well versed in the target domain, but I do have a basic understanding of how everything works. As has been mentioned, having a (standard) socket API will greatly help C++ compete with other languages that have extensive libraries such as Java. I have only taken a cursory glance at the docs at the moment. From what I have read, there is built in support for TCP and UDP I/O, but you could extend this to support other transports such as asynchronous files or named pipes. Is the interface generic enough so I can do this easily? How would I provide this functionality? (If this is in the docs, please tell me to RTFM ;) hopefully with a link to where in the docs it is). Another question: is there a way I can bind a socket to an I/O stream so I can easily send data across a network. Also, what would be interesting is an asynchronous serialisation archive so you can serialise/deserialise classes across a network like you can in Java. The documentation does not make it clear exactly what functionality is available, so the docs could be clearer. For example, I didn't know the library supported IPv4 addresses before looking at the reference. It may be useful at some point to have support for IPv6 addresses. The main question is will these be compatible with the library as it interacts with the IPv4 addresses. NOTE: I haven't made up my mind yet, but I will place a preliminary YES vote. - Reece

Reece Dunn wrote:
Also, what would be interesting is an asynchronous serialisation archive so you can serialise/deserialise classes across a network like you can in Java.
There is an example that uses boost.serialization in conjunction with asio to facilitate client/server data communications: http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/examples... The core of the code using serialization is here: http://asio.sourceforge.net/boost-asio-proposal-0.3.6/libs/asio/doc/examples... Jeff

Hi Reece, --- Reece Dunn <msclrhd@hotmail.com> wrote:>
I have only taken a cursory glance at the docs at the moment. From what I have read, there is built in support for TCP and UDP I/O, but you could extend this to support other transports such as asynchronous files or named pipes. Is the interface generic enough so I can do this easily? How would I provide this functionality? (If this is in the docs, please tell me to RTFM ;) hopefully with a link to where in the docs it is).
Have a look at my reply in the "[asio] Brief Review" thread. Also, Felipe Magno de Almeida has written a prototype async file I/O implementation for linux, which I'd like to look at integrating in the future.
Another question: is there a way I can bind a socket to an I/O stream so I can easily send data across a network.
There is an example in lib/asio/example/iostreams that uses the Boost.Iostreams library to create a socket stream.
Also, what would be interesting is an asynchronous serialisation archive so you can serialise/deserialise classes across a network like you can in Java.
Check out the example in lib/asio/example/serialization, which is also covered in the HTML docs under the "Examples" link.
The documentation does not make it clear exactly what functionality is available, so the docs could be clearer. For example, I didn't know the library supported IPv4 addresses before looking at the reference.
Hmm, yeah, it would be good to have a feature list on the main page, wouldn't it :)
It may be useful at some point to have support for IPv6 addresses.
Agreed.
The main question is will these be compatible with the library as it interacts with the IPv4 addresses.
It should simply be a matter of defining asio::ipv6 classes equivalent to the ones in asio::ipv4. Access to an IPv6 network is an issue when it comes to testing it, although it does seem that my Mac has an IPv6 loopback address. I can confirm that the socket classes already work with other addressing schemes. In the past I have defined the necessary Protocol and Endpoint classes so that I could use a stream_socket with Bluetooth/RFCOMM on Windows XP SP2. Cheers, Chris

On 12/10/05, Reece Dunn <msclrhd@hotmail.com> wrote:
Jeff Garland wrote:
All -
Today (Dec 10th) is the start of the format review of the Asynchronous I/O library (asio) library by Christopher Kohlhoff. The review will run until Friday December 23rd. I will be serving as review manager.
I am not that well versed in the target domain, but I do have a basic understanding of how everything works.
As has been mentioned, having a (standard) socket API will greatly help C++ compete with other languages that have extensive libraries such as Java.
I have only taken a cursory glance at the docs at the moment. From what I have read, there is built in support for TCP and UDP I/O, but you could extend this to support other transports such as asynchronous files or named pipes. Is the interface generic enough so I can do this easily? How would I provide this functionality? (If this is in the docs, please tell me to RTFM ;) hopefully with a link to where in the docs it is).
For what I worked with the library (which is very little), it is very straightforward to add support for other transports. I have implemented part of a asynchronous file IO with the AIO. Unfortunately I havent found time to continue working on this.
Another question: is there a way I can bind a socket to an I/O stream so I can easily send data across a network.
IMO, Asynchronous IO doesnt couple very well with the concept of streams. What you're probably looking for is the network library of Pedro Lamarão, which is basically an network stream library (had even suggestions to change the name of library to reflect this).
Also, what would be interesting is an asynchronous serialisation archive so you can serialise/deserialise classes across a network like you can in Java.
See the library I told above. It's probably straightforward to achieve that. [comments about the documentation]
- Reece
best regards, -- Felipe Magno de Almeida

Hi, I'm currently trying to port an app from libevent to asio to test out the library. I have a quick question. Why doesn't the basic_stream_socket::async_connect handler pass a pointer to the socket? Is it assumed that the handler is a stateful functor and keeps a pointer to the socket? I wanted to do the following void connect_handler(const boost::asio::error& error) { // // if (!error) { // Connect succeeded. // How do I get the connected socket here? // I want to call async_read_some like: // s->async_read_some(...); } } int main() { boost::asio::demuxer demuxer; boost::asio::stream_socket* socket = new boost::asio::stream_socket(demuxer); boost::asio::ipv4::address addr("127.0.0.1"); socket->async_connect(boost::asio::ipv4::tcp::endpoint(80, addr), connect_handler); demuxer.run(); }

Hi, --- christopher baus <christopher@baus.net> wrote:
I'm currently trying to port an app from libevent to asio to test out the library. I have a quick question. Why doesn't the basic_stream_socket::async_connect handler pass a pointer to the socket? Is it assumed that the handler is a stateful functor and keeps a pointer to the socket?
The intention is that you use boost::bind to create the appropriate function object: void connect_handler(const boost::asio::error& error, boost::asio::stream_socket* socket) { if (!error) { s->async_read_some(...); } } int main() { boost::asio::stream_socket* socket = ...; ... socket->async_connect(endpoint, boost::bind(connect_handler, _1, socket) ... // Or if you don't want to remember the index of the // arguments: // socket->async_connect(endpoint, // boost::bind(connect_handler, // boost::asio::placeholders::error, socket)); } Cheers, Chris

"Reece Dunn" <msclrhd@hotmail.com> wrote in message news:dnfm9c$1g4$1@sea.gmane.org... [snip]
I have only taken a cursory glance at the docs at the moment. From what I have read, there is built in support for TCP and UDP I/O, but you could extend this to support other transports such as asynchronous files or named pipes. Is the interface generic enough so I can do this easily? How would I provide this functionality? (If this is in the docs, please tell me to RTFM ;) hopefully with a link to where in the docs it is).
Or may be in this case WTFM? ;-))) I think a good overview of the library design would nicely complement tutorials.

Not a full review, though i will submit one. Just wanting to reiterate my view that the asio::demuxer could use a modification to support a customisation for Handler execution on the win32 message pump. I'm not sure what's the best way to do this in the context of the existing asio code - but some way to hook into the work queueing mechanism would be required. Alternatively, all the api calls that currently take a boost::asio::demuxer& could instead take a more abstract base class so entirely custom thread mechansims could be used, derived from that base class. Cheers Simon

Hi Simon, --- simon meiklejohn <simon@simonmeiklejohn.com> wrote:
Not a full review, though i will submit one. Just wanting to reiterate my view that the asio::demuxer could use a modification to support a customisation for Handler execution on the win32 message pump.
I'm not sure what's the best way to do this in the context of the existing asio code - but some way to hook into the work queueing mechanism would be required.
I'm of the opinion that asio::demuxer isn't the right place for this. Instead it should be an implementation of the Dispatcher concept.
Alternatively, all the api calls that currently take a boost::asio::demuxer& could instead take a more abstract base class so entirely custom thread mechansims could be used, derived from that base class.
But the primary purpose of the demuxer is as an I/O demultiplexer, so the way it uses threads is aimed specifically at that use case, and it's not really suitable for customisation in the way you describe. I think the appropriate place to choose how the handler should be delivered is the point where an asynchronous operation is started. For example: async_read(socket, buffers, locking_dispatcher.wrap(handler)); says that I want the handler to be invoked via the specified dispatcher object. In a multithreaded application you are likely to want different locking_dispatchers for different application-level objects, even though all share the same demuxer. Cheers, Chris

Simon said
Just wanting to reiterate my view that the asio::demuxer could use a modification to support a customisation for Handler execution on the win32 message pump. [SNIP]
Chris said
I'm of the opinion that asio::demuxer isn't the right place for this. Instead it should be an implementation of the Dispatcher concept. [SNIP] I think the appropriate place to choose how the handler should be delivered is the point where an asynchronous operation is started. For example:
async_read(socket, buffers, locking_dispatcher.wrap(handler));
And following that model, async_read(socket, buffers, win32_pump_dispatcher.wrap(handler)); I agree that would work, so maybe thats good enough. (By which i mean, very good, like the rest of the lib :) However, locking_dispatcher has fairly simple innards, and a win32_pump_dispatcher would require an implementation thats a lot like asio::demuxer -i.e. with an internal queue of Handlers. Opportunity for re-use?
But the primary purpose of the demuxer is as an I/O demultiplexer, so the way it uses threads is aimed specifically at that use case, and it's not really suitable for customisation in the way you describe.
I can't see anything in the demuxer interface which limits it purely this role. Its applicability seems entirely general. (abstracted deferral of Handlers, which in reality can be 'handling' anything). In a program that mixes asio with other callback-originating libraries its attractive to extend the asio thread model to those other libraries as well. Thanks again Simon

Hi Simon, --- simon meiklejohn <simon@simonmeiklejohn.com> wrote:
However, locking_dispatcher has fairly simple innards, and a win32_pump_dispatcher would require an implementation thats a lot like asio::demuxer -i.e. with an internal queue of Handlers. Opportunity for re-use?
Absolutely. If asio does get accepted into boost, it would be good to factor out reusable bits of the implementation and/or replace them with some other boost library.
I can't see anything in the demuxer interface which limits it purely this role. Its applicability seems entirely general. (abstracted deferral of Handlers, which in reality can be 'handling' anything).
It's currently a limitation imposed by the services it provides. E.g. the default stream_socket implementation on Win32, which uses overlapped I/O and I/O completion ports, assumes that the demuxer is also implemented using I/O completion ports. I think it would be possible to break this coupling, but it is likely to be at a performance cost. In my thinking to date, socket I/O is just a facet of the demuxer, hidden behind a friendlier interface. Cheers, Chris

"Jeff Garland" <jeff@crystalclearsoftware.com> wrote
Today (Dec 10th) is the start of the format review of the Asynchronous I/O library (asio) library by Christopher Kohlhoff.
Is it possible to obtain a printable (single document) version of the library documentation? Regards, Arkadiy

Hi Arkadiy, --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Is it possible to obtain a printable (single document) version of the library documentation?
I'll see if I can get doxygen to generate something reasonable via latex. However it will probably be four separate documents (reference, tutorial, examples, design) rather than one for the time being. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
--- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Is it possible to obtain a printable (single document) version of the library documentation?
I'll see if I can get doxygen to generate something reasonable via latex. However it will probably be four separate documents (reference, tutorial, examples, design) rather than one for the time being.
This would be fine, thanks. I just want to use my commute time to look through your docs before attempting to play with the library. Regards, Arkadiy

Hi Arkadiy, --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Is it possible to obtain a printable (single document) version of the library documentation?
I've just put up a zip file containing 4 pdf files: http://prdownloads.sourceforge.net/asio/asio_printable_docs.zip?download You may want to think twice about printing the reference one though, since at nearly 400 pages that's a lot of dead tree ;) Cheers, Chris

Hi Christopher, "Christopher Kohlhoff" <chris@kohlhoff.com> wrote
I've just put up a zip file containing 4 pdf files:
http://prdownloads.sourceforge.net/asio/asio_printable_docs.zip?download
Thanks. Now that I spent some time reading through you tutorial I have a question -- what exactly is the demuxer object? At first I thought that it somehow relates to the "demultiplexer" pattern. However, considering a simple TCP client, which just connects to the server, sends a request, and then reads the response, I don't see how demultiplexer is related here. If I write a similar program using winsock, I can't see anything analogous... Can you ellaborate on this? Thanks, Arkadiy

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnmui9$5jr$1@sea.gmane.org...
Hi Christopher,
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
I've just put up a zip file containing 4 pdf files:
http://prdownloads.sourceforge.net/asio/asio_printable_docs.zip?download
Thanks.
Now that I spent some time reading through you tutorial I have a question -- what exactly is the demuxer object? At first I thought that it somehow relates to the "demultiplexer" pattern. However, considering a simple TCP client, which just connects to the server, sends a request, and then reads the response, I don't see how demultiplexer is related here. If I write a similar program using winsock, I can't see anything analogous...
Can you ellaborate on this?
A hint: What do you think 'asio' stands for?

"Eugene Alterman" <eugalt@verizon.net> wrote
"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnmui9$5jr$1@sea.gmane.org...
Hi Christopher,
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
I've just put up a zip file containing 4 pdf files:
http://prdownloads.sourceforge.net/asio/asio_printable_docs.zip?download
Thanks.
Now that I spent some time reading through you tutorial I have a question -- what exactly is the demuxer object? At first I thought that it somehow relates to the "demultiplexer" pattern. However, considering a simple TCP client, which just connects to the server, sends a request, and then reads the response, I don't see how demultiplexer is related here. If I write a similar program using winsock, I can't see anything analogous...
Can you ellaborate on this?
A hint: What do you think 'asio' stands for?
A question: then why does the tutorial have such examples as "A _synchronous_ TCP daytime client", etc., that also use the demuxer? Does it mean the library emulates synchronous operations using asynchronous facilities? Regards, Arkadiy

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnn61t$3g3$1@sea.gmane.org...
"Eugene Alterman" <eugalt@verizon.net> wrote
A hint: What do you think 'asio' stands for?
A question: then why does the tutorial have such examples as "A _synchronous_ TCP daytime client", etc., that also use the demuxer? Does it mean the library emulates synchronous operations using asynchronous facilities?
Ok, may be a wrong hint then. :-) See "Services and the Demuxer" in asio_design doc. It turns out demuxer also serves as a service repository. I personally don't like that very much and wonder why those 2 different functionalities are lumped together.

"Eugene Alterman" <eugalt@verizon.net> wrote
See "Services and the Demuxer" in asio_design doc. It turns out demuxer also serves as a service repository. I personally don't like that very much and wonder why those 2 different functionalities are lumped together.
So the demuxer is a service repository + demultiplexer(?)... If this is the case, I agree that they don't seem to belong together. Since, according to the design doc, a service is a "wrapper around a platform's implementation of the resource", the "service repository" part of the demuxer seems to be an abstraction layer over the platform. In this case, is the runtime object really neaded for this? Can't this functionality be provided at compile time, through a template parameter to the socket and other classes? (This, of course, would not work if this service repository can somehow change at runtime. Is this the case?) Regards, Arkadiy

Hi Arkadiy, --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Now that I spent some time reading through you tutorial I have a question -- what exactly is the demuxer object? At first I thought that it somehow relates to the "demultiplexer" pattern. However, considering a simple TCP client, which just connects to the server, sends a request, and then reads the response, I don't see how demultiplexer is related here. If I write a similar program using winsock, I can't see anything analogous...
As Eugene said it does act as a sort of service repository. In the past I have considered splitting them into two separate objects, however opted not to because I felt it impacted negatively on usability. Your typical user doesn't want to know about the services (see the many requests for me to split the reference documentation into two parts so that the services and basic_* templates are not intrusive). Therefore I prefer to talk about the demuxer as a multi-faceted object, not completely unlike std::locale. Its primary facet (i.e. the one accessible via asio::demuxer) is the one that everybody must use if they are going to do any asynchronous operations at all, regardless of whether they use sockets, timers or something else. As for why the sockets must take the demuxer object even if you are just doing synchronous operations... I mean you could have a socket implementation that excluded the asynchronous operations, and a demuxer wouldn't be required in that case. But ultimately this library is about asynchronous I/O. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051213205829.28944.qmail@web32611.mail.mud.yahoo.com...
In the past I have considered splitting them into two separate objects, however opted not to because I felt it impacted negatively on usability. Your typical user doesn't want to know about the services (see the many requests for me to split the reference documentation into two parts so that the services and basic_* templates are not intrusive).
Have you considered making default service repository a singleton? That would make it transparent to a user.

Hi Eugene, --- Eugene Alterman <eugalt@verizon.net> wrote:
Have you considered making default service repository a singleton? That would make it transparent to a user.
Leaving aside my distaste for singletons generally, and particularly in a library :) this would have to be a demuxer singleton, since the services operate and are implemented as though they are parts of a demuxer -- the service repository is just an implementation detail. I am now of the opinion that an iostreams interface is the best place to hide this away. All socket iostreams instances could share a demuxer instance (a static class member perhaps?). What's neat about this is that, with your core_socket suggestion, the iostream object can expose the core_socket via a lowest_layer() function. This lets it participate in other asio operations like being used with a socket_acceptor, while still hiding the underlying read_some/write_some operations away. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
--- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Now that I spent some time reading through you tutorial I have a question -- what exactly is the demuxer object? At first I thought that it somehow relates to the "demultiplexer" pattern. However, considering a simple TCP client, which just connects to the server, sends a request, and then reads the response, I don't see how demultiplexer is related here. If I write a similar program using winsock, I can't see anything analogous...
As Eugene said it does act as a sort of service repository. In the past I have considered splitting them into two separate objects, however opted not to because I felt it impacted negatively on usability. Your typical user doesn't want to know about the services (see the many requests for me to split the reference documentation into two parts so that the services and basic_* templates are not intrusive).
Therefore I prefer to talk about the demuxer as a multi-faceted object, not completely unlike std::locale. Its primary facet (i.e. the one accessible via asio::demuxer) is the one that everybody must use if they are going to do any asynchronous operations at all, regardless of whether they use sockets, timers or something else.
Can your faucets change at runtime?
As for why the sockets must take the demuxer object even if you are just doing synchronous operations... I mean you could have a socket implementation that excluded the asynchronous operations,
Why is the ability to perform asynchronous operations a property of socket?
and a demuxer wouldn't be required in that case. But ultimately this library is about asynchronous I/O.
I can see some contradiction here, since you tutorial provides a number of syncronous examples. Using the demuxer object in such contexts seems to be a conceptual overhead, and somewhat misleading. Also, your library provides some socket API, and therefore, if it is accepted, I don't expect any other socket class in Boost in the nearest future, which means I would expect your classes to nicely wrap all of sockets, not only acynchronos ones. Regards, Arkadiy

Hi Arkadiy, --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Can your faucets change at runtime?
No.
Why is the ability to perform asynchronous operations a property of socket?
I chose to make asynchronous operations part of asio's socket interfaces because, in my opinion, having them there offers the best ease of use and gives maximum flexibility to provide efficient implementations portably.
I can see some contradiction here, since you tutorial provides a number of syncronous examples. Using the demuxer object in such contexts seems to be a conceptual overhead, and somewhat misleading.
With this tutorial, I was trying to demonstrate the library's concepts in stages, where each subsequent tutorial introduces something new. I think it's important that, as you move through the tutorial, you can see how the synchronous is mapped to the asynchronous. So the early examples simply don't use all of the functionality that's on offer. They still have to initialise the objects correctly according to the library's interface though.
Also, your library provides some socket API, and therefore, if it is accepted, I don't expect any other socket class in Boost in the nearest future, which means I would expect your classes to nicely wrap all of sockets, not only acynchronos ones.
I've been thinking about these issues since your email. My feeling is that a use case doesn't have to become very complicated for the asynchronous operations to be beneficial. But where a developer's needs are simple enough, I suspect that's also when they are going to want to use an iostreams-based interface. Since iostreams basically enforce synchronous operation, this seems like a sensible place to hide the demuxer and asynchronicity away. Cheers, Chris

Also, your library provides some socket API, and therefore, if it is accepted, I don't expect any other socket class in Boost in the nearest future, which means I would expect your classes to nicely wrap all of sockets, not only acynchronos ones.
I've been thinking about these issues since your email. My feeling is that a use case doesn't have to become very complicated for the asynchronous operations to be beneficial. But where a developer's needs are simple enough, I suspect that's also when they are going to want to use an iostreams-based interface. Since iostreams basically enforce synchronous operation, this seems like a sensible place to hide the demuxer and asynchronicity away.
I strongly, strongly agree. You can build a robust synchronous implementation, iostream based or not, on top of an asynchronous foundantion. The reverse is difficult and tends to be non-scalable. Dave Moore

On 12/14/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
But where a developer's needs are simple enough, I suspect that's also when they are going to want to use an iostreams-based interface. Since iostreams basically enforce synchronous operation, this seems like a sensible place to hide the demuxer and asynchronicity away.
As I mentioned in my review, I think casual client-side development will benefit greatly from a simple Iostreams-based interface. The stream_socket implementation in the examples/iostreams directory (or something like it) might end up being the most used interfaces in asio if you provide it. -- Caleb Epstein caleb dot epstein at gmail dot com

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
--- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Can your faucets change at runtime?
No.
Than have you considered putting them in a type rather than in an object? I mean defining them as static functions inside a class, and then providing the typedef to this class as a part of your configuration, depending on the platform used? This is better than singleton, since your library could remain header-only. I realise that would not work for demuxer (demultiplexer?), but I am still thinking this is not the best idea to mix it with the platform traits.
Why is the ability to perform asynchronous operations a property of socket?
I chose to make asynchronous operations part of asio's socket interfaces because, in my opinion, having them there offers the best ease of use and gives maximum flexibility to provide efficient implementations portably.
See, this doesn't look intuitive to me. I am by no means a networking expert, but I have done some work with sockets. And, IMO, a socket doesn't have to do anything with being asynchronous-enabled or not. Asynchronity is about how the socket is used, not about the socket itself. If you split demuxer from platform traits, and free the socket implementation from the asynchronisity, moving it outside, than all the socket needs to know about is platform traits. This could be used as I suggested above (through the typedef), and your socket constructor would not have to accept any parameters at all.
I can see some contradiction here, since you tutorial provides a number of syncronous examples. Using the demuxer object in such contexts seems to be a conceptual overhead, and somewhat misleading.
With this tutorial, I was trying to demonstrate the library's concepts in stages, where each subsequent tutorial introduces something new. I think it's important that, as you move through the tutorial, you can see how the synchronous is mapped to the asynchronous. So the early examples simply don't use all of the functionality that's on offer. They still have to initialise the objects correctly according to the library's interface though.
Also, your library provides some socket API, and therefore, if it is accepted, I don't expect any other socket class in Boost in the nearest future, which means I would expect your classes to nicely wrap all of sockets, not only acynchronos ones.
I've been thinking about these issues since your email. My feeling is that a use case doesn't have to become very complicated for the asynchronous operations to be beneficial. But where a developer's needs are simple enough, I suspect that's also when they are going to want to use an iostreams-based interface. Since iostreams basically enforce synchronous operation, this seems like a sensible place to hide the demuxer and asynchronicity away.
Well, personally, I would still like to see a nice socket API, as a part of any networking library accepted into Boost. And I think it should be closely mapped (conceptually) to existing socket APIs, such as Berkeley, winsock, etc., maybe Java sockets. The rest of the library, which implements higher-level abstractions (such as demuxer), could depend on this layer, but not visa-versa. Regards, Arkadiy

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnpcuo$qjn$1@sea.gmane.org...
Than have you considered putting them in a type rather than in an object? I mean defining them as static functions inside a class, and then providing the typedef to this class as a part of your configuration, depending on the platform used? This is better than singleton, since your library could remain header-only.
Are you implying that a singleton cannot have a header-only implementation?
Why is the ability to perform asynchronous operations a property of socket?
I chose to make asynchronous operations part of asio's socket interfaces because, in my opinion, having them there offers the best ease of use and gives maximum flexibility to provide efficient implementations portably.
See, this doesn't look intuitive to me. I am by no means a networking expert, but I have done some work with sockets. And, IMO, a socket doesn't have to do anything with being asynchronous-enabled or not. Asynchronity is about how the socket is used, not about the socket itself.
But asynchonous i/o implementation does depend on the underlying socket API (platform traits, as you call it). Some implementations prvovide asynchronous operations while on others they need to be emulated by non-blocking socket calls and a reactor framework.
If you split demuxer from platform traits, and free the socket implementation from the asynchronisity, moving it outside, than all the socket needs to know about is platform traits. This could be used as I suggested above (through the typedef), and your socket constructor would not have to accept any parameters at all.

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnpcuo$qjn$1@sea.gmane.org...
Than have you considered putting them in a type rather than in an object? I mean defining them as static functions inside a class, and then
"Eugene Alterman" <eugalt@verizon.net> wrote in message providing
the typedef to this class as a part of your configuration, depending on the platform used? This is better than singleton, since your library could remain header-only.
Are you implying that a singleton cannot have a header-only implementation?
I was under such impression... But now when I tried this, looks like Meyers singleton works OK, at least in VC71.
Why is the ability to perform asynchronous operations a property of socket?
I chose to make asynchronous operations part of asio's socket interfaces because, in my opinion, having them there offers the best ease of use and gives maximum flexibility to provide efficient implementations portably.
See, this doesn't look intuitive to me. I am by no means a networking expert, but I have done some work with sockets. And, IMO, a socket doesn't have to do anything with being asynchronous-enabled or not.
Asynchronity
is about how the socket is used, not about the socket itself.
But asynchonous i/o implementation does depend on the underlying socket API (platform traits, as you call it). Some implementations prvovide asynchronous operations while on others they need to be emulated by non-blocking socket calls and a reactor framework.
Right. Still doesn't explain while socket (a low-level abstraction) should depend on demultiplexer (a high-level abstraction), does it? Regards, Arkadiy

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
I've been thinking about these issues since your email. My feeling is that a use case doesn't have to become very complicated for the asynchronous operations to be beneficial. But where a developer's needs are simple enough, I suspect that's also when they are going to want to use an iostreams-based interface.
Your assumption seems to be that only simple problems have to be solved synchronously, but once the problem is complicated enough, asynchronous approach is definitely superior. Let me disagree with this. There are cases when having multiple threads, each working in a synchronous way, is a better approach. First, it's much more intuitive, and easier to debug. Second, sometimes it's the only way to achieve you goal, such as whan you can't (or don't want to) rewrite your processing algorithm in asynchronous way. And when such a case arrives, it would be nice to have a clean socket class, without build-in asynchronisity.
Since iostreams basically enforce synchronous operation, this seems like a sensible place to hide the demuxer and asynchronicity away.
Personally, I wouldn't like it to be "hidden away". I would like it to be "removed" for such cases. Regards, Arkadiy

Hi, Arkadiy Vertleyb wrote...
There are cases when having multiple threads, each working in a synchronous way, is a better approach. [...]
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
Since iostreams basically enforce synchronous operation, this seems like a sensible place to hide the demuxer and asynchronicity away.
Just one question: Would it be possible to "hide away" the demuxer and asynchronicity in a thread-safe way? I would like to use synchronous socket streams in a multi-threaded application. Best regards, Tilman

Arkadiy Vertleyb wrote:
Let me disagree with this. There are cases when having multiple threads, each working in a synchronous way, is a better approach. First, it's much more intuitive, and easier to debug. Second, sometimes it's the only way to achieve you goal, such as whan you can't (or don't want to) rewrite your processing algorithm in asynchronous way.
And when such a case arrives, it would be nice to have a clean socket class, without build-in asynchronisity.
Even if this is the case, is it the responsibility of a library that offers Asynchronous I/O as a primary focus to supply a clean socket class that has nothing to do with async I/O?

"Peter Dimov" <pdimov@mmltd.net> wrote
Arkadiy Vertleyb wrote:
Let me disagree with this. There are cases when having multiple threads, each working in a synchronous way, is a better approach. First, it's much more intuitive, and easier to debug. Second, sometimes it's the only way to achieve you goal, such as whan you can't (or don't want to) rewrite your processing algorithm in asynchronous way.
And when such a case arrives, it would be nice to have a clean socket class, without build-in asynchronisity.
Even if this is the case, is it the responsibility of a library that offers Asynchronous I/O as a primary focus to supply a clean socket class that has nothing to do with async I/O?
In general, no. But when the library also offers syncronous operations, then yes, I would like this to be implemented cleanly, rather than having asyncronous stuff, even if hidden underneath. Also, let's consider a broader picture. Network programming consists of both synchronous and asynchronous approaches, sometimes mixed together. One can start with one approach, and then switch to another. Maybe I got a wrong impression, but to me it looks like asio is offered (or perceived) as something like Boost Networking Library. And as such, I would expect clean sockets from it. Otherwise, who will provide them? Do we expect another Socket class to be reviewed any time soon? Regards, Arkadiy

Arkadiy Vertleyb wrote:
Maybe I got a wrong impression, but to me it looks like asio is offered (or perceived) as something like Boost Networking Library. And as such, I would expect clean sockets from it. Otherwise, who will provide them? Do we expect another Socket class to be reviewed any time soon?
There was a very good discussion about this some months ago, when someone posted a layered design. I think ideally a (network) stream API would thus be layered on top of asio. In fact, if the low level 'transport protocol' is being made a policy, the stream API could be parametrized to only depend on asio if async operations are requested. Regards, Stefan

On Wed, 14 Dec 2005 12:05:10 -0500 Stefan Seefeld <seefeld@sympatico.ca> wrote:
I think ideally a (network) stream API would thus be layered on top of asio. In fact, if the low level 'transport protocol' is being made a policy, the stream API could be parametrized to only depend on asio if async operations are requested.
However, many of us desire synchronous handling, but want nothing to do with sockets encapsulated as IOStreams.

"Stefan Seefeld" <seefeld@sympatico.ca> wrote
Arkadiy Vertleyb wrote:
Maybe I got a wrong impression, but to me it looks like asio is offered (or perceived) as something like Boost Networking Library. And as such, I would expect clean sockets from it. Otherwise, who will provide them? Do we expect another Socket class to be reviewed any time soon?
There was a very good discussion about this some months ago, when someone posted a layered design.
I think ideally a (network) stream API would thus be layered on top of asio. In fact, if the low level 'transport protocol' is being made a policy, the stream API could be parametrized to only depend on asio if async operations are requested.
I would expect both asio and a stream API to be layered on top of low-level socket API. This socket API should wrap the platform dependency, but otherwise provide pretty much the same capabilities as current C socket APIs provide, with minimum overhead. Regards, Arkadiy

Arkadiy Vertleyb wrote:
I would expect both asio and a stream API to be layered on top of low-level socket API. This socket API should wrap the platform dependency, but otherwise provide pretty much the same capabilities as current C socket APIs provide, with minimum overhead.
Yes. Though I'm not sure a thin wrapper around the platform API adds much value at all. Too big are the differences. And besides, why should people really care whether the thing is a socket, as long as the semantics are correct ? Regards, Stefan

"Stefan Seefeld" <seefeld@sympatico.ca> wrote
Arkadiy Vertleyb wrote:
I would expect both asio and a stream API to be layered on top of low-level socket API. This socket API should wrap the platform dependency, but otherwise provide pretty much the same capabilities as current C socket APIs provide, with minimum overhead.
Yes. Though I'm not sure a thin wrapper around the platform API adds much value at all. Too big are the differences. And besides, why should people really care whether the thing is a socket, as long as the semantics are correct ?
From the usage point of view there might be little difference, as long as it works correctly, and doesn't add a lot of performance overhead.
But from the design point of view, I think it's important. And if the socket class is used to perform synchronous IO, I don't think the user should have to create the demuxer object and carry it around. Also, IMO, something that is presented as a "Socket" should not hide things that are conceptually related to the socket usage, rather than the socket itself. These things should be located elsewhere, so that they become relevant only if used. Regards, Arkadiy

"Arkadiy Vertleyb" <vertleyb@hotmail.com> writes:
[snip]
I would expect both asio and a stream API to be layered on top of low-level socket API. This socket API should wrap the platform dependency, but otherwise provide pretty much the same capabilities as current C socket APIs provide, with minimum overhead.
The key problem with that approach is that a `thin' wrapper over the platform APIs is basically useless for implementing asynchronous operations, because asynchronous operations must be implemented in widely different ways on the different platforms. For synchronous operations, it happens to be the case that every platform provides basically the same interface, so at least a portable (but not necessarily very good) interface can be created through only a thin wrapper. -- Jeremy Maitin-Shepard

"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote
"Arkadiy Vertleyb" <vertleyb@hotmail.com> writes:
[snip]
I would expect both asio and a stream API to be layered on top of low-level socket API. This socket API should wrap the platform dependency, but otherwise provide pretty much the same capabilities as current C socket APIs provide, with minimum overhead.
The key problem with that approach is that a `thin' wrapper over the platform APIs is basically useless for implementing asynchronous operations, because asynchronous operations must be implemented in widely different ways on the different platforms.
For synchronous operations, it happens to be the case that every platform provides basically the same interface, so at least a portable (but not necessarily very good) interface can be created through only a thin wrapper.
Then maybe it would make sence to define this kind of API for synchronous operations, while asio encapsulates asynchronous IO using a higher-level abstraction. The socket class would be cleanned from the asynchronous stuff, and used by both asio and synchronous API. My problem with the current approach is that synchronous operations seems to be viewed as second-class citizens. I understand that this is caused by the fact that asio is about the asynchronous IO. But asio is presented as the "best C++ networking library around", and in networking both synchronous and asynchronous approaches are used, and neither is superior for all possible cases. Consider "Patterns for concurrent and networked objecs" by D. Schmidt, at al. Quite a few patterns described there are related to the synchronous processing. I think a little refactoring might lead to a better solution than trying to add synchronous operations on top of existing asio. Regards, Arkadiy

Arkadiy Vertleyb wrote:
My problem with the current approach is that synchronous operations seems to be viewed as second-class citizens. I understand that this is caused by the fact that asio is about the asynchronous IO. But asio is presented as the "best C++ networking library around", and in networking both synchronous and asynchronous approaches are used, and neither is superior for all possible cases.
Consider "Patterns for concurrent and networked objecs" by D. Schmidt, at al. Quite a few patterns described there are related to the synchronous processing.
I think a little refactoring might lead to a better solution than trying to add synchronous operations on top of existing asio.
I think that we should be careful not to break something that has been proven to work well because a little refactoring "might" lead to a better solution (unless the author agrees, of course.) The argument that asio is presented as "the best networking C++ library around" is not enough of an excuse.

"Peter Dimov" <pdimov@mmltd.net> wrote
Arkadiy Vertleyb wrote:
My problem with the current approach is that synchronous operations seems to be viewed as second-class citizens. I understand that this is caused by the fact that asio is about the asynchronous IO. But asio is presented as the "best C++ networking library around", and in networking both synchronous and asynchronous approaches are used, and neither is superior for all possible cases.
Consider "Patterns for concurrent and networked objecs" by D. Schmidt, at al. Quite a few patterns described there are related to the synchronous processing.
I think a little refactoring might lead to a better solution than trying to add synchronous operations on top of existing asio.
I think that we should be careful not to break something that has been proven to work well because a little refactoring "might" lead to a better solution (unless the author agrees, of course.) The argument that asio is presented as "the best networking C++ library around" is not enough of an excuse.
Anything should be done only if the author agrees. All I was trying to say is that, as a potential client of the library (everybody does some networking now and then), I tried to map it to one of the tasks I had in the past. I happen to have solved that task by using synchronous operations + multiple threads (maybe because it is the most intuitive way for a not very experienced network programmer, which I am, but I still doubt very much that asynchronous approach would have been better). So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost. Besides nobody yet convinced me that socket should depend on demultiplexer. The fact that it "has been proven to work well" is "not enough of an excuse", IMO. Otherwise this review would not make any sense. I totally appreciate the fact that the library has a lot of positive reviews, and, as you say, proven to work well. All I want is that the synchronous approach to networking was given enough attention, and added cleanly rather than as a fast fix -- a wrapper around asynchronous stuff. Regards, Arkadiy

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnq894$11t$1@sea.gmane.org...
All I was trying to say is that, as a potential client of the library (everybody does some networking now and then), I tried to map it to one of the tasks I had in the past. I happen to have solved that task by using synchronous operations + multiple threads (maybe because it is the most intuitive way for a not very experienced network programmer, which I am, but I still doubt very much that asynchronous approach would have been better).
If you are using socket API or a C++ library that just provides simple wrapper classes for socket API a thread per conection approach is of course the most intuitive. The problem is that it does not scale well.
So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost.
One approach is inferior to the other and they are both important (but not equally) :-)
Besides nobody yet convinced me that socket should depend on demultiplexer.
It does not. The real reason it depends on demuxer is that it contains the socket service. Otherwise demultiplexer could have been passed as an argument only when performing asynchronous operations.
The fact that it "has been proven to work well" is "not enough of an excuse", IMO. Otherwise this review would not make any sense.
I totally appreciate the fact that the library has a lot of positive reviews, and, as you say, proven to work well. All I want is that the synchronous approach to networking was given enough attention, and added cleanly rather than as a fast fix -- a wrapper around asynchronous stuff.
It is not.

"Eugene Alterman" <eugalt@verizon.net> wrote
"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnq894$11t$1@sea.gmane.org...
All I was trying to say is that, as a potential client of the library (everybody does some networking now and then), I tried to map it to one of the tasks I had in the past. I happen to have solved that task by using synchronous operations + multiple threads (maybe because it is the most intuitive way for a not very experienced network programmer, which I am, but I still doubt very much that asynchronous approach would have been better).
If you are using socket API or a C++ library that just provides simple wrapper classes for socket API a thread per conection approach is of course the most intuitive. The problem is that it does not scale well.
So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost.
One approach is inferior to the other and they are both important (but not equally) :-)
Is it a proven fact, or just your opinion? :-) Regards, Arkadiy

Arkadiy Vertleyb wrote:
"Eugene Alterman" <eugalt@verizon.net> wrote
So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost.
One approach is inferior to the other and they are both important (but not equally) :-)
Is it a proven fact, or just your opinion? :-)
Proven fact. Thread per connection doesn't scale.

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnq894$11t$1@sea.gmane.org...
So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost.
"Inferiority" is not the point here. The reason asynchronous interface is given more attention is that it is the one that is non-trivial. The real value of this library is the asynchronous part. There is nothing special about synchronous operations, and they have been implemented in similar ways in dozens of networking libraries.

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnq894$11t$1@sea.gmane.org...
So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost.
"Inferiority" is not the point here. The reason asynchronous interface is given more attention is that it is
"Eugene Alterman" <eugalt@verizon.net> wrote the
one that is non-trivial. The real value of this library is the asynchronous part.
OK
There is nothing special about synchronous operations, and they have been implemented in similar ways in dozens of networking libraries.
See, I don't care much about other libraries -- I am not going to use them. But if this one becomes part of Boost, I may start using it, and so I would like it to have what I need, with a clean interface, and implemented in a clean way. Regards, Arkadiy

In article <dnqfmd$l6e$1@sea.gmane.org>, "Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote:
"Eugene Alterman" <eugalt@verizon.net> wrote
There is nothing special about synchronous operations, and they have been implemented in similar ways in dozens of networking libraries.
See, I don't care much about other libraries -- I am not going to use them. But if this one becomes part of Boost, I may start using it, and so I would like it to have what I need, with a clean interface, and implemented in a clean way.
IMO this is completely beside the point. This is not the boost networking library, it's the boost async IO library, and as such whether it supports sync IO or not is completely irrelevant. There are really two possibilities here: 1) You may some day use an async IO library, in which case this library will be useful to you and you should review it for its merit with regards to its stated design intent or 2) You will never use an async IO library, in which case this library will never be useful to you, and you should still review it for its merit with regards to its stated design intent, while acknowledging that it does not necessarily fit your problem domain. However, right now you seem to saying that you are not interested in async IO, and that this library, whose stated intent is to provide async IO, is therefore unacceptable to you. You have a nail. This library is not a hammer. IMO, if you want a clean synchronous IO API, you should not be looking for it in an async IO library. Ben -- I changed my name: <http://periodic-kingdom.org/People/NameChange.php>

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote:
"Eugene Alterman" <eugalt@verizon.net> wrote
There is nothing special about synchronous operations, and they have been implemented in similar ways in dozens of networking libraries.
See, I don't care much about other libraries -- I am not going to use
"Ben Artin" <macdev@artins.org> wrote them.
But if this one becomes part of Boost, I may start using it, and so I would like it to have what I need, with a clean interface, and implemented in a clean way.
IMO this is completely beside the point. This is not the boost networking library, it's the boost async IO library, and as such whether it supports sync IO or not is completely irrelevant.
When I was reading through the library tutorial half of examples there were related to the synchronous IO. And, in all these examples a demuxer object was used, that has nothing to do with synchronous IO. Do you think this is a clean interface? Do you think having to create and pass around an object, that is conceptially not required, is irrelevant? If the library doesn't want to support synchronous IO this is fine, but since it does support it, how it is supported becomes relevant.
There are really two possibilities here:
1) You may some day use an async IO library, in which case this library will be useful to you and you should review it for its merit with regards to its stated design intent or 2) You will never use an async IO library, in which case this library will never be useful to you, and you should still review it for its merit with regards to its stated design intent, while acknowledging that it does not necessarily fit your problem domain.
However, right now you seem to saying that you are not interested in async IO, and that this library, whose stated intent is to provide async IO, is
I was under impression that I am reviewing it right now :-) There is a third possibility, which IMO is more probable, that I will want to use some combination os sync and async features. That's why I would like to see them nicely fit together. therefore
unacceptable to you.
This is a total mis-interpretation of what I have been saying.
You have a nail. This library is not a hammer.
IMO, if you want a clean synchronous IO API, you should not be looking for it in an async IO library.
If the lirary has the support for synchronous IO API, it should be clean. Either have a clean support or no support at all. Regards, Arkadiy

Arkadiy Vertleyb wrote:
All I was trying to say is that, as a potential client of the library (everybody does some networking now and then), I tried to map it to one of the tasks I had in the past. I happen to have solved that task by using synchronous operations + multiple threads (maybe because it is the most intuitive way for a not very experienced network programmer, which I am, but I still doubt very much that asynchronous approach would have been better). So, when I am evaluating a "Networking Library", and see that it clearly consideres one approach to networking inferior to the other one, and I think they both are equaly important, that means that I am in fundamental disagreement with the library author on the subject, and makes me wonder whether this is the networking library I would like to see in Boost.
The "thread per connection" model _is_ inferior on all existing platforms. If asio doesn't encourage its use, I consider this a feature.

"Peter Dimov" <pdimov@mmltd.net> wrote
The "thread per connection" model _is_ inferior on all existing platforms. If asio doesn't encourage its use, I consider this a feature.
I am not sure what exactly the definition of "thread per connection" model is, but I assume this means a new thread is _created_ for every connection (?) What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model can be absolutely inferior in all possible contexts. Regards, Arkadiy

Arkadiy Vertleyb wrote:
"Peter Dimov" <pdimov@mmltd.net> wrote
The "thread per connection" model _is_ inferior on all existing platforms. If asio doesn't encourage its use, I consider this a feature.
I am not sure what exactly the definition of "thread per connection" model is, but I assume this means a new thread is _created_ for every connection (?)
Right.
What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model can be absolutely inferior in all possible contexts.
That is usually referred to as 'Thread Pool', and it is usually considered the 'best' strategy among the multi-threaded ones, in particular due to its scalability. Regards, Stefan

"Stefan Seefeld" <seefeld@sympatico.ca> wrote in message news:43A1AFF4.1070507@sympatico.ca...
Arkadiy Vertleyb wrote: [snip]
What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model can be absolutely inferior in all possible contexts.
That is usually referred to as 'Thread Pool', and it is usually considered the 'best' strategy among the multi-threaded ones, in particular due to its scalability.
Well, this particular concurrency model utilizing thread pool is the simplest but not the best. Ever heard about Leader/Followers pattern?

On Thu, 15 Dec 2005 12:43:17 -0500, "Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote:
The "thread per connection" model _is_ inferior on all existing platforms. If asio doesn't encourage its use, I consider this a feature.
I am not sure what exactly the definition of "thread per connection" model is, but I assume this means a new thread is _created_ for every connection (?)
What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model can be absolutely inferior in all possible contexts.
How are you reading and writing from multiple sockets simultaneously in your main thread? Don't tell me that if one client pauses after sending a partial request that all other clients are blocked? -- Be seeing you.

"Thore Karlsen" <sid@6581.com> wrote
What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model can be absolutely inferior in all possible contexts.
How are you reading and writing from multiple sockets simultaneously in your main thread?
I was not. I was reading/writig from the worker threads. The main thread was used only to accept connections. Regards, Arkadiy

On Thu, 15 Dec 2005 13:48:37 -0500, "Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote:
What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model can be absolutely inferior in all possible contexts.
How are you reading and writing from multiple sockets simultaneously in your main thread?
I was not. I was reading/writig from the worker threads. The main thread was used only to accept connections.
So in that case you are handling one connection per thread, which does not scale well. -- Be seeing you.

Thore Karlsen wrote:
How are you reading and writing from multiple sockets simultaneously in your main thread?
I was not. I was reading/writig from the worker threads. The main thread was used only to accept connections.
So in that case you are handling one connection per thread, which does not scale well.
I'm sorry, but your conclusion seems wrong. If a dispatcher thread puts requests into a queue for worker threads to consume, it's most definitely *not* a thread-per-connection design, but a thread pool. It would be a thread-per-connection if threads were created as a result of new connection requests being put into the queue. But that he (apparently) isn't doing. Regards, Stefan

On Thu, 15 Dec 2005 14:31:51 -0500, Stefan Seefeld <seefeld@sympatico.ca> wrote:
How are you reading and writing from multiple sockets simultaneously in your main thread?
I was not. I was reading/writig from the worker threads. The main thread was used only to accept connections.
So in that case you are handling one connection per thread, which does not scale well.
I'm sorry, but your conclusion seems wrong. If a dispatcher thread puts requests into a queue for worker threads to consume, it's most definitely *not* a thread-per-connection design, but a thread pool.
It would be a thread-per-connection if threads were created as a result of new connection requests being put into the queue. But that he (apparently) isn't doing.
You seem to be reading this differently than I am. To me it looks like he's saying that he has one thread that accepts connections, and then he passes the socket to a worker thread where he does all the synchronous reading and writing to service the client. He may have the threads already standing by when he accepts new connections, but since he's doing synchronous I/O he can't service more than one connection at a time from a thread. Thus the number of connections he can handle is limited by the number of threads he has, which is why there's a scalability problem. -- Be seeing you.

On Thu, 15 Dec 2005 13:41:32 -0600 Thore Karlsen <sid@6581.com> wrote:
You seem to be reading this differently than I am. To me it looks like he's saying that he has one thread that accepts connections, and then he passes the socket to a worker thread where he does all the synchronous reading and writing to service the client.
He may have the threads already standing by when he accepts new connections, but since he's doing synchronous I/O he can't service more than one connection at a time from a thread. Thus the number of connections he can handle is limited by the number of threads he has, which is why there's a scalability problem.
You do not understand. This is a typical technique for thread pools. Each thread only performs the task requested, and the next task may be on a totally different connection. The number of connections is only limited by the operating system. The number of connections that can be serviced "simultaneously" is limited by the number of threads in the thread pool (please note the quotes around simultaneously -- I am fully aware of the actualities).

On Thu, 15 Dec 2005 15:03:48 -0500, Jody Hagins <jody-boost-011304@atdesk.com> wrote:
You seem to be reading this differently than I am. To me it looks like he's saying that he has one thread that accepts connections, and then he passes the socket to a worker thread where he does all the synchronous reading and writing to service the client.
He may have the threads already standing by when he accepts new connections, but since he's doing synchronous I/O he can't service more than one connection at a time from a thread. Thus the number of connections he can handle is limited by the number of threads he has, which is why there's a scalability problem.
You do not understand. This is a typical technique for thread pools. Each thread only performs the task requested, and the next task may be on a totally different connection.
And how do you presume that Arkadiy is doing that when he's doing all synchronous communication in the worker threads? How can one of his threads service another connection when it's blocked waiting for data from the client, or blocked sending data to a client? If he is truly servicing multiple connections from a single thread using blocking I/O I'd love to know how he's doing it. -- Be seeing you.

On Thu, 15 Dec 2005 14:23:34 -0600 Thore Karlsen <sid@6581.com> wrote:
And how do you presume that Arkadiy is doing that when he's doing all synchronous communication in the worker threads? How can one of his threads service another connection when it's blocked waiting for data from the client, or blocked sending data to a client?
If he is truly servicing multiple connections from a single thread using blocking I/O I'd love to know how he's doing it.
This thread of discussion has gone way past its intent, and I'm not sure this is the place for discussions on thread pool implementations. However, just to give you something to think about... Why does the main thread have to do any I/O at all? One could easily conceive a thread that looks for work to be done, then sends that work to an available thread for processing, without ever performing any I/O. Or, a thread pool that "passes a token" representing the responsibility of finding work. It finds some work to do, then passes the token to another thread before actually doing the work. There are many (some better than others) ways of accomplishing similar things without having one thread do all the input...

On Thu, 15 Dec 2005 16:29:51 -0500, Jody Hagins <jody-boost-011304@atdesk.com> wrote:
And how do you presume that Arkadiy is doing that when he's doing all synchronous communication in the worker threads? How can one of his threads service another connection when it's blocked waiting for data from the client, or blocked sending data to a client?
If he is truly servicing multiple connections from a single thread using blocking I/O I'd love to know how he's doing it.
This thread of discussion has gone way past its intent, and I'm not sure this is the place for discussions on thread pool implementations. However, just to give you something to think about... Why does the main thread have to do any I/O at all? One could easily conceive a thread that looks for work to be done, then sends that work to an available thread for processing, without ever performing any I/O.
That is basically what is being done in this specific case. There's one thread that just accepts connections, and then there's a set of worker threads that do all the communications and processing. My point is that once a thread is given a connection to work with, it can't do anything else while that connection is alive. Since all communication is synchronous, the thread will either be processing a request, or it will be blocked trying to read or write. It can only handle one connection at a time, and it has to handle that connection for the lifetime of the connection. This obviously doesn't scale. If you want to handle 1000 connections simultaneously, you need 1000 threads. Note that I'm not disagreeing with you in how thread pools should be used in general, I'm only addressing this specific scenario. But again, this is getting off topic. -- Be seeing you.

On Thu, 15 Dec 2005 16:01:20 -0600 Thore Karlsen <sid@6581.com> wrote:
My point is that once a thread is given a connection to work with, it can't do anything else while that connection is alive. Since all communication is synchronous, the thread will either be processing a request, or it will be blocked trying to read or write. It can only handle one connection at a time, and it has to handle that connection for the lifetime of the connection.
This obviously doesn't scale. If you want to handle 1000 connections simultaneously, you need 1000 threads.
Note that I'm not disagreeing with you in how thread pools should be used in general, I'm only addressing this specific scenario.
But... the thread only owns the connection for the life of one work unit (well, technically, if it were reading, it would read as much as possible, package the information, then process as much as possible). It would then release the "object" and become available for another piece of work. That piece of work may come from the same connection or another, it does not really matter. So, in this instance, I fail to see how it does not scale. It scales remarkably well, actually. I can handle 1000 connections with a thread pool of any size, even 1 (though that would defeat the purpose since all the work would pile up waiting for that one thread).

On Thu, 15 Dec 2005 21:20:23 -0500, Jody Hagins <jody-boost-011304@atdesk.com> wrote:
My point is that once a thread is given a connection to work with, it can't do anything else while that connection is alive. Since all communication is synchronous, the thread will either be processing a request, or it will be blocked trying to read or write. It can only handle one connection at a time, and it has to handle that connection for the lifetime of the connection.
This obviously doesn't scale. If you want to handle 1000 connections simultaneously, you need 1000 threads.
Note that I'm not disagreeing with you in how thread pools should be used in general, I'm only addressing this specific scenario.
But... the thread only owns the connection for the life of one work unit (well, technically, if it were reading, it would read as much as possible, package the information, then process as much as possible). It would then release the "object" and become available for another piece of work. That piece of work may come from the same connection or another, it does not really matter.
Of course it matters. The client could sit idle for an hour before it sent the next request, and the server thread would be stuck in a blocking read waiting for data from the client, unable to service other clients.
So, in this instance, I fail to see how it does not scale. It scales remarkably well, actually. I can handle 1000 connections with a thread pool of any size, even 1 (though that would defeat the purpose since all the work would pile up waiting for that one thread).
So if you can do this with blocking, synchronous I/O, why do you think asio exists? Why do you think people are arguing for asynchronous I/O in favor of synchronous I/O? -- Be seeing you.

On Thu, 15 Dec 2005 22:26:26 -0600 Thore Karlsen <sid@6581.com> wrote:
Of course it matters. The client could sit idle for an hour before it sent the next request, and the server thread would be stuck in a blocking read waiting for data from the client, unable to service other clients.
This isn't the place to continue this discussion. I'll give a brief reply, but after that, it should probably go to private email. The server thread is not stuck. I think you are completely missing the point (more likely, I am not clearly explaining the point). The thread is never invoked until some amount of data was available.
So if you can do this with blocking, synchronous I/O, why do you think asio exists? Why do you think people are arguing for asynchronous I/O in favor of synchronous I/O?
Hmmm. I think you are mixing too much here. First, you are assuming stream-based IO, and synchronous IO implies blocking. With "record" based IO (datagrams for IP based stacks), all the data is available, or none is available. Again, if you wish to continue, use private email.

"Thore Karlsen" <sid@6581.com> wrote
You seem to be reading this differently than I am. To me it looks like he's saying that he has one thread that accepts connections, and then he passes the socket to a worker thread where he does all the synchronous reading and writing to service the client.
He may have the threads already standing by when he accepts new connections, but since he's doing synchronous I/O he can't service more than one connection at a time from a thread. Thus the number of connections he can handle is limited by the number of threads he has, which is why there's a scalability problem.
"He" is not arguing with the fact that it seems possible to achieve somewhat better scalability using asynchronous calls. "He" just doesn't like the fact that this would mean writing the processing algorithm in a specific way to suite the needs of asynchronicity. And "he" generally believes in separation of concerns, so "he" doesn't want his processing algorithm to know that it is used from the networked environment. Regards, Arkadiy

Arkadiy Vertleyb wrote:
"Thore Karlsen" <sid@6581.com> wrote
What I did was to have the main thread accept incomming requests, and put them in the queue, whereas a number of worker threads was taking these requests from the queue, and execute them. I don't see how this model
can
be absolutely inferior in all possible contexts.
How are you reading and writing from multiple sockets simultaneously in your main thread?
I was not. I was reading/writig from the worker threads. The main thread was used only to accept connections.
Thus limiting your #simultaneous connections to the number of threads in your thread pool. You can do better by doing all the parsing, request splitting, etc. in a async-using main acceptor thread and handing off smaller bits of work to the threads in the pool. That way you can exploit the fact that some threads may finish some parts of a given request early, and may be available to serve other connections. -- Bardur Arantsson <bardurREMOVE@THISimada.sdu.dk> <bardurREMOVE@THISscientician.net> - Peg, we've been married for 17 years now, can't we just be friends? Al Bundy / Married With Children

"Bardur Arantsson" <spam@scientician.net> wrote
Thus limiting your #simultaneous connections to the number of threads in your thread pool. You can do better by doing all the parsing, request splitting, etc. in a async-using main acceptor thread and handing off smaller bits of work to the threads in the pool. That way you can exploit the fact that some threads may finish some parts of a given request early, and may be available to serve other connections.
Thus serving the same connection from different threads? I just don't happen to like how this would impact the processing algorithm... Regards, Arkadiy

On Thu, 15 Dec 2005 14:57:18 -0500, "Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote:
Thus limiting your #simultaneous connections to the number of threads in your thread pool. You can do better by doing all the parsing, request splitting, etc. in a async-using main acceptor thread and handing off smaller bits of work to the threads in the pool. That way you can exploit the fact that some threads may finish some parts of a given request early, and may be available to serve other connections.
Thus serving the same connection from different threads? I just don't happen to like how this would impact the processing algorithm...
You can do what I do, which is to always service the same connection from the same thread. Since my clients can pipeline requests, I don't want requests to be handled out of order. Assigning a connection to a thread in the thread pool is an easy way to solve that problem. Packets are read and parsed in the communications thread, and I send out smaller units of work to the thread pool. From the handlers that are executed in the thread pool I can queue up reply packets to be sent by the communications thread. This way I can connect thousands of clients and keep them connected all day, and service them all simultaneously from a very small number of threads. I could even do everything in one thread if I wanted to. I've done thread-per-connection servers as well, and to be honest I think an async design is easier to work with. If you have a good framework like asio, it can actually end up being cleaner. (At least to my eyes.) -- Be seeing you.

Arkadiy: Thus serving the same connection from different threads? I just don't happen to like how this would impact the processing algorithm...
Arkadiy, If you want to debate this point, I think you should argue in terms of an app that combines async only sockets with sync only sockets, which I believe is a real world use case for asio. Then the question should be, "should the sync only sockets take a demuxer?" I think that's a valid question, but it is being lost in the fact that your example isn't the intended use of asio. I do think that Arkadily has a point. For better or worse, much of the programming world has been trained to think about sockets synchronously, and eventually boost/C++ should address this. When that happens it would be nice to use the same fundamental types presented in asio, and these users shouldn't have to know about demuxers. I just want to point out that async reading and writing could be a function of an async I/O demuxer and not the socket itself. The socket could be passed to the demuxer and not vice versa. Sincerely, Christopher

Hi Christopher, --- christopher baus <christopher@baus.net> wrote: <snip>
I do think that Arkadily has a point. For better or worse, much of the programming world has been trained to think about sockets synchronously, and eventually boost/C++ should address this.
But the question is how this should be addressed :) I happen to think that the fact many people have been trained to think about sockets synchronously is most definitely for the worse. Of course there are situations where synchronous is more appropriate than asynchronous, but it's the lack of awareness of asynchronous as an alternative that can lead to inferior design choices.
When that happens it would be nice to use the same fundamental types presented in asio, and these users shouldn't have to know about demuxers.
With asio, I was hoping to make asynchronous operations as natural and easy to use as possible, compared to their synchronous counterparts. I think that providing a synchronous-only socket interface is counterproductive. It reinforces the notion that synchronous operations are somehow more useful for the ordinary programmer, and that asynchronous is hard and best left in the realm of the networking guru.
I just want to point out that async reading and writing could be a function of an async I/O demuxer and not the socket itself. The socket could be passed to the demuxer and not vice versa.
The above design is something I can fundamentally disagree with ;) That sort of design, in my opinion, relegates asynchronous operations to "second class citizens". Asynchronous operations are no less part of a socket interface than their synchronous counterparts. A write operation sends the same data on a socket whether it is performed synchronously or asynchronously. It's simply that a synchronous call blocks the calling thread until the operation completes, whereas an asynchronous call executes on a logical background thread and tells you when it is complete. Another goal of asio is to provide a basis for further abstraction. For this I believe it is important to provide a clear correlation between the synchronous and asynchronous operations. Consider a protocol where messages have a fixed length header followed by a body, where the body length is contained in the header. The synchronous read operation might look something like: void read_message(Stream& s, message& m) { ... create buffer for header ... read(s, header_buffer); ... create buffer for body of correct size ... read(s, body_buffer); ... populate message structure ... } The equivalent asynchronous code would be: void async_read_message(Stream& s, message& m, Handler h) { ... create buffer for header ... async_read(s, header_buffer, bind(header_handler ...)); } void header_handler(error e, Stream& s, message& m, Handler h) { ... check for failure ... ... create buffer for body of correct size ... async_read(s, body_buffer, bind(body_handler ...)); } void body_handler(error e, Stream& s, message& m, Handler h) { ... check for failure ... ... populate message structure ... h(e); // indicate that operation is complete } There is a relatively straightforward mapping between one and the other. In the future I want to exploit this mapping further by investigating the use of expression templates (perhaps similar to Boost.Lambda) to permit the encoding of a sequence of operations in a synchronous programming style. The operations could then be executed either synchronously or asynchronously as needed. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
With asio, I was hoping to make asynchronous operations as natural and easy to use as possible, compared to their synchronous counterparts.
I am afraid this might be impossible :-( People usually make/think about things sequentially. "1 2 3 4" is much more intuitive than "1 2(call 3 when finished(and 3 will schedule 4).
I just want to point out that async reading and writing could be a function of an async I/O demuxer and not the socket itself. The socket could be passed to the demuxer and not vice versa.
The above design is something I can fundamentally disagree with ;) That sort of design, in my opinion, relegates asynchronous operations to "second class citizens".
Why should a read/write operation necessarily be a member function? Isn't IO just moving bytes from one location to another where neither source nor destination is primary? Consider getline() ftom <string> or streaming operations. All of them are namespace-level functions. There is nothing wrong with having namespace-level functions in C++. We are not in Java... We don't have to be too object-oriented :-) Regards, Arkadiy

Hi Arkadiy, --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
With asio, I was hoping to make asynchronous operations as natural and easy to use as possible, compared to their synchronous counterparts.
I am afraid this might be impossible :-( People usually make/think about things sequentially.
I am more optimistic about people's abilities.
"1 2 3 4" is much more intuitive than "1 2(call 3 when finished(and 3 will schedule 4).
What makes you think that the above approach is the only way to use asynchronicity? :) It has been stated elsewhere in this thread that applications will generally either be synchronous or asynchronous. However, I mentioned in an earlier email that asio's design lets you develop mixed-mode programs. Your entire program does not have to centre around a call to demuxer::run() to benefit from the asynchronous operations. A predominantly synchronous program can still make use of asynchronous calls when concurrency is required. I have written an example (attached) for a protocol I will call AAoIP (ASCII Art over IP). This protocol uses a TCP control connection for managing subscriptions, and UDP for delivering the "frame" data. The client program is written synchronously and implements port hopping (like Skype). To ensure uninterrupted rendering of the ASCII art, the client uses asynchronous operations to continue to receive the frames while the (potentially long running) port renegotiation is in progress. The asynchronous operations are only used for the port renegotiation. Overall, the program flow is still synchronous. Concurrency without threads is what it's about. Cheers, Chris #include <boost/asio.hpp> #include <boost/lambda/lambda.hpp> #include <boost/lambda/bind.hpp> #include <boost/lambda/if.hpp> #include <boost/shared_ptr.hpp> #include <algorithm> #include <cstdlib> #include <exception> #include <iostream> #include <string> #include "protocol.hpp" using namespace boost; int main(int argc, char* argv[]) { try { if (argc != 3) { std::cerr << "Usage: client <host> <port>\n"; return 1; } using namespace std; // For atoi. std::string host_name = argv[1]; unsigned short port = atoi(argv[2]); asio::demuxer demuxer; // Determine the location of the server. asio::ipv4::host_resolver host_resolver(demuxer); asio::ipv4::host host; host_resolver.get_host_by_name(host, host_name); asio::ipv4::tcp::endpoint remote_endpoint(port, host.address(0)); // Establish the control connection to the server. asio::stream_socket control_socket(demuxer); control_socket.connect(remote_endpoint); // Create a datagram socket to receive data from the server. shared_ptr<asio::datagram_socket> data_socket( new asio::datagram_socket(demuxer, asio::ipv4::udp::endpoint(0))); // Determine what port we will receive data on. asio::ipv4::udp::endpoint data_endpoint; data_socket->get_local_endpoint(data_endpoint); // Ask the server to start sending us data. control_request start = control_request::start(data_endpoint.port()); asio::write(control_socket, start.to_buffers()); unsigned long last_frame_number = 0; for (;;) { // Receive 50 messages on the current data socket. for (int i = 0; i < 50; ++i) { // Receive a frame from the server. frame f; data_socket->receive(f.to_buffers(), 0); if (f.number() > last_frame_number) { last_frame_number = f.number(); std::cout << "\n" << f.payload(); } } // Time to switch to a new socket. To ensure seamless handover we will // continue to receive packets using the old socket until data arrives on // the new one. std::cout << " Starting renegotiation"; // Create the new data socket. shared_ptr<asio::datagram_socket> new_data_socket( new asio::datagram_socket(demuxer, asio::ipv4::udp::endpoint(0))); // Determine the new port we will use to receive data. asio::ipv4::udp::endpoint new_data_endpoint; new_data_socket->get_local_endpoint(new_data_endpoint); // Ask the server to switch over to the new port. control_request change = control_request::change( data_endpoint.port(), new_data_endpoint.port()); asio::error control_result; asio::async_write(control_socket, change.to_buffers(), lambda::var(control_result) = lambda::_1); // Try to receive a frame from the server on the new data socket. If we // successfully receive a frame on this new data socket we can consider // the renegotation complete. In that case we will close the old data // socket, which will cause any outstanding receive operation on it to be // cancelled. frame f1; asio::error new_data_socket_result; new_data_socket->async_receive(f1.to_buffers(), 0, ( // Note: lambda::_1 is the first argument to the callback handler, // which in this case is the error code for the operation. lambda::var(new_data_socket_result) = lambda::_1, lambda::if_(!lambda::_1) [ // We have successfully received a frame on the new data socket, // so we can close the old data socket. This will cancel any // outstanding receive operation on the old data socket. lambda::var(data_socket) = shared_ptr<asio::datagram_socket>() ] )); // This loop will continue until we have successfully completed the // renegotiation (i.e. received a frame on the new data socket), or some // unrecoverable error occurs. bool done = false; while (!done) { // Even though we're performing a renegotation, we want to continue // receiving data as smoothly as possible. Therefore we will continue to // try to receive a frame from the server on the old data socket. If we // receive a frame on this socket we will interrupt the demuxer, // print the frame, and resume waiting for the other operations to // complete. frame f2; done = true; // Let's be optimistic. if (data_socket) // Might have been closed by new_data_socket's handler. { data_socket->async_receive(f2.to_buffers(), 0, ( lambda::if_(!lambda::_1) [ // We have successfully received a frame on the old data // socket. Interrupt the demuxer so that we can print it. lambda::bind(&asio::demuxer::interrupt, &demuxer), lambda::var(done) = false ] )); } // Run the operations in parallel. This will block until all operations // have finished, or until the demuxer is interrupted. (No threads!) demuxer.reset(); demuxer.run(); // If the demuxer.run() was interrupted then we have received a frame on // the old data socket. We need to keep waiting for the renegotation // operations to complete. if (!done) { if (f2.number() > last_frame_number) { last_frame_number = f2.number(); std::cout << "\n" << f2.payload(); } } } // Since the loop has finished, we have either successfully completed // the renegotation, or an error has occurred. First we'll check for // errors. if (control_result != asio::error::success) throw control_result; if (new_data_socket_result != asio::error::success) throw new_data_socket_result; // If we get here it means we have successfully started receiving data on // the new data socket. This new data socket will be used from now on // (until the next time we renegotiate). std::cout << " Renegotiation complete"; data_socket = new_data_socket; if (f1.number() > last_frame_number) { last_frame_number = f1.number(); std::cout << "\n" << f1.payload(); } } } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } #include <boost/asio.hpp> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <cmath> #include <cstdlib> #include <exception> #include <iostream> #include <set> #include "protocol.hpp" typedef boost::shared_ptr<boost::asio::stream_socket> stream_socket_ptr; typedef boost::shared_ptr<boost::asio::deadline_timer> deadline_timer_ptr; typedef boost::shared_ptr<control_request> control_request_ptr; class server { public: // Construct the server to wait for incoming control connections. server(boost::asio::demuxer& demuxer, unsigned short port) : acceptor_(demuxer, boost::asio::ipv4::tcp::endpoint(port)), timer_(demuxer), datagram_socket_(demuxer, boost::asio::ipv4::udp::endpoint(0)), next_frame_number_(1) { // Start waiting for a new control connection. stream_socket_ptr new_socket( new boost::asio::stream_socket(acceptor_.demuxer())); acceptor_.async_accept(*new_socket, boost::bind(&server::handle_accept, this, boost::asio::placeholders::error, new_socket)); // Start the timer used to generate outgoing frames. timer_.expires_from_now(boost::posix_time::milliseconds(100)); timer_.async_wait(boost::bind(&server::handle_timer, this)); } // Handle a new control connection. void handle_accept(const boost::asio::error& e, stream_socket_ptr socket) { if (!e) { // Start receiving control requests on the connection. control_request_ptr request(new control_request); boost::asio::async_read(*socket, request->to_buffers(), boost::bind(&server::handle_control_request, this, boost::asio::placeholders::error, socket, request)); // Start waiting for a new control connection. stream_socket_ptr new_socket( new boost::asio::stream_socket(acceptor_.demuxer())); acceptor_.async_accept(*new_socket, boost::bind(&server::handle_accept, this, boost::asio::placeholders::error, new_socket)); } else if (e == boost::asio::error::connection_aborted) { // Try again. acceptor_.async_accept(*socket, boost::bind(&server::handle_accept, this, boost::asio::placeholders::error, socket)); } } // Handle a new control request. void handle_control_request(const boost::asio::error& e, stream_socket_ptr socket, control_request_ptr request) { if (!e) { // Delay handling of the control request to simulate latency on the // network. deadline_timer_ptr delay_timer( new boost::asio::deadline_timer(acceptor_.demuxer())); delay_timer->expires_from_now(boost::posix_time::seconds(2)); delay_timer->async_wait( boost::bind(&server::handle_control_request_timer, this, socket, request, delay_timer)); } } void handle_control_request_timer(stream_socket_ptr socket, control_request_ptr request, deadline_timer_ptr delay_timer) { // Determine what address this client is connected from, since // subscriptions must be stored on the server as a complete endpoint, not // just a port. boost::asio::ipv4::tcp::endpoint remote_endpoint; socket->get_remote_endpoint(remote_endpoint); // Remove old port subscription, if any. if (unsigned short old_port = request->old_port()) { boost::asio::ipv4::udp::endpoint old_endpoint( old_port, remote_endpoint.address()); subscribers_.erase(old_endpoint); std::cout << "Removing subscription " << old_endpoint << std::endl; } // Add new port subscription, if any. if (unsigned short new_port = request->new_port()) { boost::asio::ipv4::udp::endpoint new_endpoint( new_port, remote_endpoint.address()); subscribers_.insert(new_endpoint); std::cout << "Adding subscription " << new_endpoint << std::endl; } // Wait for next control request on this connection. boost::asio::async_read(*socket, request->to_buffers(), boost::bind(&server::handle_control_request, this, boost::asio::placeholders::error, socket, request)); } // Every time the timer fires we will generate a new frame and send it to all // subscribers. void handle_timer() { // Generate payload. double x = next_frame_number_ * 0.2; double y = std::sin(x); int char_index = static_cast<int>((y + 1.0) * (frame::payload_size / 2)); std::string payload; for (int i = 0; i < frame::payload_size; ++i) payload += (i == char_index ? '*' : '.'); // Create the frame to be sent to all subscribers. frame f(next_frame_number_++, payload); // Send frame to all subscribers. We can use synchronous calls here since // UDP send operations typically do not block. std::set<boost::asio::ipv4::udp::endpoint>::iterator j; for (j = subscribers_.begin(); j != subscribers_.end(); ++j) { datagram_socket_.send_to(f.to_buffers(), 0, *j, boost::asio::ignore_error()); } // Wait for next timeout. timer_.expires_from_now(boost::posix_time::milliseconds(100)); timer_.async_wait(boost::bind(&server::handle_timer, this)); } private: // The acceptor used to accept incoming control connections. boost::asio::socket_acceptor acceptor_; // The timer used for generating data. boost::asio::deadline_timer timer_; // The socket used to send data to subscribers. boost::asio::datagram_socket datagram_socket_; // The next frame number. unsigned long next_frame_number_; // The set of endpoints that are subscribed. std::set<boost::asio::ipv4::udp::endpoint> subscribers_; }; int main(int argc, char* argv[]) { try { if (argc != 2) { std::cerr << "Usage: server <port>\n"; return 1; } boost::asio::demuxer d; using namespace std; // For atoi. server s(d, atoi(argv[1])); d.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } #ifndef AAOIP_PROTOCOL_HPP #define AAOIP_PROTOCOL_HPP #include <boost/array.hpp> #include <boost/asio.hpp> #include <cstring> #include <iomanip> #include <string> #include <strstream> // This request is sent by the client to the server over a TCP connection. // The client uses it to perform three functions: // - To request that data start being sent to a given port. // - To request that data is no longer sent to a given port. // - To change the target port to another. class control_request { public: // Construct an empty request. Used when receiving. control_request() { } // Create a request to start sending data to a given port. static const control_request start(unsigned short port) { return control_request(0, port); } // Create a request to stop sending data to a given port. static const control_request stop(unsigned short port) { return control_request(port, 0); } // Create a request to change the port that data is sent to. static const control_request change( unsigned short old_port, unsigned short new_port) { return control_request(old_port, new_port); } // Get the old port. Returns 0 for start requests. unsigned short old_port() const { std::istrstream is(data_, encoded_port_size); unsigned short port = 0; is >> std::setw(encoded_port_size) >> std::hex >> port; return port; } // Get the new port. Returns 0 for stop requests. unsigned short new_port() const { std::istrstream is(data_ + encoded_port_size, encoded_port_size); unsigned short port = 0; is >> std::setw(encoded_port_size) >> std::hex >> port; return port; } // Obtain buffers for reading from or writing to a socket. boost::array<boost::asio::mutable_buffer, 1> to_buffers() { boost::array<boost::asio::mutable_buffer, 1> buffers = { boost::asio::buffer(data_) }; return buffers; } private: // Construct with specified old and new ports. control_request(unsigned short old_port, unsigned short new_port) { std::ostrstream os(data_, control_request_size); os << std::setw(encoded_port_size) << std::hex << old_port; os << std::setw(encoded_port_size) << std::hex << new_port; } // The length in bytes of a control_request and its components. enum { encoded_port_size = 4, // 16-bit port in hex. control_request_size = encoded_port_size * 2 }; // The encoded request data. char data_[control_request_size]; }; // This frame is sent from the server to subscribed clients over UDP. class frame { public: // The maximum allowable length of the payload. enum { payload_size = 32 }; // Construct an empty frame. Used when receiving. frame() { } // Construct a frame with specified frame number and payload. frame(unsigned long number, const std::string& payload) { std::ostrstream os(data_, frame_size); os << std::setw(encoded_number_size) << std::hex << number; os << std::setw(payload_size) << std::setfill(' ') << payload.substr(0, payload_size); } // Get the frame number. unsigned long number() const { std::istrstream is(data_, encoded_number_size); unsigned long number = 0; is >> std::setw(encoded_number_size) >> std::hex >> number; return number; } // Get the payload data. const std::string payload() const { return std::string(data_ + encoded_number_size, payload_size); } // Obtain buffers for reading from or writing to a socket. boost::array<boost::asio::mutable_buffer, 1> to_buffers() { boost::array<boost::asio::mutable_buffer, 1> buffers = { boost::asio::buffer(data_) }; return buffers; } private: // The length in bytes of a frame and its components. enum { encoded_number_size = 8, // Frame number in hex. frame_size = encoded_number_size + payload_size }; // The encoded frame data. char data_[frame_size]; }; #endif // AAOIP_PROTOCOL_HPP

Arkadiy Vertleyb wrote:
"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote
The key problem with that approach is that a `thin' wrapper over the platform APIs is basically useless for implementing asynchronous operations, because asynchronous operations must be implemented in widely different ways on the different platforms.
For synchronous operations, it happens to be the case that every platform provides basically the same interface, so at least a portable (but not necessarily very good) interface can be created through only a thin wrapper.
Then maybe it would make sence to define this kind of API for synchronous operations, while asio encapsulates asynchronous IO using a higher-level abstraction. The socket class would be cleanned from the asynchronous stuff, and used by both asio and synchronous API.
For me a 'socket class' is two things (let's ignore datagram sockets in this context): a way to create socket endpoints, as well as manipulate associated options, and on the other hand some means to read and write. For the synchronous case I think this is nicely described by a streambuf / stream pair of interfaces. A 'socket_stream' class would allow to create socket endpoints, and set socket options. A socketbuf class would implement read and write operations. I'm not sure what Jody would want to do with sockets that can't be represented by these two. On the other hand, asynchronous I/O over sockets would be handled very differently. In fact, given that platforms provide quite different means to wait for readable data (say), I'm not even sure that the 'socket' concept should be discussed independently from the sync / async policy at all. Regards, Stefan

On Wed, 14 Dec 2005 17:31:22 -0500 Stefan Seefeld <seefeld@sympatico.ca> wrote:
For me a 'socket class' is two things (let's ignore datagram sockets in this context): a way to create socket endpoints, as well as manipulate associated options, and on the other hand some means to read and write.
First, you can't ignore datagrams, as that is a large part of modern networking, especially in large data distribution systems. But, for the sake of conversation...
For the synchronous case I think this is nicely described by a streambuf / stream pair of interfaces. A 'socket_stream' class would allow to create socket endpoints, and set socket options. A socketbuf class would implement read and write operations.
I've looked at a ton of IOStream network implementations over the years. Two of my major problems with IOstreams for networking: error handling and performance (followed closely by several other issues). I've yet to see anything close to reasonable. For that matter, I've yet to see anything implemented with the IOStreams that isn't a problem for anything but the most simple applications. As soon as I see IOStreams, my antennae go crazy. Sure, it's a nice concept... I think it is appropriate to offer an IOStreams interface for those who desire such an interface. However, if that is the best way to use synch sockets in boost, I won't go near it. For many years, I have been in the business of very high performance, highly distributed network applications, and my paranoia is well placed ;-)
On the other hand, asynchronous I/O over sockets would be handled very differently. In fact, given that platforms provide quite different means to wait for readable data (say), I'm not even sure that the 'socket' concept should be discussed independently from the sync / async policy at all.
I can see your point.

"Stefan Seefeld" <seefeld@sympatico.ca> wrote in message news:43A09D3A.2030908@sympatico.ca...
Arkadiy Vertleyb wrote:
"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote
The key problem with that approach is that a `thin' wrapper over the platform APIs is basically useless for implementing asynchronous operations, because asynchronous operations must be implemented in widely different ways on the different platforms.
For synchronous operations, it happens to be the case that every platform provides basically the same interface, so at least a portable (but not necessarily very good) interface can be created through only a thin wrapper.
Then maybe it would make sence to define this kind of API for synchronous operations, while asio encapsulates asynchronous IO using a higher-level abstraction. The socket class would be cleanned from the asynchronous stuff, and used by both asio and synchronous API.
For me a 'socket class' is two things (let's ignore datagram sockets in this context): a way to create socket endpoints, as well as manipulate associated options, and on the other hand some means to read and write.
So, this right away leads you to sync_socket and async_socket. What if the read/write operations (ones that make the distinctinction between sync/async) were factored out? Then you would have a socket class to create socket endpoints and manipulate associated options, and (a)synchronicity would be defined by the operations applied to this socket. Regards, Arkadiy

Wow, what a thread. Rather than replying to everything individually, I'm going to try to address the issues in this one email. --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
My problem with the current approach is that synchronous operations seems to be viewed as second-class citizens.
On the contrary, asio attempts to elevate asynchronous operations to the same level as their synchronous counterparts. For virtually every blocking synchronous operation in asio, there is an asynchronous equivalent. The intent is to improve ease of use for asynchronous operations to be as close as possible to synchronous. Far from synchronous operations being given second class status, considerable effort has gone into ensuring that the composed operations (such as asio::[async_]read and asio::[async_]write) have the same semantics for both synchronous and asynchronous calls. Further levels of abstraction should also be achievable in a similar vein. Let me state as clearly as possible: the synchronous operations are not implemented in terms of the asynchronous operations. They call the underlying synchronous system call with little overhead. The question was raised over whether a synchronous-only sockets API should be provided, and asio implemented in terms of that. I rejected this approach early on in the development of asio. As others have already noted, such an API is not sufficient for the development of asynchronous operations, and you are forced to step outside of it to use operating system-specific functionality. Instead, asio is itself intended as a thin, portable API that provides both synchronous and asynchronous support. So, the nub of your issue appears to be the conceptual difficulties you perceive in having the socket constructor take a demuxer, and for the interface to include both synchronous and asynchronous operations. Let's consider the alternative approach proposed: to split the socket class into separate sync_socket and async_socket classes. The drawbacks of this approach are increased redundancy and complexity in the programming interface. If this approach were already in place, I can imagine that I would be getting questions about why there are so many socket classes, and which one is the most appropriate to use. There are trade-offs in every design decision. I elected to give synchronous and asynchronous operations equal weight, and combine them in a single interface. This has the added advantage of allowing mixed-mode programming, where synchronous operations can be used alongside asynchronous in a single application. As you point out, there are situations where synchronous is more appropriate, and these differences can occur within an application. I don't think that the need to pass a demuxer to the socket constructor is conceptually difficult to explain. It's there for the asynchronous operations, if and when you need them. I also do not agree that it is onerous to have to pass the demuxer to the constructor, or to pass it wherever it's needed in the program. You can make it a global, declare a new one for each use, or even use one associated with an existing object, as in: stream_socket s(my_acceptor.demuxer()); Therefore I stand by my design choices as the best balance between usability, functionality and flexibility. Now, to turn to more philosophical matters, namely synchronous versus asynchronous approaches. As you pointed out, synchronous socket programming is easier to debug. However, there are also disadvantages, notably increased resource usage (with a thread required per connection) reducing scalability, and the need for explicit synchronisation between threads. Correct programming in the presence of threads is a difficult art, and I see this as a major disadvantage of the synchronous approach. Asynchronous socket programming, on the other hand, has the advantage of allowing concurrent operations without synchronisation. Programs can be written as though they exist in a single-threaded environment, and still scale to handle numerous connections. The main disadvantage of the asynchronous approach is program complexity, since operation initiation is separate from completion, and the flow of control is inverted. However I have designed asio to mitigate these as much as possible, particularly in providing the ability to combine asynchronous operations to allow the implementation of higher-level abstractions. When people first start network programming, it is natural for them to gravitate towards synchronous operations. They may then progress to writing more complex applications, such as servers, that must handle multiple connections. If all they have been exposed to is synchronous network programming, then it is likely they will end up with a design that requires many threads. By making both synchronous and asynchronous operations readily available, asio may inform programmers that there are alternatives to multithreading. If this helps them to make a more appropriate choice for their application, then that has got to be a good thing. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
Let me state as clearly as possible: the synchronous operations are not implemented in terms of the asynchronous operations. They call the underlying synchronous system call with little overhead.
OK.
So, the nub of your issue appears to be the conceptual difficulties you perceive in having the socket constructor take a demuxer, and for the interface to include both synchronous and asynchronous operations.
Right, I have a conceptual problem with having to allocate an object that is never used.
Let's consider the alternative approach proposed: to split the socket class into separate sync_socket and async_socket classes.
That's one way of doing things. The other might be to move read/write operations outside the socket class.
The drawbacks of this approach are increased redundancy and complexity in the programming interface. If this approach were already in place, I can imagine that I would be getting questions about why there are so many socket classes, and which one is the most appropriate to use.
There are trade-offs in every design decision. I elected to give synchronous and asynchronous operations equal weight, and combine them in a single interface. This has the added advantage of allowing mixed-mode programming, where synchronous operations can be used alongside asynchronous in a single application. As you point out, there are situations where synchronous is more appropriate, and these differences can occur within an application.
I don't think that the need to pass a demuxer to the socket constructor is conceptually difficult to explain. It's there for the asynchronous operations, if and when you need them.
My complain is not about explanation, but about a conceptual cleanness of the interface. One has to allocate an object which is not used, and this rings a bell.
I also do not agree that it is onerous to have to pass the demuxer to the constructor, or to pass it wherever it's needed in the program. You can make it a global, declare a new one for each use, or even use one associated with an existing object, as in:
stream_socket s(my_acceptor.demuxer());
Therefore I stand by my design choices as the best balance between usability, functionality and flexibility.
I respect that.
Now, to turn to more philosophical matters, namely synchronous versus asynchronous approaches.
As you pointed out, synchronous socket programming is easier to debug. However, there are also disadvantages, notably increased resource usage (with a thread required per connection)
I actually meant fixed amount of worker threads taking tasks from the queue -- not a thread per connection.
[...]
Regards, Arkadiy

Christopher Kohlhoff <chris@kohlhoff.com> writes:
[snip]
I don't think that the need to pass a demuxer to the socket constructor is conceptually difficult to explain. It's there for the asynchronous operations, if and when you need them.
I also do not agree that it is onerous to have to pass the demuxer to the constructor, or to pass it wherever it's needed in the program. You can make it a global, declare a new one for each use, or even use one associated with an existing object, as in:
stream_socket s(my_acceptor.demuxer());
Therefore I stand by my design choices as the best balance between usability, functionality and flexibility.
It seems that a possible solution is to provide a `dummy' demuxer that simply makes asynchronous operations fail. This `dummy' demuxer could then be the default parameter to the socket constructors, thus eliminating the need to specify a demuxer for synchronous-only use. It seems that this would indeed clean up the interface for synchronous-only use.
[snip]
-- Jeremy Maitin-Shepard

--- Jeremy Maitin-Shepard <jbms@cmu.edu> wrote:
It seems that a possible solution is to provide a `dummy' demuxer that simply makes asynchronous operations fail. This `dummy' demuxer could then be the default parameter to the socket constructors, thus eliminating the need to specify a demuxer for synchronous-only use.
It seems that this would indeed clean up the interface for synchronous-only use.
After pondering this for a few days, I've come to the conclusion that doing the above isn't that dissimilar to having a singleton demuxer. Such a demuxer would be used as the default argument to constructors, as Jeremy said. It certainly wouldn't be any different in terms of runtime performance. What do people think of this approach? Basically I would add a new static function basic_demuxer<>::global(), which returns a reference to the singleton demuxer object. Constructors would look something like: basic_stream_socket(demuxer_type& d = demuxer_type::global()) Synchronous-only code could then be written without knowledge of the demuxer: ipv4::host_resolver r; stream_socket s; deadline_timer t; etc... However, unlike Jeremy's dummy demuxer suggestion, the async operations would still be allowed. This would benefit some async apps that only want a single demuxer, since they no longer have to pass it to everywhere it would be used. It would still be the responsibility of the application to call run() on the singleton demuxer if it does make async calls. A further advantage of this approach is that, by allowing default construction of the various resource classes, I can implement swap() to allow the underlying resources to be passed around efficiently without requiring additional dynamic memory allocation. [ And in a related idea, would it be worth implementing "move constructors" like auto_ptr has for the resource classes? This would allow users to create efficient factory functions that set up default options, etc, e.g. something like: stream_socket make_stream_socket() { stream_socket s; ... return s.move(); } Or is this something better left out since it might be non-portable, not compatible with standard C++, better covered by some separate utility...? ] I generally dislike singletons because of the issues to do with the order of cosntruction and cleanup. The problem arises here if, for example, there is a global stream_socket that uses the singleton. I suggest that the singleton could be: - Created at the first call to demuxer::global(), so that any errors (such as failure to initialise Winsock) can be thrown and caught in user code. - Never destroyed. This is not good for DLLs that are dynamically loaded and unloaded, but perhaps the documentation can advise authors of such DLLs to avoid the singleton demuxer. There may be a better way to manage creation and destruction, so I'm open to suggestions. Applications that need to initialise a demuxer in a particular way, e.g. by setting custom allocation options, can do so by not using the singleton demuxer. As with some other features of the demuxer, this approach is not totally unlike that used by std::locale. It doesn't require significant changes to the current implementation of asio and existing code should work as-is. It doesn't add new types that could significantly increase the size of the API. It doesn't promote sync over async or vice versa (for better or worse ;). Other than the creation/destruction issues discussed above, I can't think of any insidious, hard-to-find bugs that this approach might cause to justify not doing this change. Cheers, Chris

Hi Christopher, "Christopher Kohlhoff" <chris@kohlhoff.com> wrote
--- Jeremy Maitin-Shepard <jbms@cmu.edu> wrote:
It seems that a possible solution is to provide a `dummy' demuxer that simply makes asynchronous operations fail. This `dummy' demuxer could then be the default parameter to the socket constructors, thus eliminating the need to specify a demuxer for synchronous-only use.
It seems that this would indeed clean up the interface for synchronous-only use.
After pondering this for a few days, I've come to the conclusion that doing the above isn't that dissimilar to having a singleton demuxer. Such a demuxer would be used as the default argument to constructors, as Jeremy said. It certainly wouldn't be any different in terms of runtime performance. What do people think of this approach?
Could you provide some (high-level) justification of why it is beneficial to have a socket dependency upon a demuxer? Maybe I am missing something, but IMO such a dependency is unnatural even for asynchronous IO. Hiding it behind default parameter, singleton, etc., just masks what I believe is a design problem. If you absolutely believe this dependency is necessary for async IO, having two separate classes, such as sync_socket and async_socket would be much cleaner. But again, I can't see what justifies such a dependency even for async IO. Regards, Arkadiy Regards, Arkadiy

Hi Arkadiy, --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Could you provide some (high-level) justification of why it is beneficial to have a socket dependency upon a demuxer?
Maybe I am missing something, but IMO such a dependency is unnatural even for asynchronous IO.
This has not come up in the discussion thus far, but the dependency on the demuxer is required to enable an efficient and portable implementation of asynchronous I/O. This is one of the primary goals of asio. The asio interface has been designed to reflect efficient asynchronous I/O mechanisms across major operating systems. Let's take the specific case of Windows, where asynchronous I/O is implemented using I/O completion ports. Once a socket is associated with an I/O completion port it cannot be disassociated. In asio, the demuxer represents the I/O completion port. The asio interface enforces this association by constructing the socket with its associated demuxer/IO-completion-port.
From the point of view of the library user, any asynchronous operations started on the socket will have the completion handler delivered through the associated demuxer (as it is with I/O completion ports).
Separating the asynchronous I/O operations from the socket class would no longer enforce this requirement at compile time. (That's even leaving aside questions about where the operation logically belongs.)
Hiding it behind default parameter, singleton, etc., just masks what I believe is a design problem.
If you absolutely believe this dependency is necessary for async IO, having two separate classes, such as sync_socket and async_socket would be much cleaner. But again, I can't see what justifies such a dependency even for async IO.
So the question is really whether there should be separate sync and async socket classes, versus a combined class as there is now. I believe that, on balance, separate classes would be harmful. Let's reiterate some of the points: - Synchronous and asynchronous operations are essentially the same, in that they perform the same operation and aim to fulfil the same contract. How they differ is simply that one blocks the current thread, whereas the other executes in a background logical thread and tells you when it is complete. I want there to be a clear mapping from one to the other. - Separate classes reinforces the notion that synchronous operations are somehow more useful for the ordinary programmer, and that asynchronous is hard and best left in the realm of the networking guru. I do not believe that asynchronicity is difficult to use or understand, it is simply that programmers often approach sockets with preconceived ideas of how an API should behave. But I do believe that separating the classes will continue the lack of awareness of asynchronicity as an option, leading to the needless use of inferior designs. - The greater number of classes and options will increase the size of the library's interface. - I have demonstrated that there is a use case for mixed-mode applications. Predominantly synchronous designs can benefit from localised asynchronicity. Asynchronous designs can be simplified by selective use of synchronous calls. - A line of reasoning has been presented which says that, because files already have a synchronous-only interface and could be given an asynchronous interface, sockets should also have a synchronous-only interface. However, sockets are inherently different to files due to the long timescales involved in many operations. Developers will look for some form of concurrency to address this, and I believe asynchronicity *should* be promoted over threads as a solution. It is worth noting that even .NET combines the sync and async socket operations on a single interface. I do find that there is a compelling case for a synchronous-only interface to sockets at the iostreams level. But that is because of the established use and wide applicability of iostreams, not because it is synchronous. So let me turn the question around. Why must there be a low-level synchronous-only interface at all? What benefits will it bring? What use cases do you have that cannot be met by a combined interface or an iostream interface? Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote
--- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
Could you provide some (high-level) justification of why it is beneficial to have a socket dependency upon a demuxer?
Maybe I am missing something, but IMO such a dependency is unnatural even for asynchronous IO.
This has not come up in the discussion thus far, but the dependency on the demuxer is required to enable an efficient and portable implementation of asynchronous I/O. This is one of the primary goals of asio. The asio interface has been designed to reflect efficient asynchronous I/O mechanisms across major operating systems.
Let's take the specific case of Windows, where asynchronous I/O is implemented using I/O completion ports. Once a socket is associated with an I/O completion port it cannot be disassociated.
In asio, the demuxer represents the I/O completion port. The asio interface enforces this association by constructing the socket with its associated demuxer/IO-completion-port.
From the point of view of the library user, any asynchronous operations started on the socket will have the completion handler delivered through the associated demuxer (as it is with I/O completion ports).
Separating the asynchronous I/O operations from the socket class would no longer enforce this requirement at compile time. (That's even leaving aside questions about where the operation logically belongs.)
Understand and agreed. For async IO, socket doesn't seem to make a sence without a demuxer. Passing the demuxer to the socket constructor in this case makes perfect sense.
Hiding it behind default parameter, singleton, etc., just masks what I believe is a design problem.
If you absolutely believe this dependency is necessary for async IO, having two separate classes, such as sync_socket and async_socket would be much cleaner. But again, I can't see what justifies such a dependency even for async IO.
So the question is really whether there should be separate sync and async socket classes, versus a combined class as there is now. I believe that, on balance, separate classes would be harmful. Let's reiterate some of the points:
- Synchronous and asynchronous operations are essentially the same, in that they perform the same operation and aim to fulfil the same contract. How they differ is simply that one blocks the current thread, whereas the other executes in a background logical thread and tells you when it is complete. I want there to be a clear mapping from one to the other.
That should not be difficult to achieve with derivation or some sort of policy. Looks like async_socket might be derived from sync_socket (or contain it).
- Separate classes reinforces the notion that synchronous operations are somehow more useful for the ordinary programmer, and that asynchronous is hard and best left in the realm of the networking guru. I do not believe that asynchronicity is difficult to use or understand, it is simply that programmers often approach sockets with preconceived ideas of how an API should behave. But I do believe that separating the classes will continue the lack of awareness of asynchronicity as an option, leading to the needless use of inferior designs.
I don't agree that separate classes reinforce any notion. They just clearly state that there are two different ways of doing things. And I don't believe forcing people to use a demuxer without the need is a right way to raise awareness...
- The greater number of classes and options will increase the size of the library's interface.
One of the fundamental OO design principles states that clients should not depend on what they don't use (Interface segregation principle).
- I have demonstrated that there is a use case for mixed-mode applications. Predominantly synchronous designs can benefit from localised asynchronicity. Asynchronous designs can be simplified by selective use of synchronous calls.
So one would use async sockets in this case.
- A line of reasoning has been presented which says that, because files already have a synchronous-only interface and could be given an asynchronous interface, sockets should also have a synchronous-only interface. However, sockets are inherently different to files due to the long timescales involved in many operations. Developers will look for some form of concurrency to address this, and I believe asynchronicity *should* be promoted over threads as a solution.
It is worth noting that even .NET combines the sync and async socket operations on a single interface.
I do find that there is a compelling case for a synchronous-only interface to sockets at the iostreams level. But that is because of the established use and wide applicability of iostreams, not because it is synchronous.
So let me turn the question around. Why must there be a low-level synchronous-only interface at all? What benefits will it bring? What use cases do you have that cannot be met by a combined interface or an iostream interface?
I don't like the the combined interface because it doesn't satisfy the interface segregation principle. Also I don't like when things have to be specified that are not used. iostream might be alright, but it makes things kind of asynchronous (in the other sence of this word). Also you don't win anything -- you will have two interfaces, the same amount you would have with sync_ and async_socket. Regards, Arkadiy

Arkadiy Vertleyb wrote:
That should not be difficult to achieve with derivation or some sort of policy. Looks like async_socket might be derived from sync_socket (or contain it).
This was also my thought - public derivation. I.e., the current socket class is refactored into two classes - base (sync_socket) and and derived (async_socket). The sync_socket class won't know anyhing about demuxers (it doesn't need to). The async_socket class will add asynchronous operations and binding to a specific demuxer. Chris, what do you think about this?

"Peter Petrov" <ppetrov@ppetrov.com> wrote in message news:do81b5$4ir$1@sea.gmane.org...
Arkadiy Vertleyb wrote:
That should not be difficult to achieve with derivation or some sort of policy. Looks like async_socket might be derived from sync_socket (or contain it).
This was also my thought - public derivation. I.e., the current socket class is refactored into two classes - base (sync_socket) and and derived (async_socket). The sync_socket class won't know anyhing about demuxers (it doesn't need to).
Except that demuxer is currently also a service repository. And wouldn't the service repository if decoupled from a demuxer be a legitimate candidate for a singleton?

----- Original Message ----- From: "Eugene Alterman" <eugalt@verizon.net> To: <boost@lists.boost.org> Sent: Tuesday, December 20, 2005 6:39 PM Subject: Re: [boost] singleton asio::demuxer as default? (was:asioformalreview begins)
"Peter Petrov" <ppetrov@ppetrov.com> wrote in message news:do81b5$4ir$1@sea.gmane.org...
Arkadiy Vertleyb wrote:
That should not be difficult to achieve with derivation or some sort of policy. Looks like async_socket might be derived from sync_socket (or contain it).
This was also my thought - public derivation. I.e., the current socket class is refactored into two classes - base (sync_socket) and and derived (async_socket). The sync_socket class won't know anyhing about demuxers (it doesn't need to).
Except that demuxer is currently also a service repository. And wouldn't the service repository if decoupled from a demuxer be a legitimate candidate for a singleton?
Do the current synchronous methods make use of the demuxer? If not, then what about a separate constructor with no demuxer that marks the socket as for synch use only. Then throw if asynch methods are touched.

Eugene Alterman wrote:
"Peter Petrov" <ppetrov@ppetrov.com> wrote in message
This was also my thought - public derivation. I.e., the current socket class is refactored into two classes - base (sync_socket) and and derived (async_socket). The sync_socket class won't know anyhing about demuxers (it doesn't need to).
Except that demuxer is currently also a service repository. And wouldn't the service repository if decoupled from a demuxer be a legitimate candidate for a singleton?
My understanding is that the "service repository" is actually only needed for the asynchronous operations.

--- Peter Petrov <ppetrov@ppetrov.com> wrote:
My understanding is that the "service repository" is actually only needed for the asynchronous operations.
I think there may be some confusion on this point. Even a hypothetical synchronous-only sockets implementation in asio would still need the "service repository". It occurs to me that I have neglected to mention one other important point about why a demuxer parameter is required, regardless of whether or not the application is making use of async operations: portability. On some systems it is necessary to initialise something before sockets or other I/O objects can be used. Let's take the specific example of Symbian: - The Symbian sockets API is based on BSD sockets, but is presented as a collection of C++ classes (as is the rest of the Symbian programming interface). - Before using sockets, an IPC connection to the socket server (a separate process) must be established. This is achieved by opening an RSocketServ object. - A reference to the socket server must be supplied when opening a socket, i.e.: TInt RSocket::Open(RSocketServ& aServer, ... other args ...); - Similarly, a reference to the socket server must be supplied when opening an RHostResolver object: TInt RHostResolver::Open(RSocketServ& aServer, ... etc ...); Even on Windows it is necessary to initialise Winsock using WSAStartup before socket operations are available. Likewise, when socket operations are no longer required you need to call WSACleanup. With respect to using singletons as a means to address this, leaving design objections aside, Symbian does not allow programs to have global or static data - something I had actually forgotten. :) If the demuxer parameter is not passed to the socket, then portability to operating systems with these requirements would be restricted. So I wonder if the problem is really just one of naming. Specifically, the name of the demuxer class. Asio started life as a library intended for asynchronous use-cases, and for developers with that sort of background I believe the name demuxer (i.e. demultiplexer) is perfectly natural. Now that it has grown into general purpose use, perhaps the name should reflect the expectations of a wider audience. I propose that it could be called something like "io_system". (Other naming suggestions will be appreciated.) Consider the following statements: - An io_system object must be initialised before sockets (or other I/O objects) can be used. See the portability requirement above. - An io_system object is an extensible collection of I/O services (drivers?). - Synchronous operations implicitly run the io_system object for an individual operation. - You must run() the io_system object for it to perform asynchronous operations on your behalf. - You can partition your program by using multiple io_system objects. For asynchronous operations, the fact that a demultiplexer is used is an implementation detail. The public interface of asio does not prohibit an implementation that uses, say, a thread per operation. Given the portability rationale for requiring such a parameter, I can't think of any reasons, be they conceptual, ease-of-use or otherwise, to have a low-level synchronous-only interface. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051221090730.33683.qmail@web32603.mail.mud.yahoo.com...
For asynchronous operations, the fact that a demultiplexer is used is an implementation detail. The public interface of asio does not prohibit an implementation that uses, say, a thread per operation.
Does it make any difference? Thread completion still needs to be demultiplexed.

Hi Eugene, --- Eugene Alterman <eugalt@verizon.net> wrote:
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051221090730.33683.qmail@web32603.mail.mud.yahoo.com...
For asynchronous operations, the fact that a demultiplexer is used is an implementation detail. The public interface of asio does not prohibit an implementation that uses, say, a thread per operation.
Does it make any difference? Thread completion still needs to be demultiplexed.
Let me put my point another way: Knowledge of the existence of demultiplexing, or any other method for implementing asynchronous I/O, is not a prerequisite for the use or understanding of asynchronous operations. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051221090730.33683.qmail@web32603.mail.mud.yahoo.com...
- You must run() the io_system object for it to perform asynchronous operations on your behalf.
Just to be precise, if asynchronous i/o is real (not emulated) you don't have to do anything for the operation to be performed, and run() will just demultiplex completion events. This BTW brings up a question of possible portability issues resulting from such a difference in behavior. I also think that 'run' is not descriptive enough, especially if the class name becomes 'io_system'. Something like dispatch_events() may be?

Hi Eugene, --- Eugene Alterman <eugalt@verizon.net> wrote:
Just to be precise, if asynchronous i/o is real (not emulated) you don't have to do anything for the operation to be performed, and run() will just demultiplex completion events.
True.
This BTW brings up a question of possible portability issues resulting from such a difference in behavior.
The actual behaviour is implementation-defined. That is, the operation may be performed at any time from when the async_*() call is made, but ultimately a run() call is still required. In practice this variance in implementation-defined behaviour has not been an issue, provided run() is used appropriately. There are many other issues that can also affect the observable effects of operations, such as buffering, Nagle algorithm, OS scheduling, latency and so on.
I also think that 'run' is not descriptive enough, especially if the class name becomes 'io_system'. Something like dispatch_events() may be?
Since the run() function does more than just dispatch completion handlers, I'm not sure that dispatch_events() sufficiently reflects what it does, or the requirement that it be called for asynchronous operations. To my mind, run() reflects that you are lending your thread to the demuxer (or 'io_system') so that it may perform whatever work is required. That's not to say that I'm particularly attached to the name run(). Any other suggestions? Cheers, Chris

Christopher Kohlhoff <chris <at> kohlhoff.com> writes:
I propose that it could be called something like "io_system". (Other naming suggestions will be appreciated.)
Based on the statements below, I think io_engine might be appropriate.
Consider the following statements:
- An io_system object must be initialised before sockets (or other I/O objects) can be used. See the portability requirement above.
- An io_system object is an extensible collection of I/O services (drivers?).
- Synchronous operations implicitly run the io_system object for an individual operation.
- You must run() the io_system object for it to perform asynchronous operations on your behalf.
- You can partition your program by using multiple io_system objects.
I wonder if 'service' is the best name for the set of I/O objects encapsulated by the library, but I can't offer any alternatives... Matt

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051221090730.33683.qmail@web32603.mail.mud.yahoo.com...
...
So I wonder if the problem is really just one of naming. Specifically, the name of the demuxer class.
Asio started life as a library intended for asynchronous use-cases, and for developers with that sort of background I believe the name demuxer (i.e. demultiplexer) is perfectly natural.
Now that it has grown into general purpose use, perhaps the name should reflect the expectations of a wider audience.
I think that would be very helpful.
I propose that it could be called something like "io_system". (Other naming suggestions will be appreciated.)
"io_system" seems a little broad. I think of an "io_system" as encompassing all I/O mechanisms an operating system supports. A web search turns up broad phrases like "The basic model of the UNIX I/O system is a sequence of bytes that can be accessed either randomly or sequentially." "io_engine" implies to me something at a very low level, like the set of device drivers. How about "io_service"? That seems both narrowly focused and about the right level to me. I've changed "io_system" to "io_service" in your text below. Reads quite well IMO.
Consider the following statements:
- An io_service object must be initialised before sockets (or other I/O objects) can be used. See the portability requirement above.
- An io_service object is an extensible collection of I/O services (drivers?).
- Synchronous operations implicitly run the io_service object for an individual operation.
- You must run() the io_service object for it to perform asynchronous operations on your behalf.
- You can partition your program by using multiple io_service objects.
--Beman

Beman Dawes wrote:
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051221090730.33683.qmail@web32603.mail.mud.yahoo.com...
I propose that it could be called something like "io_system". (Other naming suggestions will be appreciated.)
"io_system" seems a little broad. I think of an "io_system" as encompassing all I/O mechanisms an operating system supports. A web search turns up broad phrases like "The basic model of the UNIX I/O system is a sequence of bytes that can be accessed either randomly or sequentially."
"io_engine" implies to me something at a very low level, like the set of device drivers.
How about "io_service"? That seems both narrowly focused and about the right level to me.
I think that a "driver" matches the description quite well:
- A driver object must be initialised before sockets (or other I/O objects) can be used. See the portability requirement above.
- A driver object is an extensible collection of I/O services (drivers?).
- Synchronous operations implicitly run the driver object for an individual operation.
- You must run() the driver object for it to perform asynchronous operations on your behalf.
- You can partition your program by using multiple driver objects.

"Peter Dimov" <pdimov@mmltd.net> wrote
Beman Dawes wrote:
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051221090730.33683.qmail@web32603.mail.mud.yahoo.com...
I propose that it could be called something like "io_system". (Other naming suggestions will be appreciated.)
"io_system" seems a little broad. I think of an "io_system" as encompassing all I/O mechanisms an operating system supports. A web search turns up broad phrases like "The basic model of the UNIX I/O system is a sequence of bytes that can be accessed either randomly or sequentially."
"io_engine" implies to me something at a very low level, like the set of device drivers.
How about "io_service"? That seems both narrowly focused and about the right level to me.
I think that a "driver" matches the description quite well:
"dispatcher"? Regards, Arkadiy

--- Beman Dawes <bdawes@acm.org> wrote:
How about "io_service"? That seems both narrowly focused and about the right level to me.
I've changed "io_system" to "io_service" in your text below. Reads quite well IMO.
I like it, except for the following statement:
- An io_service object is an extensible collection of I/O services (drivers?).
Perhaps io_services would be better? To continue the high-latency brainstorming... Following on from Peter's suggestion of driver, and in light of io_services vs io_service, what about io_kernel, which is a collection of drivers? Might sound too low level though. Regarding Arkadiy's suggestion, I'm not sure about just dispatcher, although maybe io_dispatcher is ok. But I don't think it conveys the collection of services idea sufficiently -- unless it refers to a dispatcher from the Client-Dispatcher-Server pattern :) I did some reading up on patterns during the break; mostly Pattern Oriented Software Architecture volume 1. The one pattern that stands out is the Broker pattern, even though it is intended for distributed applications. I believe that asio implements a variant of this pattern. The relevant parts include: - A Client (the application) sends requests through a Client-side Proxy (e.g. stream_socket). - A Client-side Proxy encapsulates functionality and mediates between the Client and the Broker. - A Broker registers and locates Servers (get_service()) and runs an event loop (run()). It provides a way for the results of operations to be forwarded back to Clients (e.g. dispatch() and post()). - A Server implements services, and is registered with a Broker. Some Servers are implemented locally to the client (e.g. locking_dispatcher_service) and others can be located on a remote Broker. In this case you could consider that the remote Broker is a virtual Broker that exists within the operating system. So what about io_broker? For people who have used CORBA, similar rules apply in respect of calling run(): - No need to call run() in a client making synchronous calls. - Need to call run() if using asynchronous method invocation so that results can be delivered. A possible disadvantage of io_broker is that the name conveys too much meaning, in that users may feel it is a concept they need to understand before using the facilities it provides. I don't think a name like io_services suffers from this. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051228012139.77878.qmail@web32615.mail.mud.yahoo.com...
So what about io_broker? For people who have used CORBA, similar rules apply in respect of calling run():
- No need to call run() in a client making synchronous calls.
- Need to call run() if using asynchronous method invocation so that results can be delivered.
A possible disadvantage of io_broker is that the name conveys too much meaning, in that users may feel it is a concept they need to understand before using the facilities it provides. I don't think a name like io_services suffers from this.
I have a mild preference for io_services. But io_drivers or some other variation on Peter's "driver" suggestion would also work for me. For the "too much meaning" rationale you give, I don't particular like io_broker, but even that seems better than demuxer, which I always found confusing. Thanks for worrying about such quibbles - good names do help a library. --Beman

| -----Original Message----- | From: boost-bounces@lists.boost.org | [mailto:boost-bounces@lists.boost.org] On Behalf Of Beman Dawes | Sent: 29 December 2005 02:40 | To: boost@lists.boost.org | Subject: Re: [boost] singleton asio::demuxer | asdefault?(was:asioformalreview begins) | | I have a mild preference for io_services. Having racked my brain to come up with a better idea than this or the several other ideas, I have strong preference for Beman's original suggestion: io_service I think it just hits the spot. The pluralization is entirely unecessary as a 'service' can include many elements - think 'service' at a restaurant. And iostream is singular (but lacks the _ so ioservice would be consistent but not nice?). 'Driver' is a daft name - and always has been IMO. asio is short and snappy - does it stand for Async AND Sync Input Output? But is the sync or not that important? | Thanks for worrying about such quibbles - good names do help a library. I agree that names are _really_ important. Paul -- Paul A Bristow Prizet Farmhouse, Kendal, Cumbria UK LA8 8AB Phone and SMS text +44 1539 561830, Mobile and SMS text +44 7714 330204 mailto: pbristow@hetp.u-net.com http://www.hetp.u-net.com/index.html http://www.hetp.u-net.com/Paul%20A%20Bristow%20info.html

Hi Paul, --- Paul A Bristow <pbristow@hetp.u-net.com> wrote:
I have strong preference for Beman's original suggestion:
io_service
I think it just hits the spot.
That's good enough for me, I'll go with io_service.
asio is short and snappy - does it stand for Async AND Sync Input Output?
Nah, it stands for Australian Security Intelligence Organisation ;) Seriously though, it got the name because my wife thought it was funny. Cheers, Chris

Christopher Kohlhoff wrote: [...]
Basically I would add a new static function basic_demuxer<>::global(), which returns a reference to the singleton demuxer object. Constructors would look something like:
basic_stream_socket(demuxer_type& d = demuxer_type::global())
Synchronous-only code could then be written without knowledge of the demuxer:
ipv4::host_resolver r; stream_socket s; deadline_timer t; etc...
However, unlike Jeremy's dummy demuxer suggestion, the async operations would still be allowed.
[...]
I generally dislike singletons because of the issues to do with the order of cosntruction and cleanup.
You should dislike singletons because they are global variables. The problem with the above apporach is that most code will end up using the global demuxer because this is the path of least resistance. When it turns out that the code needs to be refactored to use a specific demuxer, the programmers would need to go over it with a fine-toothed comb and replace every implicit reference to the global demuxer with an explicit demuxer reference, passing that reference wherever necessary. It'd be a pain, and it'd be easy to miss some. Not to mention that the code that implicitly uses the global demuxer may be in a library for which the source is not available. So yes, the easy case will be easier, but the hard case will be harder.

Peter Dimov <pdimov@mmltd.net> writes:
The problem with the above apporach is that most code will end up using the global demuxer because this is the path of least resistance. When it turns out that the code needs to be refactored to use a specific demuxer, the programmers would need to go over it with a fine-toothed comb
We had this exact problem with a large application built atop ACE. Within ACE, there are a few areas where one can refer to "/the/ reactor" rather than "/a/ reactor" or "/this/ reactor", mostly for convenience. We had our reasons for creating and passing around our own reactor, but it was difficult to ensure that all the reactor-dependent code explicitly used the proper reactor instance. -- Steven E. Harris

The problem with the above apporach is that most code will end up using the global demuxer ...
We had this exact problem with a large application built atop ACE. Within ACE, there are a few areas where one can refer to "/the/ reactor" rather than "/a/ reactor" or "/this/ reactor ...
I really dislike the ACE singleton interface in the Reactor (and in some other classes), for the same reasons already described. In particular, an application might use a library that internally uses the Asio singleton demuxer - now the application's use of the singleton demuxer might conflict. This dislike is based on real-life use cases (with ACE), btw. Please don't add a singleton demuxer interface. Cliff

On Mon, 19 Dec 2005 19:43:00 -0500, Cliff Green wrote
The problem with the above apporach is that most code will end up using the global demuxer ...
We had this exact problem with a large application built atop ACE. Within ACE, there are a few areas where one can refer to "/the/ reactor" rather than "/a/ reactor" or "/this/ reactor ...
I really dislike the ACE singleton interface in the Reactor (and in some other classes), for the same reasons already described. In particular, an application might use a library that internally uses the Asio singleton demuxer - now the application's use of the singleton demuxer might conflict. This dislike is based on real-life use cases (with ACE), btw. Please don't add a singleton demuxer interface.
One more voice against this...I've had similar issues with singletons in ACE and other places. There would have to be extreme justification to introduce a singleton into the asio interface. Small simplification for end users isn't enough for me. Peter's points are right on the mark... Jeff

One more voice against this...I've had similar issues with singletons in ACE and other places. There would have to be extreme justification to introduce a singleton into the asio interface. Small simplification for end users isn't enough for me. Peter's points are right on the mark...
Yes, please. No singletons for convenience purposes - it's extremely hard to enforce that such singletons -not- be used in complex applications. Dave Moore

Hi Peter, --- Peter Dimov <pdimov@mmltd.net> wrote:
You should dislike singletons because they are global variables.
The problem with the above apporach is that most code will end up using the global demuxer because this is the path of least resistance. When it turns out that the code needs to be refactored to use a specific demuxer, the programmers would need to go over it with a fine-toothed comb and replace every implicit reference to the global demuxer with an explicit demuxer reference, passing that reference wherever necessary. It'd be a pain, and it'd be easy to miss some. Not to mention that the code that implicitly uses the global demuxer may be in a library for which the source is not available.
So yes, the easy case will be easier, but the hard case will be harder.
Good points. Scratch that idea. Cheers, Chris

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnq31k$f1f$1@sea.gmane.org...
My problem with the current approach is that synchronous operations seems to be viewed as second-class citizens. I understand that this is caused by the fact that asio is about the asynchronous IO. But asio is presented as the "best C++ networking library around", and in networking both synchronous and asynchronous approaches are used, and neither is superior for all possible cases.
Consider "Patterns for concurrent and networked objecs" by D. Schmidt, at al. Quite a few patterns described there are related to the synchronous processing.
I just want to point out (hopefully without getting off topic too much) that there is an important difference between what you mean by 'synchronous' and the way this term is usually used in this context (and in particular in the publication you are mentioning). By 'synchronous' you seem to mean *blocking* synchronous i/o while in those patterns it is understood as synchronous i/o event demultiplexing and handling, meaning that a synchronous i/o operation is issued only if there is a ready event and the operation would not block. As a matter of fact this is the way asynchronous operations are emulated in asio on platforms that don't support them directly. As has been already pointed out blocking i/o operations is a bad option for critical high performance servers since they waste thread resources. That is why you won't find them in the book - just take a look at the Half Sync/Half Async pattern and compare it to your implementation. As in general, you may use whatever approach you come up with as long as it suits your particular needs, but providing solutions that encourage best practices is what design patterns are for. And asio provides a solution based on one of such patterns (Proactor) that happens to be asynchronous.

"Eugene Alterman" <eugalt@verizon.net> wrote in message news:dnv3c8$eo2$1@sea.gmane.org...
I just want to point out (hopefully without getting off topic too much) that there is an important difference between what you mean by 'synchronous' and the way this term is usually used in this context (and in particular in the publication you are mentioning).
By 'synchronous' you seem to mean *blocking* synchronous i/o while in those patterns it is understood as synchronous i/o event demultiplexing and handling, meaning that a synchronous i/o operation is issued only if there is a ready event and the operation would not block.
This is not accurate of course :( Please disregard the whole thing - I just got carried away. :-)

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnpe9q$vk6$1@sea.gmane.org...
Let me disagree with this. There are cases when having multiple threads, each working in a synchronous way, is a better approach. First, it's much more intuitive, and easier to debug. Second, sometimes it's the only way to achieve you goal, such as whan you can't (or don't want to) rewrite your processing algorithm in asynchronous way.
And when such a case arrives, it would be nice to have a clean socket class, without build-in asynchronisity.
Hold on a second... Where has this perception that asio implements synchronous operations on top of asynchronous come from?
From what I can see in docs and sources this does not seem to be the case. Notice that demuxer::run() is not called in the tutorial synchronous samples.
Synchronous operations are simply implemented as regular blocking socket calls. I think Chris was misinterpreted when he tried to justify constructing a socket with demuxer even if it is only used synchronously.

"Arkadiy Vertleyb" <vertleyb@hotmail.com> wrote in message news:dnpe9q$vk6$1@sea.gmane.org...
Let me disagree with this. There are cases when having multiple
"Eugene Alterman" <eugalt@verizon.net> wrote threads,
each working in a synchronous way, is a better approach. First, it's much more intuitive, and easier to debug. Second, sometimes it's the only way to achieve you goal, such as whan you can't (or don't want to) rewrite your processing algorithm in asynchronous way.
And when such a case arrives, it would be nice to have a clean socket class, without build-in asynchronisity.
Hold on a second...
Where has this perception that asio implements synchronous operations on top of asynchronous come from?
From what I can see in docs and sources this does not seem to be the case. Notice that demuxer::run() is not called in the tutorial synchronous samples.
Synchronous operations are simply implemented as regular blocking socket calls.
This is good. Now what's left is to remove the dependency of the socket on the demuxer. Otherwise it's confusing: if demuxer is used, it has to be used for some purpose, right? Chris admitted that the socket class has built in features for asynchronous processing. This IMO is a conceptual overhead, and reminds of MFC's CWindow class, which is always an activeX container. Just in case. Regards, Arkadiy

Today (Dec 10th) is the start of the format review of the Asynchronous I/O library (asio) library by Christopher Kohlhoff. http://asio.sourceforge.net/
I would like to see asio in Boost. In the past I have looked on this library several times and the issues I found got resolved. The library is actively maintained and getting better. Generally, the documentation may be improved with more overcommented examples, with more detailed description of used concepts and patterns (at least external links), with diagrams showing dynamic behavior of the system, etc. /Pavel __________________________________________________ 1. Link to the glossary may be added on top line so it gets immediatelly visible to newcomers. __________________________________________________ 2. While having common core handling threads with Boost.Threads/a futures library/etc would be nice but it should not delay inclusion into Boost. __________________________________________________ 3. The existing implementation of futures over asio may be added as an example. __________________________________________________ 4. serialization example: the name "serialization" used for namespace is rather confusing. The word got overused. -------- The connection::async_write() may be named serialize_and_send_asynchronously() to distinguish it from asio's functionality. __________________________________________________ 5. Some parts of reference docs are too cryptic, e.g. reference\a00115.html says: Error_Source class: Detailed Description "Error source concept." That may be bit more verbose ;-) __________________________________________________ EOF

Hi Chris, Almost all my use cases for a network lib involve a lot of fan-out - ie branching based on the contents of a message, which gets implemented as client code deriving from an interface class such as struct ISomeCustomMessageSet { virtual void Custom1( Custom1Msg& msg ); virtual void Custom2( Custom2Msg& msg); virtual void Custom3( Custom3Msg& mgs); ... etc }; Now assuming i've written some code using asio that sits on the network receiving and parseing the relevant message i might connect to that component as follows // class that wants the messages class MessageRecipient : public ISomeCustomMessageSet { public: void Custom1( CustomMessage1& msg ) { /* do useful work */ } etc }; class MessageListenerOnAsio // listens and parses { void Register( ISomeCustomMessageSet* registered) { registered_ = registered; } .. listening/receiving using asio { // at some point need to call back into the registered_ interface registered_->Custom1( a_msg ); } } main() { // pseudo code MessageRecipient recipient; boost::asio::demuxer demux; MessageListenerOnAsio listener(demux ); listener.register( &recipient ); demux.run(); } All well and good, but what if i want to use the dispatcher concept for the callbacks. i.e. i'd like to be able to do the equivalent of boost::asio::some_dispatcher_class listener_dispatch; listener.register( listener_dispatch.wrap( recipient ) ); I know that's not correct asio - but what's a recommended way to achieve this without wrapping each method of the interface. Cheers Simon

Hi Simon, --- simon meiklejohn <simon@simonmeiklejohn.com> wrote:
Almost all my use cases for a network lib involve a lot of fan-out - ie branching based on the contents of a message, which gets implemented as client code deriving from an interface class such as <snip> I know that's not correct asio - but what's a recommended way to achieve this without wrapping each method of the interface.
I haven't really thought about it that deeply, but since you talk about a fan-out, cannot the dispatcher wrap be done on a function just before the fan-out occurs? I.e. a function that takes a complete message before a decision about what type it is has been made -- before the one becomes many? Cheers, Chris

Hi Chris
I know that's not correct asio - but what's a recommended way to achieve this without wrapping each method of the interface.
I haven't really thought about it that deeply, but since you talk about a fan-out, cannot the dispatcher wrap be done on a function just before the fan-out occurs? I.e. a function that takes a complete message before a decision about what type it is has been made -- before the one becomes many?
Yes, i believe thats necessary. Problem is - whats a clean way for the recipient of the fanned out callbacks to control the dispatcher policy. (see post about polymorphic_dispatcher for one approach). Cheers Simon

As you may have noticed i'm quite keen on a 'base class' for the Asio Dispatcher concept - for two reasons. 1. an easy path to inject custom dispatchers into a system. (clearly cant replace demuxer at will this way though) 2. a convenient type for passing dispatchers of any kind around the system, eg. to parameterise components with a dispatch policy without forcing the components to be templates themselves (presumably they would need to be if they had to store an instance of 'any' kind of dispatcher) Is it feasible to accomplish something like this with a class something like the following (but more correct hopefully) class polymorphic_dispatcher { public: template<typename T> polymorphic_dispatcher( T& non_poly_dispatcher ) { wrap_ = boost::bind( &T::wrap, non_poly_dispatcher ); post_ = boost::bind( &T::post, non_poly_dispatcher ); dispatch_ = boost::bind( &T::dispatch, non_poly_dispatcher ); } template<typename T> boost::function<void (void)> wrap( T func ) { return wrap_(func); } template<typename T> boost::function<void (void)> wrap( T func ) { return wrap_(func); } // dispatch likewise private: boost:::function<....> wrap_; // ellipsis cause i've no idea what should be in there boost:::function<....> post_; boost:::function<....> dispatch_; }; now then asio::locking_dispatcher disp1; asio::demuxer disp2; polymorphic_dispatcher disp3(disp1), disp4(disp2); Little tools like these (assuming feasible etc) would be great additions to asio. Cheers Simon

Hi Simon, --- simon meiklejohn <simon@simonmeiklejohn.com> wrote:
Is it feasible to accomplish something like this with a class something like the following (but more correct hopefully) <snip code>
Yes, I think it's feasible. It would probably be something more like: class abstract_dispatcher { public: virtual ~abstract_dispatcher(); virtual void post(function<void(void)> f) = 0; virtual void dispatch(function<void(void)> f) = 0; // Note: non-virtual template member function. Can reuse // asio::detail::wrapper_handler to implement it. template <typename Handler> unspecified wrap(Handler h); }; template <typename Dispatcher_Impl> class dispatcher : public abstract_dispatcher { public: // Forwarding constructors here ... void post(function<void(void)> f) { impl_.post(f); } void dispatch(function<void(void)> f) { impl_.dispatch(f); } private: Dispatcher_Impl impl_; }; And to use: // An abstract dispatcher that owns its implementation dispatcher<my_dispatcher> d(... args here ...); // An abstract dispatcher that doesn't own the implementation. demuxer d1; ... dispatcher<demuxer&> d2(d1); Cheers, Chris

Hi Chris Chris wrote
Yes, I think it's feasible. It would probably be something more like:
class abstract_dispatcher { [SNIP] };
template <typename Dispatcher_Impl> class dispatcher : public abstract_dispatcher { [SNIP] };
Perfect! I'd vote for including these in asio from the get go. abstract_dispatcher would be a good interoperability medium for future boost and user libraries built on top of asio. Cheers Simon

Hi to all, I've been playing with asio and here is my review. I've tried to see it through the eyes of embedded systems developer, so that we could, for example, use asio, in a router or gateway. ------------------------------------------------------- * Design: I like the overall design of the library, based on known C++ networking patterns. I also like the great use of boost::bind for callback functions and the idea of asio::buffer wrapper. The flexible error handling is in my opinion, a strong point. Optional exceptions should be common in C++ libraries, so that we can use them in real-time environments. But I think asio covers two aspects: asynchronous programming and network. I think both should be separated: --> boost::asio: a framework for asynchronous programming --> boost::net: a network library. I think both concepts are mixed now, and don't see how a developer can easily plug its asynchronous device (file, network, ipc, or other) in asio. Everyone can extend iostream to provide its own classes, but I don't see that explanation is asio, I suppose a user should implement asio concepts like asynchonous streams but a tutorial on this is a must. As some have mentioned, I think a separate boost::net library should contain synchronous (blocking) sockets that don't need a demuxer to work. I find the example of "Timer.1 - Using a timer synchronously" disturbing. I think boost::net should provide portable sockets (synchronous and asynchronous) that can be used (optionally) in asio framework. If both concepts are separated, boost::filesystem, could for example, be used with boost::asio to provide asynchronous file i/o (read, write, search, etc...) or this could be used also with inter-process communications (which are very similar to network communications) * Implementation Good overall. I've following the "Daytime.3 - An asynchronous TCP daytime server" example with the Visual 7.1 debugger and specially looking for mechanisms that hurt embedded systems. I've found some interesting things about memory allocations. I've commented the code with the number of calls to "operator new": int main() { //--> Two allocations constructing a shared_ptr before main enters try { //--> 4 calls to new inside the asio::demuxer constructor asio::demuxer demuxer; //--> 17 calls to new inside the asio::socket_acceptor constructor asio::socket_acceptor acceptor(demuxer, asio::ipv4::tcp::endpoint(13)); // -> 1 explicit new operator //--> 4 calls to new inside asio::stream_socket constructor asio::stream_socket* socket = new asio::stream_socket(demuxer); //--> 3 calls to new inside acceptor.async_accept(...) // -> 2 in the expression "if(impl == null())" to create // a socket_shared_ptr_type for null() // -> 1 to create new operation object. acceptor.async_accept(*socket, boost::bind(handle_accept, &acceptor, socket, asio::placeholders::error)); //--> 2 calls to new every new operation (accept or completion) // to create a new shared_ptr socket type for new connection demuxer.run(); } catch (asio::error& e) { std::cerr << e << std::endl; } return 0; } void handle_write(asio::stream_socket* socket, char* write_buf, const asio::error& /*error*/, size_t /*bytes_transferred*/) { using namespace std; // For free. free(write_buf); // -> 2 calls to new in the expression "if(impl == null())" // to destroy a socket // -> 2 calls to new when assigning impl = null() delete socket; } void handle_accept(asio::socket_acceptor* acceptor, asio::stream_socket* socket, const asio::error& error) { if (!error) { using namespace std; // For time_t, time, ctime, strdup and strlen. time_t now = time(0); char* write_buf = strdup(ctime(&now)); size_t write_length = strlen(write_buf); //2 news to create a new shared_ptr send_operation asio::async_write(*socket, asio::buffer(write_buf, write_length), boost::bind(handle_write, socket, write_buf, asio::placeholders::error, asio::placeholders::bytes_transferred)); // -> 1 explicit user new operator // -> 2 in the expression "if(impl == null())" to create // a socket_shared_ptr_type for null() socket = new asio::stream_socket(acceptor->demuxer()); // -> 2 calls to new in the expression "if(impl == null())" // to create a null socket_shared_ptr_type for null() acceptor->async_accept(*socket, boost::bind(handle_accept, acceptor, socket, asio::placeholders::error)); } else { delete socket; } } I find the number of allocations too big. I don't mind if the demuxer or the acceptor allocates memory dynamically because those objects have long lifetime. The user's explicit allocations are not a problem, because we can use a pool if we want to avoid fragmentation. But I find that repetitive operations (async_accept(), sync_send(), run()) make use of dynamic allocations, that I think can be problematic in high reliability systems. Some of them come to the fact that boost::shared_ptr is used widely in the library. For example, a socket is really containing a shared_ptr<unsigned int> and a new created socket (for example, when accepting a connection) needs 2 calls to new (one for the socket itself and another one for the shared_counter). This way, even the null socket is implemented with shared_ptr, so internal expressions like: if(impl == null()) create a temporary null socket that is a fully constructed shared_ptr<unsigned int>-> 2 allocations and 2 deallocations. I think that copying happily shared_ptr could be expensive because atomic operations are cheap but not free due to memory barriers (not maybe in asynchronous network operations because those have long latency, but maybe in other asynchronous ipc or asynchronous inter-thread communications). ---------------- It's clear that the use of runtime bind functions as callbacks, every asynchronous operation call needs to do a dynamic allocation to store a copy of the functor. Asynchronous Completion Tokens like windows' OVERLAPPED must be also allocated. asio allocates both in the same object and that's good, but there is no way to provide a custom allocator, so that the programmer can control and optimize memory use. I suggest to avoid implementing sockets as shared_ptr-s and giving the user the possibility to provide an allocator to allocate this per-asynchronous operation objects. This maybe is not easy, because each operation (accept, read, write, close, etc...) may require a different memory size, but maybe all could be unified in a single structure, (the structure would be dependent on <typename Async_Write_Stream, typename Handler> but the user knows the types that he uses, so maybe a internal type definition <typename Async_Write_Stream, typename Handler> struct async_context { typedef /* some_type */ type; }; could be used so that the user can create a pool of typename async_context<my_stream_t, my_handler_t>::type objects and asio could use it to allocate objects of that type. Just an idea. The goal would be that the user could avoid ANY repetitive dynamic allocation and build high reliability systems with asio, without fearing memory fragmentation issues. My review has been quite quick so maybe this is not possible because there are more subtle problems, but I would want to make asio ready to be used from routers to mainframes. * Documentation Nice. I think the tutorial is good, but I would add more complex examples using more classes. I think that it should contain a chapter explaining how asio can be extended to use other asynchronous devices. * Usefulness Extremely useful. C++ asynchronous programming and a general network implementation is a must. * Use I've used asio for some days compiling and debugging some tutorial examples in Visual 7.1. * Me I've done some asynchronous programming (although not with sockets) and I know some of its gotchas. Not an expert but a casual user of asynchronous programming * Summary I consider asio should be separated in two libraries: _boost::asio_ (as asynchronous programming framework) and _boost::net_ (providing socket and other network programming portable wrappers). I think that this second library should provide synchronous blocking (and non-blocking if possible) operations without the use of demuxers or other _boost::asio_ classes. I consider the documentation should contain a chapter explaining how you can extend asio to provide new asynchronous devices. I think the library should review a bit its memory allocation policy, and provide to the user the possibility to avoid _any_ repetitive memory allocation via customized memory allocation. This will make _boost::asio_ extremely portable. If all conditions are met, I vote STRONG YES to Boost inclusion because I think that asio is the best effort around and good basis for a Standard C++ network library. Regards, Ion

Hi Ion, Just some quick comments about the memory allocations: --- Ion Gaztañaga <igaztanaga@gmail.com> wrote: <snip>
Good overall. I've following the "Daytime.3 - An asynchronous TCP daytime server" example with the Visual 7.1 debugger and specially looking for mechanisms that hurt embedded systems. I've found some interesting things about memory allocations. I've commented the code with the number of calls to "operator new": <snip annotated code>.
I made a few quick changes and re-ran the daytime3 server to observe the memory allocations. What I changed was: - Created a class static member to be returned by the null() function. Returning a new object each time was a bit of a pessimisation, I admit :) - Explicitly initialised the services socket_acceptor_service and stream_socket_service. Normally these are initialised on the first use of a socket_acceptor or stream_socket respectively. I made this change to the example to make it clearer what the per-object cost is. - Only initialise the reactor implementation on the first use of asynchronous connect emulation (it's not used in this program). ------------------------------ int main() { try { //--> 4 calls to new. asio::demuxer demuxer; //--> 4 calls to new. demuxer.get_service(asio::service_factory< asio::socket_acceptor_service<> >()); //--> 2 calls to new. demuxer.get_service(asio::service_factory< asio::stream_socket_service<> >()); //--> 2 calls to new associated with socket in shared_ptr. asio::socket_acceptor acceptor(demuxer, asio::ipv4::tcp::endpoint(13)); //--> 1 call to new (the explicit new shown here). asio::stream_socket* socket = new asio::stream_socket(demuxer); //--> 1 call to new for OVERLAPPED-derived object. acceptor.async_accept(*socket, boost::bind(handle_accept, &acceptor, socket, asio::placeholders::error)); //--> 2 calls to new when accepting new connection and // underlying socket in shared_ptr is created. demuxer.run(); } catch (asio::error& e) { std::cerr << e << std::endl; } return 0; } void handle_accept(asio::socket_acceptor* acceptor, asio::stream_socket* socket, const asio::error& error) { if (!error) { using namespace std; // For time_t, time, ctime, strdup and strlen. time_t now = time(0); char* write_buf = strdup(ctime(&now)); size_t write_length = strlen(write_buf); //--> 1 call to new for OVERLAPPED-derived object. asio::async_write(*socket, asio::buffer(write_buf, write_length), boost::bind(handle_write, socket, write_buf, asio::placeholders::error, asio::placeholders::bytes_transferred)); //--> 1 call to new (the explicit new shown here). socket = new asio::stream_socket(acceptor->demuxer()); //--> 1 call to new for OVERLAPPED-derived object. acceptor->async_accept(*socket, boost::bind(handle_accept, acceptor, socket, asio::placeholders::error)); } else { delete socket; } } void handle_write(asio::stream_socket* socket, char* write_buf, const asio::error& /*error*/, size_t /*bytes_transferred*/) { using namespace std; // For free. free(write_buf); //--> 0 calls to new. delete socket; } ------------------------------ The shared_ptr wrapping the socket is a workaround for Windows not behaving as MSDN says it should with respect to operation cancellation. I'd be *very* happy to find another efficient way to correctly indicate operation cancellation without requiring the allocation. The intention was that the Allocator template parameter be used for custom memory allocation, much as it is in the standard library. However the current implementation doesn't use it. Christopher Baus has said that he is looking at another way to allow customisable allocation, and I look forward to seeing his proposal. Cheers, Chris

Hi Chris,
I made a few quick changes and re-ran the daytime3 server to observe the memory allocations. What I changed was:
<some code>
Looks good. Eliminating "null()" makes things much better. However the underlying shared_ptr<socket> is quite important. Until you find a better solution, you could try to use a simple segregated storage (boost::pool or similar) for socket allocation to minimize memory use (in 32 systems unsigned int has the same size as a pointer) and the user could define BOOST_SP_USE_QUICK_ALLOCATOR so that shared_ptr's shared_count objects are also pooled. This way, memory waste will be minimum and the allocation very fast. Now we just need a way to provide a custom allocation for async_xxx operations. I will follow Christopher Baus' proposal. Regards, Ion

Ion Gaztañaga wrote:
Hi Chris,
I made a few quick changes and re-ran the daytime3 server to observe the memory allocations. What I changed was:
<some code>
Looks good. Eliminating "null()" makes things much better. However the underlying shared_ptr<socket> is quite important. Until you find a better solution, you could try to use a simple segregated storage (boost::pool or similar) for socket allocation to minimize memory use (in 32 systems unsigned int has the same size as a pointer) and the user could define BOOST_SP_USE_QUICK_ALLOCATOR so that shared_ptr's shared_count objects are also pooled. This way, memory waste will be minimum and the allocation very fast.
If ::operator new isn't terribly broken (if it is, use dlmalloc), memory waste will aready be minimal and allocation would already be fast for small objects. Custom per-component allocators are rarely worth the trouble, and should never be the default.

If ::operator new isn't terribly broken (if it is, use dlmalloc), memory waste will aready be minimal and allocation would already be fast for small objects. Custom per-component allocators are rarely worth the trouble, and should never be the default.
In some experiments, I see that for example, in windows, the default allocator uses pools for small objects, but this is not true in all systems. For example, in many embedded operating systems (for example, QNX, eCos, Nucleus... and correct me if someone knows these OS better than me), there is no optimized new. In these systems, more than speed problems, you can have fragmentation problems. The fastest, is of course, avoiding dynamic allocation for small objects. If we need reference counting, the second one would be to use a intrusive pointer. Even in windows, you can test your own pool against "new" and you will see a (very) small speed gain. But if you don't need multi threaded allocation (some very possible with asio, since from just one thread you can control all async operations) you can avoid mutexes in the allocator, the speed gain is noticeable, because the allocation algorithm in a simple segregated storage is much faster than a mutex lock. If I were developing a router/rock stable embedded system using asio, I would prefer controlling the allocations myself, when I know that each repetitive operations leads to several calls to "new". Maybe a bit of paranoia, but not all "new" implementations are optimized and pooled for small allocations. I agree with you that this shouldn't be default, though. "new" should be default, but I suggest a mechanism to override this mechanism with user defined allocations. I would love to see that mechanism in shared_ptr for the counter, because for the object itself you can use a custom deleter. But this is another history. Regards, Ion

Ion Gaztañaga wrote:
If ::operator new isn't terribly broken (if it is, use dlmalloc), memory waste will aready be minimal and allocation would already be fast for small objects. Custom per-component allocators are rarely worth the trouble, and should never be the default.
In some experiments, I see that for example, in windows, the default allocator uses pools for small objects, but this is not true in all systems. For example, in many embedded operating systems (for example, QNX, eCos, Nucleus... and correct me if someone knows these OS better than me), there is no optimized new. In these systems, more than speed problems, you can have fragmentation problems.
[...]
Maybe a bit of paranoia, but not all "new" implementations are optimized and pooled for small allocations.
They aren't. My point is that the solution is to globally replace ::operator new or malloc with one that is, not make every component deal with the possibility in its own (incompatible and possibly inferior) way. BTW, Windows's allocator is far from being one of the best. Good general-purpose allocators match quick_allocator in the ST case and run circles around it in the MT case. The main reason that it was included is contrived "shared_ptr vs ..." benchmarks. For real applications, even dlmalloc is better. :-)
I agree with you that this shouldn't be default, though. "new" should be default, but I suggest a mechanism to override this mechanism with user defined allocations. I would love to see that mechanism in shared_ptr for the counter, because for the object itself you can use a custom deleter. But this is another history.
The CVS version of shared_ptr supports custom allocators.

Hi Ion, --- Ion Gaztañaga <igaztanaga@gmail.com> wrote:
Looks good. Eliminating "null()" makes things much better. However the underlying shared_ptr<socket> is quite important.
I have now also eliminated the allocation of the socket by adding a separate member to impl_type for storing the socket, and changing to use shared_ptr<void> with a no-op deleter object. The shared_ptr member of impl_type is now just used as a token to indicate cancellation. Thanks for pointing out these inefficiencies! :) Cheers, Chris

I have now also eliminated the allocation of the socket by adding a separate member to impl_type for storing the socket, and changing to use shared_ptr<void> with a no-op deleter object. The shared_ptr member of impl_type is now just used as a token to indicate cancellation.
Ummm, very clever move! There is no faster allocation than doing nothing. If you want to avoid the counter, you could use intrusive_ptr and the party is over. Regards and thanks for you quick replies, Ion

Ion Gaztañaga wrote:
I have now also eliminated the allocation of the socket by adding a separate member to impl_type for storing the socket, and changing to use shared_ptr<void> with a no-op deleter object. The shared_ptr member of impl_type is now just used as a token to indicate cancellation.
Ummm, very clever move! There is no faster allocation than doing nothing. If you want to avoid the counter, you could use intrusive_ptr and the party is over.
In case you decide to go with the intrusive_ptr, I've just put up a wrapper template, shared_proxy<T>, that attaches a reference count to a class. I wrote shared_proxy specifically to cut the overhead of shared_ptr in those cases where one controls the allocation of shared objects - you might as well go ahead and allocate the reference count with it. You can find the shared_proxy implementation here, http://tinyurl.com/8axpz I believe the implementation to be simple and readable, alas there are no docs for now :-( Use case, #include <breeze/shared_proxy.hpp> #include <boost/intrusive_ptr.hpp> ... typedef breeze::shared_proxy<socket> socket_proxy; typedef boost::intrusive_ptr<socket_proxy> socket_shared_ptr; socket_shared_ptr ptr = new socket_proxy(); // double indirection is required (**ptr).socket_member_function(); While I don't think this would look nice on the user visible interface, shared_proxy could be used in some places as an implementation detail. [ If shared_proxy (or something like it) is considered useful, maybe I could get away with hijacking partial specializations of intrusive_ptr, shared_ptr and weak_ptr to make its use transparent :-] HTH. Best regards, João Abecasis

Christopher Kohlhoff wrote:
The shared_ptr wrapping the socket is a workaround for Windows not behaving as MSDN says it should with respect to operation cancellation. I'd be *very* happy to find another efficient way to correctly indicate operation cancellation without requiring the allocation. May be you can use intrusive_ptr and Windows's own reference counter for handles (DuplicateHandle/CloseHandle)? This way you won't need to allocate counter for shared_ptr.
-- With respect, Alex Besogonov (cyberax@elewise.com)

Here's my review of asio. First, i think asio should be accepted into boost. I built and ran several examples, on VC8.0. They built noisily (an issue with the vc8.0 safe c++ library rather than asio) but worked fine. I also coded up some tests of my own, and found the facilities were generally easy to follow and complete. (A minor niggle with the API was the order of parameters in async operations - i.e. with flags preceding the Handler parameter, not allowing a default of 0 to be used for flags.) I also couldnt see whether QOS was settable, a feature in use in media streaming libraries at my workplace. The ability to use boost::bind to specify and compose handlers is terrific and offers great flexibility in use of the library. Tools for some standard handler compositions (such as splittig error handling paths from mainline paths) would be welcome. On the same lines, Chris's abstract_dispatcher class (not currently part of asio) would be useful as part of the base lib. Beyond that much of my review activity has focussed on evaluating the threading system. Given that the threading system primarily exists to dispatch i/o events back into application code i can't fault it from this perspective. It provides an easily understood scheme for providing thread(s) to the asio in which callbacks can occur. Comments indicate that the mechanism used to trigger the callback is very efficient. Perhaps this is at the cost of tight coupling between the demuxer threading facilities and other parts of the library, favouring high throughput apps rather than customisability. The locking_dispatcher in combination with the demuxer offers some great ways isolate areas of code, or groups of objects from eachother without having to do a lot of explicit thread protection. I like it a lot. The richness of behavour offered by the demuxer/locking_dispatcher/ boost::function/boost::bind combination really raises asio to the level of an application-wide framework for asynchronous programming, not just with i/o entities like sockets and files, but with any technology that a programmer might need to integrate into an app. Particularly what i mean here is that its desirable to use the same demuxer-controlled threads to do callbacks when events arise from those technologies (e.g. legacy libraries that insist on calling back in their own privately created thread). This would make available the same pseudo-single threaded programming model throughout the app. Another factor is that for many apps (win32 GUI being my favorite example) the programmer doesnt want callbacks on a thread calling demuxer::run(), but on some other special purpose thread that cannot be locked up in the demuxer. This can be achieved in the win32 gui case using something comparable to a locking_dispatcher that reflects the event into that thread by a windows message. That however breaks the guarantees of the dispatcher concept ('callbacks will occur in a thread in demuxer::run()') It may be an acceptable compromise, but perhaps the concept is too specific and tied to the implementation. Anyway, the upshot of all this is i think that longer term the event dispatcher system in asio should be generalised into another library upon which asio should depend. This library could be the basis of other asynch programming efforts unrelated to i/o. It could offer more general guarantees about what thread executes a callback, and allow wider customisation than asio. Thanks Chris for the excellent library, and responding to my somewhat noisy posting. Cheers Simon

Hi Simon, --- simon meiklejohn <simon@simonmeiklejohn.com> wrote:
I also coded up some tests of my own, and found the facilities were generally easy to follow and complete. (A minor niggle with the API was the order of parameters in async operations - i.e. with flags preceding the Handler parameter, not allowing a default of 0 to be used for flags.)
I do want to keep the callback handlers last for consistency, but I'll add an overload of these functions that leaves out the flags parameter.
I also couldnt see whether QOS was settable, a feature in use in media streaming libraries at my workplace.
Are the QoS settings configured using ioctl? My brief reading of MSDN would indicate so on Windows. If this is the case, then a new implementation of the IO_Control_Command concept could be added for this, which can then be passed to basic*socket::io_control. If you'd like to suggest what interface it needs (or, even better, create an implementation!) I'll look at adding it. BTW, is this an OS-specific thing? Cheers, Chris

From: "Christopher Kohlhoff" <chris@kohlhoff.com>
I do want to keep the callback handlers last for consistency, but I'll add an overload of these functions that leaves out the flags parameter.
Sounds good.
Are the QoS settings configured using ioctl? My brief reading of MSDN would indicate so on Windows. If this is the case, then a new implementation of the IO_Control_Command concept could be added for this, which can then be passed to basic*socket::io_control. If you'd like to suggest what interface it needs (or, even better, create an implementation!) I'll look at adding it. BTW, is this an OS-specific thing?
Not up on this myself, though i might be able to look through our code for some answers. Our implementation is purely win32 and is based around a separate socket class. Apparently uses some microsoft specific API, so perhaps not just ioctl. There seems to be a concept of negotiating/guaranteeing some data flow rate through the socket. I'm away from work for a couple of weeks from now, so i wont be able to look further into it till i get back. Any experts out there please feel free to jump in. Cheers Simon

Documentation The design documentation is very terse and makes some rather sweeping statements. At first, I thought a service was some rather under-documented concept, but in the reference section I discover that services appear to be a mixed bag of things, implementing some of the concepts listed. I really think the design documentation needs more depth and supporting rationale. The reference section needs extending with a more usage oriented section or sections, as well as documentation on customisation. The tutorials are inadequate. I would suggest at least a simple multi-session tcp/ssl echo server, and a tcp/ssl client able to be used interactively and for stress testing. Similarly for udp. Design I think the design as described by the concepts section is very good, but it would be better if the implementation offered finer-grained selection of the desired concepts. Perhaps this is just an aesthetic concern, and it would be easy to modify the existing services, but if these really are separate concepts, why are they bundled together? I'm concerned that there may be some lack of flexibility in the implementation/interface actually offered that isn't apparent from the design. The issue of separating out the sync vs async interface has already been raised; I would also like to see the ability to expose a reactive rather than proactive interface by using a reactive_stream_socket_service in place of an async_stream_socket_service. The concepts would seem to allow for this. One item that I have difficulty in discerning a clear design/rationale for is the demuxer. The demuxer as implemented is a repository of services (another wooly concept afaiks) and something that provides the run, interrupt and related functions to control the scheduling/dispatching of handlers in reaction to changes in Async_Object state. Is there anything preventing a more policy-based approach to assembling services? I would like to see the aspects of a socket that have nothing to do with reading/writing better separated, as this would make for a more consistent interface for other forms of I/O such as pipes, message queues etc as well as files. A very simple active object template that supports read and write, but that relies on free functions or helper classes for less universal operations such as bind, setting options, etc, used as a base class, allows appropriate (and necessary in my opinion) use of runtime polymorphism. Taken to the extreme, the base class is just an Async_Object - and doesn't even support read and write. I've used this general approach in an in-house library with some success, where an active_file_descriptor (basically asio's Async_Object concept) is created through some form of "opener" such as an acceptor or a connector. There is very little you can do with this object, except to construct a (enforced that there can only be 1 of each) read_active_file_descriptor or write_active_file_descriptor from it. This extends similarly for message oriented rather than stream-based "file descriptors". I should mention this is a reactor model - not a proactor, but I think the general ideas are still applicable. The read and write AFDs each carry a handler functor of course. The intention was for this general idea to support other types of "file descriptor" that don't offer conventional read and write interfaces, and to allow other interfaces (such as proactive) to use the same demuxing etc infrastructure. I had hoped to have time to investigate introducing asio as a replacement/alternative to this library, but the timing hasn't been gode for that. In any case, I don't think asio would be a good fit for a system like the following (where the other lib is being used): Components support tcp and ssl for WAN connections and unix domain sockets locally. Throw in some weird stuff like communicating over special purpose point-to-point links implemented as character devices. There are large chunks of code that have no need to care what exact type of "socket" they are using. Some of this code is actually in dynamically linked libraries itself. There are multiple processes using these libraries. Short of placing a wrapper around asio to make it more amenable to separate compilation and to introduce runtime polymorphism I can't see how I could use asio effectively this system, or anything similar. As far as I can see asio doesn't even allow interchangeable use of an ssl and a tcp stream. I think this is unacceptable in a general purpose library. I'm also concerned about the potential code bloat from unnecessary duplication of code (not from differnt template parameters, but from outright duplication in differnt templates). Implementation The code is clean, however I'm very concerned about the performance (or lack of) and weight that a combination of the implementation and some design decisions have introduced. The documentation mentions the memory usage overhead of the proactor emulation on systems using select/epoll. However it is clear that there is considerable allocation overhead and construction overhead involved (and not only on those systems). While much discussion has centered on allocator performance, I'm concerned that the user/library constructor code invoked for each i/o operation is likely to be non-trivial as well. For bulk transfers, it is likely that these issues won't be too severe, however for systems where low-latency handling of small messages is critical, it is quite possible that these overheads will be comparable to or larger than any other part of the processing for a message/transaction. I find the way the design supports, and the documentation encourages, breaking up handling of structured messages into a small header read followed by a larger read (with appropriate handler for expected body/chunk) while the implementation appears to do some quite heavyweight operations compared to just reading a small header to be almost an encouragement of an antipattern for efficient I/O. I would expect an optimistic reactive approach (read the header when notified, determine size, handler etc and opertunistically attempt a read of the body) to be significantly faster. It is implied that an appropriate demuxer/demuxer service can be used to implement various multi-threaded processing schemes. This may well be true. However, an alternative approach is to simply consider the affinity of an Async_Object and its demuxer(s) (one for read, one for write) to be dynamic and run a number of separate (per thread) demuxers, moving Async_Objects between them as required. In this way the handlers for a given Async_Object will only run in the expected context. I imagine other approaches are possible to achieve similar results, but it is not clear (to me) from the design documentation or a quick look at the code that this is the case. I haven't reviewed the code to a level of detail where I can be sure I am understanding the dispatching done by the epoll and select reactors, but it looks to me as though multiple threads may all wait on the same fdset (or equivalent)? Is this right? Intended? I had expected some form of modified leader/followers pattern here? Conclusion As I mentioned above, had the review been at a different time I would have at least attempted to port some code (even if only test/benchmark code) from an in-house reactor library to asio. As it is my comments are based on a fair bit of experience with network and async (in its most general sense) I/O from the bit-level (literally) up to the application level on a number of OSs/kernels/platforms and libraries (including ACE) and I've spent a few hours reading the code and docs. I have a healthy distrust of my (and everyone elses) guesses regarding performance, and the author and review manager should consider my points above regarding performance as areas for investigation only. A fair amount of the above appears to be a request for reactor support (which it is, partly) but I would consider mechanisms that support very light-weight proactor use highly desirable irrespective of reactor support. In particular, it would seem that some form of re-use of a single handler and buffer for an Async_Object instance without any allocation/(copy)construction of anything heavier than a pointer should be quite practical. That along with support for "socket type" runtime polymorphism would be the minimum set of changes I would consider to be necessary for acceptance (along with the inevitable better docs request). At this point, I vote no, but the library is very promising and the concepts generally well thought out. I would like to encourage Christopher to continue the already excellent work. Regards Darryl Green.

Hi Darryl, --- Darryl Green <darryl.green@unitab.com.au> wrote: <snip>
I think the design as described by the concepts section is very good, but it would be better if the implementation offered finer-grained selection of the desired concepts. Perhaps this is just an aesthetic concern, and it would be easy to modify the existing services, but if these really are separate concepts, why are they bundled together? I'm concerned that there may be some lack of flexibility in the implementation/interface actually offered that isn't apparent from the design. The issue of separating out the sync vs async interface has already been raised;
This has been extensively discussed elsewhere, but in summary I can see no compelling reason why there should be a separate low-level sync-only interface, and many advantages to having them combined. However I still intend to rename the demuxer to make its purpose clearer for all sorts of applications.
I would also like to see the ability to expose a reactive rather than proactive interface by using a reactive_stream_socket_service in place of an async_stream_socket_service. The concepts would seem to allow for this.
This is not consistent with the goal of portability. As I mentioned in the "reactor versus proactor" thread, the proactor is a more portable pattern than the reactor. In the longer term I'm not averse to allowing a reactor abstraction to become part of some secondary public interface, where the restricted portability is explicitly noted.
One item that I have difficulty in discerning a clear design/rationale for is the demuxer. The demuxer as implemented is a repository of services (another wooly concept afaiks) and something that provides the run, interrupt and related functions to control the scheduling/dispatching of handlers in reaction to changes in Async_Object state. Is there anything preventing a more policy-based approach to assembling services?
The demuxer is composed of services in a similar way to how std::locale is composed of facets. The demuxer is not necessarily aware of, or coupled to, the service types that it contains. For example, the SSL implementation uses a service that is only added in to the demuxer if SSL is used. The service used by a particular public interface (e.g. basic_stream_socket) is already a policy template parameter. One example program shows how basic_stream_socket can be instantiated with a custom service to perform logging, where this service is in turn implemented using the standard stream_socket_service. There's no reason why, in principle, policy-driven services could not be added to allow further customisation. But these would be inherently non-portable, and so would only be part of some secondary public interface.
I would like to see the aspects of a socket that have nothing to do with reading/writing better separated, as this would make for a more consistent interface for other forms of I/O such as pipes, message queues etc as well as files.
For stream-oriented I/O, this separation is presented in the form of the Stream concept. This concept is implemented by stream_socket, ssl::stream, buffered_read_stream and so on. It would be implemented by a hypothetical pipe class.
A very simple active object template that supports read and write, but that relies on free functions or helper classes for less universal operations such as bind, setting options, etc, used as a base class, allows appropriate (and necessary in my opinion) use of runtime polymorphism.
I have to disagree on runtime polymorphism: compile-time polymorphism should always be the default. Runtime polymorphism can impose additional costs (such as virtual function calls on a fine-grained interface), and eliminates potential optimisations. This adds up considerably if there are many layers of runtime polymorphism involved. Runtime polymorphism can be layered on top of compile-time polymorphism using some sort of wrapper. E.g. for the stream concept: class abstract_stream { ... }; template <typename Stream> class stream : public abstract_stream { ... }; stream<ssl::stream<stream_socket> > s(...); runtime_polymorphic_function(s); I am not certain if this is something that should be included in asio or not, since the appropriate place to introduce runtime polymorphism can vary according to the design of an application. What do you think? I'd be happy to add it if you think it generally useful. <snip>
The intention was for this general idea to support other types of "file descriptor" that don't offer conventional read and write interfaces, and to allow other interfaces (such as proactive) to use the same demuxing etc infrastructure.
I had hoped to have time to investigate introducing asio as a replacement/alternative to this library, but the timing hasn't been gode for that. In any case, I don't think asio would be a good fit for a system like the following (where the other lib is being used):
Components support tcp and ssl for WAN connections and unix domain sockets locally. Throw in some weird stuff like communicating over special purpose point-to-point links implemented as character devices. There are large chunks of code that have no need to care what exact type of "socket" they are using. Some of this code is actually in dynamically linked libraries itself. There are multiple processes using these libraries.
Short of placing a wrapper around asio to make it more amenable to separate compilation and to introduce runtime polymorphism I can't see how I could use asio effectively this system, or anything similar.
Would the polymorphic wrapper I proposed above address these issues?
As far as I can see asio doesn't even allow interchangeable use of an ssl and a tcp stream.
As I mentioned above, asio addresses this using compile time polymorphism, with the Stream concept being implemented by stream_socket and ssl::stream. For example, both work equally well with free functions like asio::async_read.
I think this is unacceptable in a general purpose library. I'm also concerned about the potential code bloat from unnecessary duplication of code (not from differnt template parameters, but from outright duplication in differnt templates).
Can you please point these out? I'm always in favour of reducing code bloat.
Implementation
The code is clean, however I'm very concerned about the performance (or lack of) and weight that a combination of the implementation and some design decisions have introduced. The documentation mentions the memory usage overhead of the proactor emulation on systems using select/epoll. However it is clear that there is considerable allocation overhead and construction overhead involved (and not only on those systems). While much discussion has centered on allocator performance, I'm concerned that the user/library constructor code invoked for each i/o operation is likely to be non-trivial as well. For bulk transfers, it is likely that these issues won't be too severe, however for systems where low-latency handling of small messages is critical, it is quite possible that these overheads will be comparable to or larger than any other part of the processing for a message/transaction.
I don't deny that there may be room for performance improvement in the implementation. But as I recently tested the custom allocation with Win32 I/O completion ports, and found that it performs almost identically to synchronous I/O, I don't think the public interface imposes any unnecessary inefficiencies.
I find the way the design supports, and the documentation encourages, breaking up handling of structured messages into a small header read followed by a larger read (with appropriate handler for expected body/chunk) while the implementation appears to do some quite heavyweight operations compared to just reading a small header to be almost an encouragement of an antipattern for efficient I/O.
I don't agree that asio favours this approach over others. But I do believe that this approach is perfectly valid and extremely useful, since it permits asynchronous code to be written in terms of "contracts". I.e. perform this operation (or operations) and don't come back to me until it's all done or an error has occurred. For code where absolute efficiency is not required, this technique allows a clearer and more elegant expression of intent. Furthermore, the cost of multiple small read system calls can be mitigated by leveraging the compile-time polymorphism of the Stream concept and replacing: stream_socket my_socket; with: buffered_read_stream<stream_socket> my_socket;
I would expect an optimistic reactive approach (read the header when notified, determine size, handler etc and opertunistically attempt a read of the body) to be significantly faster.
Let's take your example of a fixed sized header followed by a body, and compare and contrast a reactive approach with the alternatives offered by asio. --------------------- 1. Reactive. Here you might have a single handler that is informed when a socket is ready for reading. In this handler you perform non-blocking reads. void handle_read(...) { switch (state_) { case reading_header: // Attempt non-blocking read of header. size_t bytes_read = read(sock_, header_buf_ + header_bytes_so_far_, header_length - header_bytes_so_far_); header_bytes_so_far_ += bytes_read; // Check if we have complete header. if (header_bytes_so_far_ < header_length) return; // Keep waiting for more. // Got the whole header now. body_length_ = decode_body_length(header_buf_); body_buf_ = ...; // size body buffer appropriately body_bytes_so_far_ = 0; state_ = reading_body; // Fall through to make opportunistic read of body. case reading_body: // Attempt non-blocking read of body. bytes_read = read(sock_, body_buf_ + body_bytes_so_far_, body_length_ - body_bytes_so_far_); body_bytes_so_far_ += bytes_read; // Check if we have complete body. if (body_bytes_so_far_ < body_length_) return; // Keep waiting for more. // Got whole body now. notify_app(...); // Start over. state_ = reading_header; } } 2. Asio with multiple reads. This is the approach where we issue asynchronous reads for exactly the length of the header, and then the body. It may be less efficient, but only because of the additional pass through the demultiplexer, not because it makes more read() system calls than the above. However feel free to compare in terms of readability :) The main reason it's clearer is that you no longer have to deal with short reads -- async_read's contract takes care of that for you. void start_read() { // Issue read for entire header. async_read(sock_, buffer(header_buf_, header_length), handle_read_header); } void handle_read_header(const error& e) { if (!e) { // Got header, determine body length. body_length_ = decode_body_length(header_buf_); body_buf_ = ...; // size body buffer appropriately // Issue read for entire body. async_read(sock_, buffer(body_buf_, body_length_), handle_read_body); } } void handle_read_body(const error& e) { if (!e) { // Got whole body now. notify_app(...); // Start over. start_read(); } } 3. Asio with multiple reads + opportunistic body read. This extends number 2 by adding in the opportunistic read of the body. In effect this makes it equivalent to your reactive read approach, but I think you'll still find it's easier to follow -- again because most of the code to handle short reads is eliminated. void start_read() { // Issue read for entire header. async_read(sock_, buffer(header_buf_, header_length), handle_read_header); } void handle_read_header(const error& e) { if (!e) { // Got header, determine body length. body_length_ = decode_body_length(header_buf_); body_buf_ = ...; // size body buffer appropriately // Make opportunistic non-blocking read of body. error read_error; size_t bytes_read = sock_.read_some( buffer(body_buf_, body_length_), assign_error(read_error)); // Did we get whole body? if (bytes_read == body_length_ || read_error) { handle_read_body(read_error); } else { // Issue read for rest of body. async_read(sock_, buffer(body_buf_ + bytes_read, body_length_ - bytes_read), handle_read_body); } } } void handle_read_body(const error& e) { if (!e) { // Got whole body now. notify_app(...); // Start over. start_read(); } } 4. Asio with bulk reads. This approach is exemplified by the HTTP server example. Essentially it reads as much as is available (up to some limit of course) in one go, and then processes it using some state machine similar to that in number 1. It is generally the most efficient since it requires the fewest number of read system calls or passes through the demuxer. void handle_read(const error& e, size_t bytes_read) { if (!e) { process_data(buffer_, bytes_read); async_read(sock_, buffer(buffer_, buffer_length), handle_read); } } 5. Asio with transfer_at_least(...). In certain applications you may be able to combine the opportunistic read into the initial asynchronous read operation, by using the transfer_at_least() completion condition. This executes the operation with a contract that it transfer a minimum number of bytes. In this way you can avoid short read handling for the header, while still maximising the number of bytes transferred in a single operation. void start_read() { // Issue read for entire header, and maybe we'll get some // body as a bonus. boost::array<mutable_buffer, 2> bufs = { buffer(header_buf_, header_length), buffer(body_buf, max_body_length) }; async_read(sock_, bufs, transfer_at_least(header_length), handle_read_header); } ... ---------------------
It is implied that an appropriate demuxer/demuxer service can be used to implement various multi-threaded processing schemes. This may well be true. However, an alternative approach is to simply consider the affinity of an Async_Object and its demuxer(s) (one for read, one for write) to be dynamic and run a number of separate (per thread) demuxers, moving Async_Objects between them as required. In this way the handlers for a given Async_Object will only run in the expected context.
This is not portable. With Win32 I/O completion ports for example, once a socket is associated with an I/O completion port it cannot be disassociated. The demuxer design reflects this.
I imagine other approaches are possible to achieve similar results, but it is not clear (to me) from the design documentation or a quick look at the code that this is the case. I haven't reviewed the code to a level of detail where I can be sure I am understanding the dispatching done by the epoll and select reactors, but it looks to me as though multiple threads may all wait on the same fdset (or equivalent)? Is this right? Intended? I had expected some form of modified leader/followers pattern here?
No, only one thread in the pool waits on select/epoll/kqueue. Any other threads in the pool wait on a condition.
Conclusion
As I mentioned above, had the review been at a different time I would have at least attempted to port some code (even if only test/benchmark code) from an in-house reactor library to asio. As it is my comments are based on a fair bit of experience with network and async (in its most general sense) I/O from the bit-level (literally) up to the application level on a number of OSs/kernels/platforms and libraries (including ACE) and I've spent a few hours reading the code and docs. I have a healthy distrust of my (and everyone elses) guesses regarding performance, and the author and review manager should consider my points above regarding performance as areas for investigation only.
A fair amount of the above appears to be a request for reactor support (which it is, partly) but I would consider mechanisms that support very light-weight proactor use highly desirable irrespective of reactor support. In particular, it would seem that some form of re-use of a single handler and buffer for an Async_Object instance without any allocation/(copy)construction of anything heavier than a pointer should be quite practical.
Asio's interface already supports "lightweight", reusable handlers, since ultimately a handler is just a function object. If you wish, you can implement your handlers so that they are no more expensive than a pointer: class my_handler { public: explicit my_handler(my_class* p) : p_(p) {} void operator()(const asio::error& e) { p_->handle_event(e); } private: my_class* p_; }; <snip> Thanks for taking the time to write such an extensive review. Cheers, Chris

Hi Chris, I think the main issue I have with the library implementation and interface really comes down to a different perspective on portability and extensibility. Perfect portability would be great (well, maybe not, if nothing else it would be boring :-) but in the absence of that, interoperability and ease of porting are better. Also porting of libraries, porting of applications and porting of entire systems are different things, as I'm sure you are aware. The trade offs involved in any given "port" may be quite different - some service providing app that needs to provide services over some form of channel *should* make relatively few demands on the channel but the actual channel(s) it uses on a particular platform, occupying a particular place in a particular larger system may be quite different from those it uses in a different deployment environment. Ideally the set of channels supported should be able to be easily selected/extended at compile and/or runtime (ie. in some cases tuning/compiling for a very specific environment is necessary and in some cases very broad interoperability at runtime is necessary). With that perspective in mind maybe I'll make slightly more sense? Christopher Kohlhoff wrote:
This is not consistent with the goal of portability. As I mentioned in the "reactor versus proactor" thread, the proactor is a more portable pattern than the reactor. In the longer term I'm not averse to allowing a reactor abstraction to become part of some secondary public interface, where the restricted portability is explicitly noted.
I see no reason why a reactive interface can't be implemented on top of an async completion notification based system - in fact this is rather common within the OS itself, or as an optional layer/lib in an embedded kernel/rtos. All you need is a receive buffer managed by the lib that notifies when it goes non-empty (edge) or while non-empty (level) - obviously there are a variety of trade offs about buffer size/number of pending buffers etc to consider. Transmit is similar - claim to be ready for transmit while there are less than N lib managed requests not yet completed.
There's no reason why, in principle, policy-driven services could not be added to allow further customisation. But these would be inherently non-portable, and so would only be part of some secondary public interface.
I disagree that all of these would be non-portable. I'm not sure what you mean by "secondary public interface" but if it is a public interface I'm happy - I just don't want all policy hidden in detail or worse yet rejected from the lib altogether because of a lack of portability. This is because I need *portability* but not in the same sense/at the same level we are talking about here. Perhaps "adaptability" would be a better term for what I am after.
I would like to see the aspects of a socket that have nothing to do with reading/writing better separated, as this would make for a more consistent interface for other forms of I/O such as pipes, message queues etc as well as files.
For stream-oriented I/O, this separation is presented in the form of the Stream concept. This concept is implemented by stream_socket, ssl::stream, buffered_read_stream and so on. It would be implemented by a hypothetical pipe class.
Yes - I didn't describe my reasoning very well. What I was trying to suggest was that you already have implementations of: Pure notifiers (timers) - that implement the Async_Object concept Streams (stream_socket) that implement the Async_Object and Async_Read/Write_Stream concepts. Messages (datagram_socket) - there isn't really a conceptual model for these above the Async_Object level - maybe there should be. While the send/receive interfaces look similar to the socket stream interface parameter-wise, the semantics of messaging are of course different (even without considering the send_to/receive_from bundling of message and endpoint). I would like to see: 1) Driver services that bind a concept (or a minimal set of concepts) to a particular underlying, possibly platform dependent facility. These clearly won't in general be portable. 2) Policy-based composed services bringing together (composition may be reflected at runtime ie. there would be multiple "service references" per active object or at compile time ie. an actual, useful service is built from policies) "drivers" from (1) to produce useful services. I would expect the service to be at least conceptually portable, even though there might be some platforms for which a particular service cannot be composed from the supplied drivers. 3) Default/standard/portable services using/implemented in terms of (1) and (2) (ie. standardizing the policies used). Concrete examples of 1: A driver for Async_Read_Stream on posix-esq systems that uses a reactive model and any file descriptor. A protocol (actually, I'm not sure if that is too much of a stretch for the existing concept - it crosses over into ACE acceptor/connector concepts, though I'd be inclined not to follow those exactly either) for posix named pipes or anon pipe - read end or unix domain sockets. Example of 2: generic async readable stream - this needs not just the policy for Async_read_Stream but also a compatible protocol, etc) to allow the channel to be created, connected etc. By selecting polices can produce stream-socket or char device I/O for example. Examples of 3: The current stream_socket_service, a local IPC messaging service using unix domain sockets on some platforms, windows named pipes on others, posix message queues on others - default selection based on service availability. The effects of this are: On posix-esq platforms, just about anything/everything is a "file descriptor" that a select/poll/epoll call can be used on (interesting omission is actually conventional disk files) and most of these are streams, so it is possible to provide async streams that will work with pipes/char devices at least, once you figure out how to c'truct/open them, presumably through providing a protocol for opening things that works on (char devices), a protocol for opening named pipes (fifo's) and a protocol for dealing with anon pipes/inherited fds etc in general. On windows overlapped I/O is similarly widely applicable, with analogous issues re handling creating and connecting to particular "special" files. I would expect to see some abstract local IPC channel policies that are portable (but in general only if both ends of the channel use asio and compatible policies) that simply promise to offer eg. a duplex message oriented channel or a simplex stream (pipe-like). While these can be sensibly defaulted on particular platforms for interoperability reasons it should be possible to eg. use a very portable shared memory IPC message passing impl or (more likely to interoperate with processes not using asio, or more stable across asio version updates or...) an appropriate platform facility such as posix message queues. I would like to see fine-grained composition from policies similar to the concepts just to make it easier to maintain and extend the lib (or maybe more the other way around ;-). None of the above implies any major change of design, rather it takes advantage of the design to offer adaptability and portability without limiting either. I think it would be unfortunate if forms of IO that are not 100% portable were excluded from "first class" status in the library. Encouraging the utilization of the design's strengths by making it easy to re-use and extend the library can only encourage its acceptance and wider use and development. This would perhaps require some refactoring of the services and documenting/exposing some of the detail, but not much else. What do you think? Regards Darryl Green. -- No virus found in this outgoing message. Checked by AVG Free Edition. Version: 7.1.371 / Virus Database: 267.14.7/214 - Release Date: 23/12/2005

Hi Darryl, --- Darryl Green <darryl.green@myrealbox.com> wrote: <snip>
I would like to see fine-grained composition from policies similar to the concepts just to make it easier to maintain and extend the lib (or maybe more the other way around ;-).
None of the above implies any major change of design, rather it takes advantage of the design to offer adaptability and portability without limiting either. I think it would be unfortunate if forms of IO that are not 100% portable were excluded from "first class" status in the library. Encouraging the utilization of the design's strengths by making it easy to re-use and extend the library can only encourage its acceptance and wider use and development. This would perhaps require some refactoring of the services and documenting/exposing some of the detail, but not much else.
What do you think?
I do appreciate where you're coming from, but I think we must have fundamentally different design philosophies in mind. To summarise my understanding of it, you want to be able to leverage commonality of implementation on the platforms that you target, and compose policies to influence how the abstractions, such as stream_socket, behave. With asio, the public interface instead says nothing about commonality of implementation, since such commonality cannot be portably assumed. The implementation detail can make use of such commonality, certainly. For the user of asio, commonality is provided through implementation of concepts, such as the Stream concept implemented by stream_socket and ssl::stream. I am not averse to compile time or runtime tweaks to alter implementation behaviour. Or, dare I say, completely different implementations of the public interface that provide alternative QoS characteristics. But ultimately I am not in favour of the sort of framework that allows endlessly customisable behaviour via policy composition for a library such as this. Of the programmers developing networking code, I imagine that very few have both the skills and inclination to make use of this level of customisation in source code. Besides, the more combinations, the greater the chance of something going wrong, and the less time I can spend testing each one! The number of permutations allowed by such a policy-driven interface would be a barrier to what I believe is a more important goal: the development of portable, reusable, higher-level abstractions. Cheers, Chris

Christopher Kohlhoff wrote:
I do appreciate where you're coming from, but I think we must have fundamentally different design philosophies in mind.
To summarise my understanding of it, you want to be able to leverage commonality of implementation on the platforms that you target, and compose policies to influence how the abstractions, such as stream_socket, behave.
I don't want to change the behavior of anything. I want to map some concepts into policies, properly define them and have lots of impls all with the same behavior. I see that you didn't respond to any of my specific examples of other I/O channels or the breakdown of "what I would like to see". Do you intend to implement every form of I/O, including message-based and stream-based IPC, disk, character devices and the ever-growing plethora of networking protocols (I see someone else has mentioned SCTP already) that can be done asynchronously? I realize that it would be unusual for anyone to need all of them, but someone needs each of them - I was hoping that the library could be opened up to those someones to extend without creating a maintenance nightmare.
With asio, the public interface instead says nothing about commonality of implementation, since such commonality cannot be portably assumed.
Fine. I'm not suggesting you can or should require it - I am suggesting that if the design was tweaked slightly, it would be possible for the user/extender (or the maintainer/author) to re-use impl when it can be re-used.
The implementation detail can make use of such commonality, certainly. For the user of asio, commonality is provided through implementation of concepts, such as the Stream concept implemented by stream_socket and ssl::stream.
Yes. I want to make public impls of the concepts in a more flexible way.
I am not averse to compile time or runtime tweaks to alter implementation behaviour. Or, dare I say, completely different implementations of the public interface that provide alternative QoS characteristics.
Huh? I'm utterly confused. I guess you must be too - sorry I'm obviously doing such an atrocious job of describing what I'm after. I split up my replies to try to focus the discussion better and avoid confusion but obviously I've failed.
But ultimately I am not in favour of the sort of framework that allows endlessly customisable behaviour via policy composition for a library such as this. Of the programmers developing networking code, I imagine that very few have both the skills and inclination to make use of this level of customisation in
If the library just magically supports every form of IO on a platform out of the box, with the choice of I/O mechanism being largely transparent to higher-level code, then there will be no need for customization. I don't think that is a realistic requirement.
source code. Besides, the more combinations, the greater the chance of something going wrong, and the less time I can spend testing each one!
The testing and maintenance angle is one of my major motivations for proposing this. If you have umpteen different forms of I/O all of which use the same readiness notification mechanism and require the same processing to actually do an async read/write why would you want to implement and maintain and test umpteen separate user accessible services for read/write rather than testing that your shared read/write impl in depth and the aspects of those various forms of I/O that are unique such as the way that the channel is opened separately? I'm guessing that you wouldn't actually chose to duplicate code and you would use shared code in detail to avoid it. I'm suggesting that with care the shared code can align with concepts/policies and, once it is (meaning it has a defined interface and semantics) it can be made public. Thats all. If nothing else this makes the testing scale better without having tests rely on non-public interfaces. The other aspect of testing is that nobody can be expected to test code that isn't in the lib - if a user extends the library and either doesn't contribute the code or it isn't accepted into the lib there is nothing extra to test? I'm not asking for a lot of weird mix-and-match policies - just a few to compose the sorts of services that the lib should (imho) support anyway, but in a user configurable way.
The number of permutations allowed by such a policy-driven interface would be a barrier to what I believe is a more important goal: the development of portable, reusable, higher-level abstractions.
I see the ability to adapt the library while preserving a consistent interface to be a necessity to support higher-level abstractions. Regards Darryl. -- No virus found in this outgoing message. Checked by AVG Free Edition. Version: 7.1.371 / Virus Database: 267.14.9/217 - Release Date: 30/12/2005

Hi Darryl,
If the library just magically supports every form of IO on a platform out of the box, with the choice of I/O mechanism being largely transparent to higher-level code, then there will be no need for customization. I don't think that is a realistic requirement.
Here I think you have identified precisely what I intend asio to be in the longer term. It is what I mean when I say that a goal of asio is to take a toolkit, rather than framework, approach. Asio is intended for the development of applications that need to use the I/O services of their target platforms in an efficient and scalable way, without: - having to be aware of the details (or even the existence) of demultiplexing - needing to worry about platform differences. Asio is a codification of experience with demultiplexing and low-level networking APIs. It should provide an interface that requires none of this knowledge. I think it is realistic and, more importantly, widely beneficial for this experience to be captured and presented as a library. It is unrealistic to expect most projects to require their developers to learn all of these I/O mechanisms to the level of detail required to use them effectively.
I'm suggesting that with care the shared code can align with concepts/policies and, once it is (meaning it has a defined interface and semantics) it can be made public. Thats all.
So what it seems you want (and I hope I am now understanding you correctly) is access to asio's implementation details through a public interface, so that you may use them to develop your own I/O mechanisms. Let's review what I see as asio's level of abstraction: high level networking application frameworks and protocol abstractions \ | / \ | / \|/ asio toolkit /|\ / | \ / | \ platform-specific and restricted portability APIs and demultiplexing methods I have said on previous occasions that I would be happy to see asio's implementation details evolve into a secondary public interface. I recently realised that it would be more appropriate as a separate library. This library would package I/O mechanisms at the lower level of abstraction. It would be targeted at developers who require the lower level of abstraction. Asio could potentially be implemented in terms of this library, but this library is not asio. I believe it is important for a library to have all its public interfaces at the same level of abstraction. This better defines the intended scope and uses of the library. To have a library encompass multiple levels of abstraction would lead to tension in trying to support different audiences with differing needs. By making current implementation details part of the library's interface contract, you either restrict the ability to refactor the implementation, or risk creating orphaned interface elements. During the review, some people indicated a desire for lower level APIs, just as others requested higher level networking abstractions. There is nothing wrong with asking for a level of abstraction that asio does not provide. Naturally, it is not sensible to say that a library provides the wrong level of abstraction. The abstraction level is only wrong in the context of a use case. If an application requires a different level of abstraction, then asio may not be the appropriate tool.
I'm not asking for a lot of weird mix-and-match policies - just a few to compose the sorts of services that the lib should (imho) support anyway, but in a user configurable way.
But if your ultimate need is an implementation of specific I/O services (for example, pipes or UNIX domain sockets), why must they be implemented outside of asio? I agree they should be supported, so please, contribute them to the toolkit or assist in their development. Others will benefit from your expertise, just as reciprocally you may benefit from the expertise of others as captured in asio. Cheers, Chris

Christopher Kohlhoff wrote:
Let's review what I see as asio's level of abstraction:
high level networking application frameworks and protocol abstractions \ | / \ | / \|/ asio toolkit /|\ / | \ / | \ platform-specific and restricted portability APIs and demultiplexing methods
I have said on previous occasions that I would be happy to see asio's implementation details evolve into a secondary public interface. I recently realised that it would be more appropriate as a separate library. This library would package I/O mechanisms at the lower level of abstraction. It would be targeted at developers who require the lower level of abstraction.
Asio could potentially be implemented in terms of this library, but this library is not asio.
Hum, I think that asio should guarantee to be *implemented* in terms of this library. Let's say that this library provides a reactor, i would want that my code and asio use the same instance of that reactor. And any way, it would be useful to use asio callback dispatching with custom strems.
I believe it is important for a library to have all its public interfaces at the same level of abstraction. This better defines the intended scope and uses of the library.
I do not agree, in fact it is useful if a library provides two diferent intefaces: one for end users and one for library developpers/extenders.
To have a library encompass multiple levels of abstraction would lead to tension in trying to support different audiences with differing needs. By making current implementation details part of the library's interface contract, you either restrict the ability to refactor the implementation, or risk creating orphaned interface elements.
Well, we are not asking to make the implementation public right now. This could be done at a latter stage, when the implementation is stable enough.
During the review, some people indicated a desire for lower level APIs, just as others requested higher level networking abstractions. There is nothing wrong with asking for a level of abstraction that asio does not provide. Naturally, it is not sensible to say that a library provides the wrong level of abstraction. The abstraction level is only wrong in the context of a use case. If an application requires a different level of abstraction, then asio may not be the appropriate tool.
You are certanly right. Asio does very well what it guarantees it does. But it would be a pity, if it you didn't extend it a little to do what it could do with a very small amount of work (this sentence doen't make much sense. What i mean is that just by exporting the detail interface asio would become immensely more useful)
But if your ultimate need is an implementation of specific I/O services (for example, pipes or UNIX domain sockets), why must they be implemented outside of asio?
Well, may be because they don't make sense outside a specific application, and still benefit from the internal detail of asio. Reimplementing this details would be a wasteful duplication of code. And even if a service is not that specific, i don't think that asio can realistically be a repository of all possible I/O types. In the end, the final decision is up to you. HTH --- Giovanni P. Deretta

Christopher Kohlhoff wrote:
One item that I have difficulty in discerning a clear design/rationale for is the demuxer. The demuxer as implemented is a repository of services (another wooly concept afaiks) and something that provides the run, interrupt and related functions to control the scheduling/dispatching of handlers in reaction to changes in Async_Object state. Is there anything preventing a more policy-based approach to assembling services?
The demuxer is composed of services in a similar way to how std::locale is composed of facets.
That tells me that there services are objects identified by type that can be obtained from the thing that holds them (locale in iostreams, demuxer in asio) by type. Which is - um - about what I knew already....
The demuxer is not necessarily aware of, or coupled to, the service types that it contains. For example, the SSL implementation uses a service that is only added in to the demuxer if SSL is used.
I gathered that much. But tell me why/what for! Alternatively I'll try to figure it out and write the docs - you can tell me if I get them right... [pause to read code in more detail] Ok - there are a number of service categories and some services depend on other services. The stream_socket_service impl may, for example, be a reactive_socket_service. The reactive_socket_service ultimately needs a demuxer and a reactor, so it gets the reactor (itself a service) from the demuxer via get_service. It is documented that (unlike facets of a locale) the services are constructed on use. This means that there is no way (?) that a custom replacement service can be provided via the demuxer, as the actual construction of the service is done by the service_factory based on the type of service requested from the demuxer. Hence, to customize the service, it has to have a different type from any existing service, and the user of the service has to be modified to use the new type. There is a possibility that some services are used by multiple components (eg the demuxer and the socket) so care is needed in doing this customization. Basically about as unlike a local and facets as you can get? I think this is what threw me to start with. Did I get that right? Having been completely mislead by the docs, I think the real model is something like this: Having obtained the "locale" in the form of the demultiplexer the "socket" can look up services that it needs by type. Thats fine, I supposed initially that this meant a socket would look up something that actually was a concrete implementation of a documented concept, but I found that it looked up a blob-o-stuff documented as a service. As it happens that blob-o-stuff may depend on detail impl that uses detail services, some of which look suspiciously like they do implement the concepts. I'm left feeling that aside from impl detail this is all of no significance except that service instances are 1:1 with demuxer instances so it is a demuxer extension mechanism that is handy if a socket policy needs to associate state with a demuxer not a socket instance. As a special case the demuxing itself is accessed as a service. In the case of policies that are associated with (or include a component associated/implementing) the socket, variations on an idiom of using a service::null function to get a null impl (ptr) and some form of create/open etc to c'truct the actual impl is used. What I was asking for (vaguely) was that the services be more facet-like so that when customizing a socket-like class it can be assembled from appropriate facets/concepts. This might mean pulling some of the detail out of detail (leaving the platform dependent parts there). Alternatively composite services could be assembled (as appears to be the case in detail already) from their basic concepts. In any case, I think a better description of what the service concept is all about (possibly more than 1 concept here - the core service access, services that use the demuxer and demuxer services) are needed if this is to be usable as an accessible customization point of the library.
The service used by a particular public interface (e.g. basic_stream_socket) is already a policy template parameter. One example program shows how basic_stream_socket can be instantiated with a custom service to perform logging, where this service is in turn implemented using the standard stream_socket_service.
I've had a look at that now. Is there some particularly profound reason why logging implemented to fit in with the service idiom is a good thing/example? It seems to achieve nothing special, though it goes through some hoops to create a demuxer (private to the logging service) to perform work in a background thread? Is that supposed to illustrate something? I think this relates back to the need for better docs/concepts in this area. The replacement stream_socket_service is a copy of the original's interface with logging calls added. It wraps (has a reference to) an implementation that is the original asio::stream_socket_service. I agree this illustrates some of the techniques I was asking about should be practical within the design, which addresses some of my concerns. I'm happy (unless you can tell me where I've got it completely wrong!) I understand this area well enough for now. I would want see a much better level of docs and examples of this area in a post-review version of the lib. Regards Darryl Green. -- No virus found in this outgoing message. Checked by AVG Free Edition. Version: 7.1.371 / Virus Database: 267.14.7/214 - Release Date: 23/12/2005

[asio] demuxer and services One thing the documentation does not really make clear is the relationship between the demuxer and the services. I think the documentation should probably try to clarify their general relationship. Possibly most of this is implementation detail, but a rough grasp of it might help use the library effectively in large systems, and would probably be required when thinking about customizing the library. Some of the structural objections raised in the review might be more manageable if it were clearer how customization might work. One particular point of confusion, I suspect, is the demuxer's demuxer_service. I think the library (or maybe just the naming and the doc) hasn't quite made up it's mind as to whether the demuxer is just a collection of services or actually provides a demultiplexing service, or both: On the one hand, TCP, timer and other services are "pay as you go" - they are added at run-time as they are used, and from the demuxer's point of view they are just members in a collection of services. Additionally, the demuxer itself performs almost no work (right?), but instead forwards member function calls to services. On the other hand, demuxer always creates a "demuxer_service" that exists in the service collection as a regular service, but also as a "distinguished" service in that the demuxer has a direct pointer to it and the demuxer forwards its member functions to that pointer. This demuxer_service has real responsibility: it implements asynchronous callbacks. So demuxer has 2 responsibilities: 1) collect and provide access to services. 2) create and provide access to the asynchronous callback service. ...correct, more or less? Chris, I can spend some hours on doc in the next couple weeks if that's helpful. I'm not sure I know enough about asio or networking to actually write the doc but I'm happy to try or to review or edit. Just let me know. Here's an attempt to describe the relationship between the demuxer and the services. --------------------------------------- demuxer: demuxer is the entry point of the asio library. A demuxer object is required to use asio. A reference to a demuxer object is passed to all asio functions that perform io. The demuxer class itself does very little. Instead demuxer "forwards" requests to its collection of "services". The demuxer always creates a "demuxer_service" service to implement asynchronous callbacks. Other services are created at run-time when they are first needed. Service collection + creation 1) demuxer's collection of services is a linked list, with one entry of each requested service type. 2) A service can be requested through demuxer::get_service<ServiceType>, which will find an existing service by type or add one if it is not found. 3) Asio code that needs a service automatically calls demuxer::get_service<TheServiceTypeINeed>, finding or adding the service to the demuxer. The user does not need to explicitly call get_service. 3.1) Incidentally, this sounds sort of like a list of boost::any, right?. The list and the "any" feature are implemented by hand for efficiency? demuxer's demuxer_service 4) demuxer constructor creates a demuxer_service service. 5) demuxer_service creates a platform-implementation demuxer_service (right?) 6) The role of the demuxer_service is to implement asynchronous callbacks (right?) 7) demuxer forwards its member functions to demuxer_service. 8) Although a loop is required in which to do the callbacks, the demuxer_service does not start a thread for that loop. Instead, user is given full control over this loop and must call demuxer::run() to execute it. 9) The demuxer_service is a "distinguished" service: it exists in the list of services as a service that can be retrieved with demuxer::get_service, but demuxer also has a direct pointer to it and uses it in a special way. 10) Any demuxer has two services after construction: the demuxer_service and the platform-implementation of the demuxer_service. 11) On Windows XP, the demuxer_service creates a win_iocp_demuxer_service, which allocates a windows IoCompletionPort (but does not start any threads). --------------------------------------- Services: Services factor functionality provided by demuxer into more manageable pieces. (right?) They isolate code and allow resources to be loaded at run-time when they are needed. 1) High-level services "forward" functionality to lower-level services, selected by platform. (right?) 2) Low-level and high-level services exist together in demuxer's linked list, and can all be retrieved with get_service<ServiceType>. 3) Services can use each other. Multiple high-level services can use the same low-level service, and services can use "cousin" services. 4) Actual user-level objects such as timers and sockets use a service, and it is probably through the instantiation of these objects that the service is created in the first place. 5) In general, services maintain a pointer to their "implementation" service, the demuxer maintains a pointer to the demuxer_service, and user-level objects (e.g. deadline_time and stream_socket) maintain a pointer to their service. Some services also have a pointer "up" to the demuxer, or "across" to another service. (Dunno if I happened to see any service pointing up to another service, but don't see any reason it couldn't happen). --------------------------------------- Services example: 4) Example of a Windows program using: *asynchronous timer callbacks, *synchronous socket i/o, *one demuxer shared between timer and socket code. In this example, the demuxer is created first (it has to be), then a stream_socket is created, then an async_timer is created, finally demuxer::run is called. 4.1) demuxer::demuxer() creates: demuxer_service creates: win_iocp_demuxer_service The demuxer now has 2 services: demuxer_service win_iocp_demuxer_service No additional threads are running. The win_iocp_demuxer_service has allocated a Windows IoCompletionPort. 4.2) stream_socket::stream_socket(demuxer) creates: stream_socket_service creates: win_iocp_socket_service creates: select_reactor<true> (a service) The demuxer now has 5 services: demuxer_service win_iocp_demuxer_service stream_socket_service win_iocp_socket_service select_reactor<true> The select_reactor service has started a thread. The behavior of this thread depends on whether any async winsock calls are made (right?) If none are made the thread will call Sleep(till next timer expires); otherwise it will call select(with timeout when next timer expires). Synchronous socket calls can now be made. They do no use the select_reactor thread (right?). 4.3) deadline_timer::deadline_timer(demuxer) creates: deadline_timer_service creates: reactive_deadline_timer_service finds already existing: select_reactor<true> The demuxer now has 7 services: demuxer_service win_iocp_demuxer_service stream_socket_service win_iocp_socket_service select_reactor<true> deadline_timer_service reactive_deadline_timer_service No additional threads are running. However, in order to use asynchronous timer callbacks, demuxer::run must be called in some thread. 4.4) demuxer::run() is called in WinMain. There are now two threads running: select_reactor thread executes select_reactor which loops on Sleep(till next timer expires) main thread executes demuxer::run which loops on GetQueuedCompletionStatus and executes timer completion handlers.

Andrew Schweitzer wrote: [...]
demuxer's demuxer_service [...] 5) demuxer_service creates a platform-implementation demuxer_service (right?)
Not as far as I know. This is one of the points that some people have contention with. The real relation between demuxer_service and the implementation is closer to a PIMPL idiom, but without the pointer. In other words it's equivalent to: demuxer_service *is-a* detail::win_iocp_demuxer_service And... demuxer_service *is-a* detail::task_demuxer_service<detail::epoll_reactor<false> > And... demuxer_service *is-a* detail::task_demuxer_service<detail::kqueue_reactor<false> > And... demuxer_service *is-a* detail::task_demuxer_service<detail::select_reactor<false> > -- -- Grafik - Don't Assume Anything -- Redshift Software, Inc. - http://redshift-software.com -- rrivera/acm.org - grafik/redshift-software.com -- 102708583/icq - grafikrobot/aim - Grafik/jabber.org

Andrew Schweitzer wrote:
5) demuxer_service creates a platform-implementation demuxer_service (right?)
Rene Rivera wrote:
Not as far as I know. This is one of the points that some people have contention with. The real relation between demuxer_service and the implementation is closer to a PIMPL idiom, but without the pointer. In other words it's equivalent to:
demuxer_service *is-a* detail::win_iocp_demuxer_service
[snip] I think I'm confused. As far as I can tell, there's no inheritance relationship between demuxer_service and win_iocp_demuxer_service, but as you said, it's a PIMPL relation, or perhaps an RIMPL relation since it's a reference not a pointer. Is PIMPL considered is-a or has-a? I think the library design or naming might be a bit confused on this point (or quite possibly it's me). As far as I can tell two things are true: 1) the demuxer ends up with two actual services in its list after it is constructed: a) demuxer_service b) win_iocp_demuxer_service (or the selected platform-implemenation of demuxer_service) 2) the demuxer also has a PIMPL relation with demuxer_service, and demuxer_service has a PIMPL relation with win_iocp_demuxer_service Are you reading the code differently? I might have gotten lost.

Andrew Schweitzer wrote:
Andrew Schweitzer wrote:
5) demuxer_service creates a platform-implementation demuxer_service (right?)
Rene Rivera wrote:
Not as far as I know. This is one of the points that some people have contention with. The real relation between demuxer_service and the implementation is closer to a PIMPL idiom, but without the pointer. In other words it's equivalent to:
demuxer_service *is-a* detail::win_iocp_demuxer_service
[snip]
I think I'm confused. As far as I can tell, there's no inheritance relationship between demuxer_service and win_iocp_demuxer_service, but as you said, it's a PIMPL relation, or perhaps an RIMPL relation since it's a reference not a pointer.
We can consider the reference as just a different kind of pointer in this case. After all it could just as easily be a pointer and the forwarding functions would deref the pointer, instead of the compiler doing the deref for you.
Is PIMPL considered is-a or has-a?
I've always considered it an is-a. A very special case, but equivalent.
I think the library design or naming might be a bit confused on this point (or quite possibly it's me).
The fact that it's confusing, even when looking at the code, is a point against the design ;-) OK, I'm rereading the code, instead of relying on memory...
As far as I can tell two things are true: 1) the demuxer ends up with two actual services in its list after it is constructed: a) demuxer_service b) win_iocp_demuxer_service (or the selected platform-implemenation of demuxer_service)
OK, AFAICT it only ends up with (b) in the service_registry.
2) the demuxer also has a PIMPL relation with demuxer_service, and demuxer_service has a PIMPL relation with win_iocp_demuxer_service
Yes. I guess the key point here is that there's only 1 (singleton) of win_iocp_demuxer_service for all demuxer_service instances in the same basic_demuxer instance? (again AFAICT)
Are you reading the code differently? I might have gotten lost.
I guess, I'm reading it differently :-) -- -- Grafik - Don't Assume Anything -- Redshift Software, Inc. - http://redshift-software.com -- rrivera/acm.org - grafik/redshift-software.com -- 102708583/icq - grafikrobot/aim - Grafik/jabber.org

Rene Rivera wrote: [snip]
Andrew Schweitzer wrote:
I think the library design or naming might be a bit confused on this point (or quite possibly it's me).
The fact that it's confusing, even when looking at the code, is a point against the design ;-) OK, I'm rereading the code, instead of relying on memory...
Yes, although I think I'd say maybe it's not a design issue but just that the documentation isn't as complete as the code.
As far as I can tell two things are true: 1) the demuxer ends up with two actual services in its list after it is constructed: a) demuxer_service b) win_iocp_demuxer_service (or the selected platform-implemenation of demuxer_service)
OK, AFAICT it only ends up with (b) in the service_registry.
Ok, time for debugger. [runs debugger] I think it's a and b. VC7.1 debugger appears to show that service_registry_.first_service_ points at a demuxer_service, and the demuxer_service's node's next_ member points at win_iocp_demuxer_service. I think here's how the code works: 1) demuxer::demuxer calls get_service<demuxer_service> 2) get_service finds nothing in the service_registry_, so it creates a new demuxer_service. 3) demuxer_service::demuxer_service calls get_service<win_iocp_demuxer_service> 4) get_service doesn't find a win_iocp_demuxer_service in service_registry_, so it creates a new one, and adds it. It returns. 5) demuxer_service::demuxer_service returns 6) the first call to get_service now adds the new demuxer_service to the list.
2) the demuxer also has a PIMPL relation with demuxer_service, and demuxer_service has a PIMPL relation with win_iocp_demuxer_service
Yes. I guess the key point here is that there's only 1 (singleton) of win_iocp_demuxer_service for all demuxer_service instances in the same basic_demuxer instance? (again AFAICT)
Yes I think there's only one win_iocp_demuxer_service, within that particular instance of the demuxer. I think get_servive<ServiceType> guarantees there's only one list member of a given ServiceType, by using the language's typeid operator.

Hi Andrew, --- Andrew Schweitzer <a.schweitzer.grps@gmail.com> wrote: <snip>
Chris, I can spend some hours on doc in the next couple weeks if that's helpful. I'm not sure I know enough about asio or networking to actually write the doc but I'm happy to try or to review or edit. Just let me know.
Some of this stuff is likely to change post review so I'd be afraid that any docs would become out of date before they'd been written! More examples are always welcome, if you have time to spend on them :)
Here's an attempt to describe the relationship between the demuxer and the services. <snip>
Thanks for this stuff. It will make a good start for some design notes on these core classes. I will also turn some of what you wrote, particularly the example, into diagrams to show how the implementation hangs together. Thanks! Cheers, Chris

More notes/examples. Let me know if anything needs to be refined. In particular, are there other examples that would be useful, off the top of your head? --------------Services on Windows----------------- This is a complete (?) list of creatable on Windows NT 4.0 and above. **Services are created at run-time when a user-level object (e.g. a stream_socket) that needs them is created. **Services in this list with the same name will be re-used if they already exist. So, for example, if user creates a socket_acceptor then a stream_socket, the socket_acceptor will add the win_iocp_socket_service and the select_reactor<true> services, and then the stream_socket will find and re-use them. **The first item in each hierarchy is the user-level object whose instantiation creates the hierarchy. The reset of the items are services. demuxer demuxer_service win_iocp_demuxer_service stream_socket stream_socket_service win_iocp_socket_service select_reactor<true> datagram_socket datagram_socket_service win_iocp_socket_service select_reactor<true> acceptor_socket socket_acceptor_service win_iocp_socket_service select_reactor<true> deadline_timer deadline_timer_service reactive_deadline_timer_service select_reactor<true> locking_dispatcher locking_dispatcher_service [with one set of template parameters] locking_dispatcher_service [with another set of template parameters] (??) host_resolver host_resolver_service [with one set of template parameters] host_resolver_service [with another set of template parameters] (??) ssl::context context_service openssl_context_service ssl::stream stream_service openssl_stream_service --------------Platform differences---------------- Platform: Uses: linux 2.5.45 and above epoll Mac kqueue Windows NT 4.0 and above IoCompletionPort Everything else select In general specialized services look like this: Layer Class 0 user-level object 1 asio-level service 2 general-concept-implementation service 3 platform-implementation service In general specialized services look like this: Layer Example class 0 stream_socket 1 stream_socket_service 2 reactive_socket_service 3 epoll_reactor **For the demuxer_service and the socket services, WinNT 4.0 doesn't use the general-concept-implementation service but intstead has it's own "win_iocp-conecpt-implementation" classes. **All three socket services share the same conecept-implementation and platform-implementation-services. **Not all services have platform-specific implementations. locking_dispatcher, host_resolver and the ssl services context and stream are the same on all platforms. Comparison of specialized servives on Windows and Linux: SHARED NT LINUX 0 demuxer 1 demuxer_service 2 win_iocp_demuxer_service task_demuxer_service 3 [none] epoll_reactor<false> 0 stream_socket 1 stream_socket_service 2 win_iocp_socket_service reactive_socket_service 3 select_reactor<true> epoll_reactor<false> 0 datagram_socket 1 datagram_socket_service 2 win_iocp_socket_service reactive_socket_service 3 select_reactor<true> epoll_reactor<false> 0 acceptor_socket 1 socket_acceptor_service 2 win_iocp_socket_service reactive_socket_service 3 select_reactor<true> epoll_reactor<false> 0 deadline_timer 1 deadline_timer_service 2 reactive_deadline_timer_service 3 select_reactor<true> epoll_reactor<false> --------------------------Example------------------ Example of a Windows program using: *asynchronous timer callbacks, *asynchronous socket i/o, *tcp and udp *serving, via stream_socketasync_accept *one demuxer shared between timer and socket code. *no additional threads beyond main() and threads created by library. In this example, the demuxer is created first (it has to be), then a stream_socket is created, then an async_timer is created, finally demuxer::run is called. 1) demuxer::demuxer() creates: demuxer_service creates: win_iocp_demuxer_service The demuxer now has 2 services: demuxer_service win_iocp_demuxer_service No additional threads are running. The win_iocp_demuxer_service has allocated a Windows IoCompletionPort. 2) stream_socket::stream_socket(demuxer) creates: stream_socket_service creates: win_iocp_socket_service creates: select_reactor<true> (a service) The demuxer now has 5 services: demuxer_service win_iocp_demuxer_service stream_socket_service win_iocp_socket_service select_reactor<true> The select_reactor service has started a thread. The behavior of this thread depends on whether any async winsock calls are made If none are made the thread will call Sleep(till next timer expires); otherwise it will call select(with timeout when next timer expires). When this thread has nothing to do it will call select and wait to be interrupted. 3) datagram_socket::datagram_socket(demuxer) creates: datagram_socket_servivice finds already existing: win_iocp_socket_service The demuxer now has 6 services: demuxer_service win_iocp_demuxer_service stream_socket_service win_iocp_socket_service select_reactor<true> datagram_socket_service 4) acceptor_socket::acceptor_socket(demuxer) creates: socket_acceptor_service finds already existing: win_iocp_socket_service The demuxer now has 7 services: demuxer_service win_iocp_demuxer_service stream_socket_service win_iocp_socket_service select_reactor<true> datagram_socket_service socket_acceptor_service 5) deadline_timer::deadline_timer(demuxer) creates: deadline_timer_service creates: reactive_deadline_timer_service finds already existing: select_reactor<true> The demuxer now has 8 services: demuxer_service win_iocp_demuxer_service stream_socket_service win_iocp_socket_service select_reactor<true> deadline_timer_service reactive_deadline_timer_service No additional threads are running. However, in order to use asynchronous callbacks, demuxer::run must be called in some thread. 6) demuxer::run() is called in WinMain. There are now two threads running: select_reactor thread executes select_reactor which loops on Sleep(till next timer expires) main thread executes demuxer::run which loops on GetQueuedCompletionStatus and executes completion handlers.

--- Darryl Green <darryl.green@myrealbox.com> wrote: <snip>
It is documented that (unlike facets of a locale) the services are constructed on use. This means that there is no way (?) that a custom replacement service can be provided via the demuxer, as the actual construction of the service is done by the service_factory based on the type of service requested from the demuxer.
Service construction can be customised by specialising the service_factory template, and then calling get_service e.g.: demuxer.get_service(service_factory<my_service>(...)); The "standard" services in the public interface are not intended for customisation in this way. However this facility can be used for user-defined services. Note: this service_factory template was introduced originally to allow the get_service member template to work with MSVC6.
Hence, to customize the service, it has to have a different type from any existing service, and the user of the service has to be modified to use the new type. There is a possibility that some services are used by multiple components (eg the demuxer and the socket) so care is needed in doing this customization. Basically about as unlike a local and facets as you can get? I think this is what threw me to start with.
Did I get that right?
The thing that makes a facet in a locale replaceable by a derived facet implementation is that the facet itself has overrideable virtual functions. It is not an attribute of the locale object, as far as I know, that determines whether a facet is replaceable. Yes it's true that the services in asio's public interface are not replaceable, as these services do not have virtual functions for reasons discussed extensively elsewhere. :) However other services could be added by the user which do. If you don't feel the comparison to locale is useful, that's fine, as it was just intended as an aid to understanding the design. I guess when I drew the comparison (which I still think is apt) I was thinking of different aspects of the way locale works. <snip>
I've had a look at that now. Is there some particularly profound reason why logging implemented to fit in with the service idiom is a good thing/example?
It aims to demonstrate how socket operations can be customised, in this case to add debugging information. Obviously the log itself is shared between all socket instances.
It seems to achieve nothing special, though it goes through some hoops to create a demuxer (private to the logging service) to perform work in a background thread? Is that supposed to illustrate something? I think this relates back to the need for better docs/concepts in this area.
The demuxer is being used as a thread-safe work queue. I agree this could be explained better in the example. Cheers, Chris

Christopher Kohlhoff wrote:
I would like to see the aspects of a socket that have nothing to do with reading/writing better separated, as this would make for a more consistent interface for other forms of I/O such as pipes, message queues etc as well as files.
For stream-oriented I/O, this separation is presented in the form of the Stream concept. This concept is implemented by stream_socket, ssl::stream, buffered_read_stream and so on. It would be implemented by a hypothetical pipe class.
Precisely. Why write it/maintain it again?
A very simple active object template that supports read and write, but that relies on free functions or helper classes for less universal operations such as bind, setting options, etc, used as a base class, allows appropriate (and necessary in my opinion) use of runtime polymorphism.
I have to disagree on runtime polymorphism: compile-time polymorphism should always be the default.
A tad dogmatic. Would you like to qualify "always" at all? What do you mean by "default" - is the default selectable at design/implementation or compilation time?
Runtime polymorphism can impose additional costs (such as virtual function calls on a fine-grained interface), and eliminates potential optimisations. This adds up considerably if there are many layers of runtime polymorphism involved.
Can we cut the re-education program for java programmers :-) out of this and deal with the use case constructively, or would you prefer to ignore it? I qualified my concerns about performance by saying they lacked actual benchmark results, however I have now seen posts from others backing my concerns (between the time I spent responding and gmane being my only interface at the time I hadn't seen Caleb's results when I posted). You are now expressing concern about virtual function overhead etc with no measurements to back it up and no qualification of when they are "bad". In the development of the library I referred to in my post I have measured negligible performance impact from runtime polymorphism. A double indirection is unlikely to be significant compared to the other operations being performed. Users expect to be able to use eg. an iostream of some sort without having to template every bit of code that uses it on what sort of stream it is.
Runtime polymorphism can be layered on top of compile-time polymorphism using some sort of wrapper.
Yes (see below).
I am not certain if this is something that should be included in asio or not, since the appropriate place to introduce runtime polymorphism can vary according to the design of an application.
The appropriate place to introduce compile time polymorphism can vary too...
What do you think? I'd be happy to add it if you think it generally useful.
I do think it is generally useful.
Short of placing a wrapper around asio to make it more ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Yes to the following - but there is an aspect of code re-use that it doesn't address.
Would the polymorphic wrapper I proposed above address these issues?
As far as I can see asio doesn't even allow interchangeable use of an ssl and a tcp stream. As I mentioned above, asio addresses this using compile time polymorphism, with the Stream concept being implemented by stream_socket and ssl::stream. For example, both work equally well with free
template !
functions like asio::async_read.
I'm also concerned about the potential code bloat from unnecessary duplication of code (not from differnt template parameters, but from outright duplication in differnt templates).
Can you please point these out? I'm always in favour of reducing code bloat.
I'm referring to the potential for bloat if eg a pipe service duplicates much of the socket interface. I know openssl is quite weird enough to be a special case all of its own, but I would expect many systems would be able to share a lot of implementation across different stream-like file/socket/device interfaces at least. Regards Darryl Green -- No virus found in this outgoing message. Checked by AVG Free Edition. Version: 7.1.371 / Virus Database: 267.14.7/214 - Release Date: 23/12/2005

Hi Darryl, --- Darryl Green <darryl.green@myrealbox.com> wrote:
Christopher Kohlhoff wrote:
For stream-oriented I/O, this separation is presented in the form of the Stream concept. This concept is implemented by stream_socket, ssl::stream, buffered_read_stream and so on. It would be implemented by a hypothetical pipe class.
Precisely. Why write it/maintain it again?
Well... because although the interface might be the same the implementation is different.
A very simple active object template that supports read and write, but that relies on free functions or helper classes for less universal operations such as bind, setting options, etc, used as a base class, allows appropriate (and necessary in my opinion) use of runtime polymorphism.
I have to disagree on runtime polymorphism: compile-time polymorphism should always be the default.
A tad dogmatic. Would you like to qualify "always" at all?
I was referring to operations such as read, write, etc. I thought that was clear from the context. If not, my apologies.
What do you mean by "default" - is the default selectable at design/implementation or compilation time?
By "default" I mean that the interface should be designed to use compile time polymorphism with concepts. Runtime polymorphism can be introduced, should the developer choose, through a wrapper.
Runtime polymorphism can impose additional costs (such as virtual function calls on a fine-grained interface), and eliminates potential optimisations. This adds up considerably if there are many layers of runtime polymorphism involved.
Can we cut the re-education program for java programmers :-) out of this and deal with the use case constructively, or would you prefer to ignore it?
My design choice is based on experience in developing real-world high-performance networking applications that are often CPU bound. A previous system I worked on made extensive use of runtime polymorphism via the ACE_Streams abstraction, where passing data through each layer involves a virtual function call. Since configuration of the individual layers did not need to occur at runtime, this runtime polymorphism was unnecessary. Removing it markedly improved performance.
I qualified my concerns about performance by saying they lacked actual benchmark results, however I have now seen posts from others backing my concerns (between the time I spent responding and gmane being my only interface at the time I hadn't seen Caleb's results when I posted).
I do not believe that particular test is representative of typical use, as it only involves a single socket on a demuxer receiving data from another socket in the same process. Also as I have indicated elsewhere I have since made some optimisations that have significantly reduced costs.
You are now expressing concern about virtual function overhead etc with no measurements to back it up and no qualification of when they are "bad". In the development of the library I referred to in my post I have measured negligible performance impact from runtime polymorphism. A double indirection is unlikely to be significant compared to the other operations being performed.
Double indirection is not the only cost of runtime polymorphism, as you are no doubt aware. Virtual functions can also result in a loss of locality-of-reference, causing increased cache misses and possibly page faults in memory-constrained systems, and they eliminate the possibility of inlining. Furthermore, the custom allocation optimisation is possible because type information has not been erased, which would have occurred if the interface used virtual functions. Do I have the benchmarks on hand to support this? No, but I have done these tests in the past. Working on high performance systems has taught me that runtime polymorphism has very real costs, and so in general should not be introduced unless runtime variation of behaviour is required. The design of asio reflects this experience. <snip>
What do you think? I'd be happy to add it if you think it generally useful.
I do think it is generally useful.
Short of placing a wrapper around asio to make it more ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Yes to the following - but there is an aspect of code re-use that it doesn't address.
Which aspect would this be?
As far as I can see asio doesn't even allow interchangeable use of an ssl and a tcp stream. As I mentioned above, asio addresses this using compile time polymorphism, with the Stream concept being implemented by stream_socket and ssl::stream. For example, both work equally well with free
template !
I guess this means that you like templates about as much as I like virtual functions. :)
I'm referring to the potential for bloat if eg a pipe service duplicates much of the socket interface. I know openssl is quite weird enough to be a special case all of its own, but I would expect many systems would be able to share a lot of implementation across different stream-like file/socket/device interfaces at least.
I don't think there would be as much sharing as you might think. And even in cases where there is some sharing, the common functionality can be extracted into a function or class to be called from each place, so I don't see how this design necessarily leads to bloat. Fundamentally, asio's public interface cannot assume sharing, since that assumption is already known to be incorrect. Cheers, Chris P.S. Still reading your other replies. I hope to respond to them soon.

Christopher Kohlhoff wrote:
Hi Darryl,
Christopher Kohlhoff wrote:
For stream-oriented I/O, this separation is presented in the form of the Stream concept. This concept is implemented by stream_socket, ssl::stream, buffered_read_stream and so on. It would be implemented by a hypothetical pipe class.
Precisely. Why write it/maintain it again?
Well... because although the interface might be the same the implementation is different.
But in many cases it isn't. That was my point.
What do you mean by "default" - is the default selectable at design/implementation or compilation time?
By "default" I mean that the interface should be designed to use compile time polymorphism with concepts. Runtime polymorphism can be introduced, should the developer choose, through a wrapper.
Ok.
This adds up considerably if there are many layers of runtime polymorphism involved.
Can we cut the re-education program for java programmers :-) out of this and deal with the use case constructively, or would you prefer to ignore it?
My design choice is based on experience in developing real-world high-performance networking applications that are often CPU bound.
A previous system I worked on made extensive use of runtime polymorphism via the ACE_Streams abstraction, where passing data through each layer involves a virtual function call.
I don't recall suggesting building ACE. If anyone had I would have screamed loudly. I didn't ask for a traditional OO layered polymorphic monster - I just suggested that it was such a common use case to want to read/write to anything that supported read/write concepts that this should be supported. It isn't exactly unprecedented.
Since configuration of the individual layers did not need to occur at runtime, this runtime polymorphism was unnecessary. Removing it markedly improved performance.
Yep. Sounds familiar.
responding and gmane being my only interface at the time I hadn't seen Caleb's results when I posted).
I do not believe that particular test is representative of
Ok. Lets leave the optimization for a separate discussion based on tests etc.
You are now expressing concern about virtual function overhead
Double indirection is not the only cost of runtime polymorphism, [snip others] Do I have the benchmarks on hand to support this? No, but I have done these tests in the past. Working on high performance systems has taught me that runtime polymorphism has very real costs, and so in general should not be introduced unless runtime variation of behaviour is required. The design of asio reflects this experience.
I don't disagree with your concerns here, though it seems from my perspective that runtime polymorphism is being singled out as the root of all evil while other impl details can be at least as significant. Note I am only asking for runtime polymorphism to the extent that it is needed to support basic operations (read/write) not for some monstrous framework. My experience in memory + CPU constrained high performance systems using ACE led me to delete it and replace it with an in-house lib with rather selective (like 1) use of runtime polymorphism. YMMV. I'm done.
Short of placing a wrapper around asio to make it more
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Yes to the following - but there is an aspect of code re-use that it doesn't address.
Which aspect would this be?
I meant wrapping N identical impls in a polymorphic wrapper to make them interchangeable didn't improve code re-use, thats all.
stream_socket and ssl::stream. For example, both work equally well with free
template !
I guess this means that you like templates about as much as I like virtual functions. :)
You must be kidding. I've been writing code that VC6 can't compile since VC6 was released :-). C++ has a lot of features - I try to use them appropriately. The "template!" was in the context of my concern that I didn't want to have to template every piece of code that did any reading/writing using the lib - a template function doesn't help in that.
I would expect many systems would be able to share a lot of implementation across different stream-like file/socket/device interfaces at least.
I don't think there would be as much sharing as you might think
I'm only basing this on my experience - note I'm not expecting a huge difference in code size etc - I'm more interested in reliability and maintainability.
And even in cases where there is some sharing, the common functionality can be extracted into a function or class to be called from each place, so I don't see how this design necessarily leads to bloat.
Ok. Bloat wasn't fair - its not a huge amount of code, and the code can be extracted as you say. In one of my other posts I suggest doing that by making lower level policy/concept composition available for library extension etc.
Fundamentally, asio's public interface cannot assume sharing, since that assumption is already known to be incorrect.
Yes - thats where you need runtime polymorphism :-) If we can step back from the technique a little and take a look at usage, what I would like to see is some object that provides enough of the interface to allow stream read/write independent of the underlying stream. I would like a user that only required stream read to be able to use a read only stream such as the read end of a pipe. I would like that checking to occur at compile time. Similarly for write. While it is always possible to wrap a number of underlying objects with similar interfaces to introduce polymorphism, these conceptual base classes can't be injected as easily. This crosses over into the area of service composition as discussed in my reply re portability (and a little in demuxer). It would probably be more productive to let this specific thread die and concentrate on the portability one. Regards Darryl Green.

Christopher Kohlhoff wrote:
documentation mentions the memory usage overhead of the proactor emulation on systems using select/epoll. However it is clear that there is considerable allocation overhead and construction overhead involved (and not only on those systems). While much discussion has centered on allocator performance, I'm concerned that the user/library constructor code invoked for each i/o operation is likely to be non-trivial as well. For bulk transfers, it is likely that these issues won't be too severe, however for systems where low-latency handling of small messages is critical, it is quite possible that these overheads will be comparable to or larger than any other part of the processing for a message/transaction.
I don't deny that there may be room for performance improvement in the implementation. But as I recently tested the custom allocation with Win32 I/O completion ports, and found that it performs almost identically to synchronous I/O, I don't think the public interface imposes any unnecessary inefficiencies.
Caleb's results on Linux aren't too encouraging though.
I find the way the design supports, and the documentation encourages, breaking up handling of structured messages into a small header read followed by a larger read (with appropriate handler for expected body/chunk) while the implementation appears to do some quite heavyweight operations compared to just reading a small header to be almost an encouragement of an antipattern for efficient I/O.
I don't agree that asio favours this approach over others. But I do believe that this approach is perfectly valid and extremely
Ok - my concern is that the examples should show best practice and the lib should make it easy to do things the right way. Using the lib as currently implemented, in the way shown somewhere (I think maybe this was just in a post - I can't find it in the examples which was where I imagined I saw it - sorry for the noise) would give poor performance. This should be able to be addressed by: i) Improving lib efficiency ii) Providing examples of the right way to do it iv) Providing an interface that is more efficient I believe you intend to do (i) and below you offer some alternatives for (ii) so this would seem to be well enough covered.
useful, since it permits asynchronous code to be written in terms of "contracts". I.e. perform this operation (or operations) and don't come back to me until it's all done or an error has occurred.
I tend to implement that at a layer above the raw I/O, but I take your point.
stream_socket my_socket;
with:
buffered_read_stream<stream_socket> my_socket;
Ah - that would be a closer approx to how I would expect this to be done - docs!
I would expect an optimistic reactive approach (read the header when notified, determine size, handler etc and opertunistically attempt a read of the body) to be significantly faster.
Let's take your example of a fixed sized header followed by a body, and compare and contrast a reactive approach with the alternatives offered by asio.
---------------------
1. Reactive. 2. Asio with multiple reads.
This is the approach where we issue asynchronous reads for exactly the length of the header, and then the body. It may be less efficient, but only because of the additional pass through the demultiplexer, not because it makes more read() system calls
Yes - precisely the problem. The select-based demuxer needs to be made more efficient by optimization or a modified interface.
than the above. However feel free to compare in terms of readability :) The main reason it's clearer is that you no longer have to deal with short reads -- async_read's contract takes care of that for you.
I think you will find reasonable reactive libs/async buffering providers etc do much the same thing for readability.
3. Asio with multiple reads + opportunistic body read.
This extends number 2 by adding in the opportunistic read of the body. In effect this makes it equivalent to your reactive read
Yes. It seemed like an odd mix of sync and async IO so I didn't consider it as an idiom to recommend in general.
4. Asio with bulk reads.
This approach is exemplified by the HTTP server example. Essentially it reads as much as is available (up to some limit of course) in one go, and then processes it using some state machine similar to that in number 1. It is generally the most efficient since it requires the fewest number of read system calls or passes through the demuxer.
Yes. This is fine for streaming/bulk transfers. My concern was more about an idiom for lots of short request/response type (low latency needed) messages.
5. Asio with transfer_at_least(...).
In certain applications you may be able to combine the opportunistic read into the initial asynchronous read operation,
The circumstances where this applies for TCP are hard to imagine, but if you had char device IO I would have a use for this (mind you, the device in question always returns the full message or nothing, so it is not quite a match either). Thanks for pointing out the alternatives, and maybe at least some of these should get a mention in docs in the context of some more substantial tutorial, but better would be to just ensure that the simplest approach/interface is also efficient and illustrate that.
It is implied that an appropriate demuxer/demuxer service can be used to implement various multi-threaded processing schemes. This may well be true. However, an alternative approach is to simply consider the affinity of an Async_Object and its demuxer(s) (one for read, one for write) to be dynamic and run a number of separate (per thread) demuxers, moving Async_Objects between them as required. In this way the handlers for a given Async_Object will only run in the expected context.
This is not portable. With Win32 I/O completion ports for example, once a socket is associated with an I/O completion port it cannot be disassociated. The demuxer design reflects this.
Ah - that makes it a bit messy. Do you consider that the locking dispatcher effectively provides the same result? I suspect it does so long as all access to what amounts to an active object guarded by the dispatcher does in fact go through the dispatcher. I assume this is the intent and other interfaces (other than I/O based ones I mean) should be wrapped and dispatch or post used to run them? An example would help a lot here.
case. I haven't reviewed the code to a level of detail where I can be sure I am understanding the dispatching done by the epoll and select reactors, but it looks to me as though multiple threads may all wait on the same fdset (or equivalent)? Is this right? Intended? I had expected some form of modified leader/followers pattern here?
No, only one thread in the pool waits on select/epoll/kqueue. Any other threads in the pool wait on a condition.
I believe you - just can't see how this works when multiple threads call run() on the same demux. I must be missing something in how the service actually gets invoked.
In particular, it would seem that some form of re-use of a single handler and buffer for an Async_Object instance without any allocation/(copy)construction of anything heavier than a pointer should be quite practical.
Asio's interface already supports "lightweight", reusable handlers, since ultimately a handler is just a function object. If you wish, you can implement your handlers so that they are no more expensive than a pointer:
class my_handler { public: explicit my_handler(my_class* p) : p_(p) {} void operator()(const asio::error& e) { p_->handle_event(e); } private: my_class* p_; };
Yes I can - but the lib should make simple things simple. I don't anticipate that I would make much use of the dynamic (per request) handler facility for architectural reasons and I image a lot of use would be similar. Providing an easy way to associate a persistent handler with a stream/async object and making this efficient would be relatively easy wouldn't it? Simple use would then use bind and allocate a boost::function or similar once, not per request, with correspondingly better performance. Regards Darryl Green. -- No virus found in this outgoing message. Checked by AVG Free Edition. Version: 7.1.371 / Virus Database: 267.14.7/214 - Release Date: 23/12/2005
participants (38)
-
Alex Besogonov
-
Andrew Schweitzer
-
Andy Little
-
Arkadiy Vertleyb
-
Arno Wilhelm
-
Bardur Arantsson
-
Beman Dawes
-
Ben Artin
-
Brian Allison
-
Caleb Epstein
-
christopher baus
-
Christopher Kohlhoff
-
Cliff Green
-
Darryl Green
-
Darryl Green
-
Dave Moore
-
Eugene Alterman
-
Felipe Magno de Almeida
-
Giovanni P. Deretta
-
Ion Gaztañaga
-
Jeff Garland
-
Jeremy Maitin-Shepard
-
Jody Hagins
-
João Abecasis
-
Manfred Doudar
-
Matt Vogt
-
Paul A Bristow
-
Pavel Vozenilek
-
Peter Dimov
-
Peter Petrov
-
Reece Dunn
-
Rene Rivera
-
simon meiklejohn
-
Stefan Seefeld
-
Steven E. Harris
-
Thore Karlsen
-
Tilman Kuepper
-
Victor A. Wagner Jr.