[asio] Draft version uploaded to vault

Hello all, I have just uploaded a draft version of Boost.Asio to the vault (in the "Input - Output" folder). I'm particularly interested in feedback on the handler-based custom memory allocation and IPv4/v6 protocol independence. Please note: this is not a properly tested release. Caveat emptor. It has the following known issues: - It uses getaddrinfo/getnameinfo, but these functions are not thread-safe on Mac OS X. They will be replaced by thread-safe emulation using getipnodebyname and getipnodebyaddr. - Although builds using MSVC or Borland C++ automatically emulate getaddrinfo/getnameinfo on Windows 2000 and earlier, MinGW does not. Therefore MinGW currently only supports Windows XP and Windows Server 2003 targets. - It has not been compiled or tested at all on Solaris. It has been tested on Mac OS X 10.4, Debian Sarge with 2.6 linux kernel and Windows XP. Cheers, Chris Changes since 0.3.6 =================== (Excluding minor bug fixes and in no particular order.) * Renamed demuxer to io_service. * All services inherit from a common base io_service::service. As part of this change, the demuxer::get_service() member template function has been replaced with three free template functions: use_service(), add_service() and has_service(). * Resource classes inherit from basic_io_object. Sockets, timers, etc inherit from basic_io_object<Service>. This class automatically obtains the correct service object from the io_service, and provides the typedefs and functions that all objects must provide (e.g. an io_service() member function for obtaining the associated io_service object). * All handler objects destroyed during io_service destruction. Previously, if you did not allow demuxer::run() to continue until it had no more work to do, and then destroyed the demuxer, uninvoked handlers would never be destroyed. All stored handler objects that are "owned" by the io_service or any associated services are now destroyed automatically from inside the io_service. This allows quick, clean shutdown of programs using io_service::interrupt(). This handler cleanup has been implemented using "two-phase" service shutdown. Every service that inherits from io_service::service must implement a virtual function called shutdown_service(). The service's implementation of this function is required to destroy all copies of handlers that it owns (directly or indirectly). The cleanup must be done in a separate phase prior to the destruction of the service objects, since the handler destructors may try to access other services. * Renamed locking_dispatcher to strand. A strand is defined as a strictly sequential invocation of event handlers (i.e. no concurrent invocation). Use of strands allows execution of code in a multithreaded program without the need for explicit locking (e.g. using mutexes). Strands may be either implicit or explicit, as illustrated by the following alternative approaches: - Calling io_service::run() from only one thread means all event handlers execute in an implicit strand, due to the io_service's guarantee that handlers are only invoked from inside run(). - Where there is a single chain of asynchronous operations associated with a connection (e.g. in a half duplex protocol implementation like HTTP) there is no possibility of concurrent execution of the handlers. This is an implicit strand. - An explicit strand is an instance of asio::strand. All event handler function objects need to be wrapped using asio::strand::wrap() or otherwise posted/dispatched through the asio::strand object. * Add macros to disable platform-specific implementations. The following macros can be used to disable platform-specific implementation code and revert to portable select-based asynchronous I/O emulation: - BOOST_ASIO_DISABLE_EPOLL - BOOST_ASIO_DISABLE_IOCP - BOOST_ASIO_DISABLE_KQUEUE * Handler-based custom memory allocation. Many asynchronous operations need to allocate an object to store state associated with the operation. For example, the Win32 implementation needs OVERLAPPED-derived objects to pass to Win32 API functions. Furthermore, programs typically contain easily identifiable chains of asynchronous operations. A half duplex protocol implementation (e.g. an HTTP server) would have a single chain of operations per client (receives followed by sends). A full duplex protocol implementation would have two chains executing in parallel. Programs should be able to leverage this knowledge to reuse memory for all asynchronous operations in a chain. Given a copy of a user-defined Handler object h, if asio needs to allocate memory associated with that handler it will execute the code: void* pointer = asio_handler_allocate(size, &h); Similarly, to deallocate the memory it will execute: asio_handler_deallocate(pointer, &h); These functions are located using ADL. The asio library provides default implementations of the above functions in the global namespace: void* asio_handler_allocate(size_t, ...); void asio_handler_deallocate(void*, ...); which are implemented in terms of ::operator new() and ::operator delete() respectively. The implementation guarantees that the deallocation will occur before the associated handler is invoked, which means the memory is ready to be reused for any new asynchronous operations started by the handler. Custom memory allocation support is not yet used throughout the asio implementation. In particular, the reactor-based emulation still uses dynamically allocated objects. It is fully implemented for the Win32-specific I/O completion port code. The custom memory allocation support has also been disabled for Borland C++, since it generated obscure compiler errors. See libs/asio/example/allocation/server.cpp for an example of how to use the handler-based custom memory allocation. * Allocator template parameter removed. Removing the Allocator template parameter reduces the number of core types in asio and makes it easier to develop custom services (i.e. you don't have to worry what Allocator object the io_service has been templated on). It should also make it simpler to create a shared or static library-based implementation. A combination of handler-based custom memory allocation, and the ability for the asio implementation to embed objects directly into object internals (e.g. socket implementations), should minimise dynamic memory allocations. If there is a demonstrable need, I may consider adding an io_service::service-based allocator to allow memory allocation to be further customised on a per-io_service-object basis. * Add support for building without thread support. POSIX platforms only. The Windows implementation still requires threads. * Async waits cancelled by deadline_timer expires_*() setters. Outstanding asynchronous wait operations are now automatically cancelled if you call expires_at() or expires_from_now() to modify the expiry time of a deadline_timer. The previous documented behaviour was that it was "undefined" to modify the expiry time while there were outstanding waits. * Support for reference-counted buffers. The Const_Buffers and Mutable_Buffers concepts have been modified to allow reference counted buffers to be used. In particular: - The {Const|Mutable}_Buffers::iterator typedef and non-const begin()/end() functions have been removed from the concepts' requirements. Implementations of the concepts now need only provide const_iterator support, and do not need to allow in-place modification of the elements. - The asio implementation guarantees that at least one copy of the buffers object will be maintained for as long as the system may need access to the data. - The asio implementation explicitly converts the dereferenced const_iterator into mutable_buffer or const_buffer as needed. See the libs/asio/example/buffers/reference_counted.cpp example program. * Strongly-typed socket classes. Rather than asio::stream_socket, asio::datagram_socket and asio::socket_acceptor classes that were used for all protocols, these socket classes are now specific to each protocol. For example, we now have: ip::tcp::socket ip::tcp::acceptor ip::udp::socket and in the future we might have: unix::stream_protocol::socket bluetooth::rfcomm::socket and so on. These protocol-specific classes are simply typedefs for templates instantiated on the corresponding Protocol class. E.g.: template <typename Protocol> class stream_socket_service; template <typename Service> class basic_stream_socket; class tcp { ... typedef stream_socket_service<tcp> socket_service; typedef basic_stream_socket<socket_service> socket; ... }; New protocols can be added by creating classes like tcp above. * Use inheritance in socket types to prevent "layer violations". The basic_stream_socket and basic_datagram_socket templates now inherit from basic_socket<Service>. This class includes all functions that are common to stream and datagram sockets (everything except send/receive and friends). Stream layers such as ssl::stream return a reference to basic_socket<> from the lowest_layer() function. * IPv4 and IPv6 protocol independence. Socket, endpoint and address classes for IP (i.e. TCP and UDP) are independent of the particular IP version (4 or 6) used. The ipv4 namespace has been removed. For example: - The ipv4::address class has become IP version-independent ip::address. If you need to manipulate addresses for a specific IP version use ip::address_v4 or ip::address_v6. - The protocol classes are now ip::tcp and ip::udp, and the corresponding endpoint types are ip::tcp::endpoint and ip::udp::endpoint. - To open a socket you must specify the protocol version, e.g.: ip::tcp::socket socket(io_service); ... socket.open(ip::tcp::v4()); // IPv4 ... socket.open(ip::tcp::v6()); // IPv6 Note: if you already have an endpoint you can avoid this by using the protocol associated with the endpoint object: socket.open(endpoint.protocol()); - When constructing an endpoint with just a port number, you must specify the IP version: ip::tcp::endpoint(ip::tcp::v4(), 12345); The development of programs that are independent of the IP version is further aided by the new resolver interface outlined below. * Resolver replaces ipv4::host_resolver. The ipv4::host_resolver and ipv4::host classes have been removed in favour of an endpoint-oriented interface that can support different protocols. The functionality of this new interface is based on getaddrinfo and getnameinfo. Use the resolver by constructing a query, calling resolve() (or async_resolve()), and using the returned forward-only iterator to enumerate the endpoints that match the query. For example: ip::tcp::resolver resolver(io_service); ip::tcp::resolver::query query("foobar.com", "http"); ip::tcp::resolver::iterator iter = resolver.resolve(query); ip::tcp::resolver::iterator end; while (iter != end) { ip::tcp::endpoint ep = *iter; std::cout << iter->endpoint() << std::endl; std::cout << iter->host_name() << std::endl; std::cout << iter->service_name() << std::endl; ++iter; } The asynchronous equivalent is: void my_class::start_resolve() { ip::tcp::resolver resolver(io_service); ip::tcp::resolver::query query("foobar.com", "http"); resolver.async_resolve(query, boost::bind(&my_class::resolve_handler, this, asio::placeholders::error, asio::placeholders::iterator)); } void my_class::resolve_handler(const asio::error& e, ip::tcp::resolver::iterator iter) { if (!e) { ip::tcp::resolver::iterator end; while (iter != end) { ... ++iter; } } } There are overloads of resolve()/async_resolve() that take an endpoint and return an iterator. These are intended for reverse lookups. Since the ipv4::host_resolver had a local_host() function that returned information about the local system, a new function ip::host_name() has been added which returns a string containing the name of the system. * Multicast socket options renamed. The IP-specific socket options for multicast have been renamed. They are now also independent of the IP version. The name changes are: - add_membership is now join_group - drop_membership is now leave_group - time_to_live is now hops These multicast socket options are now found in the ip::multicast namespace. * String-to-address conversion now uses from_string function. This change was driven by three things: - The need to indicate an error without throwing if a conversion from string to address fails. - String literal addresses would not be common and so support for them does not need to be convenient. - It is more common to use a resolver to convert strings into complete endpoints. To convert a string into an address you would now write: ip::address address = ip::address::from_string("1.2.3.4"); which throws an asio::error on failure, or: asio::error error; ip::address address = ip::address::from_string( "1.2.3.4", asio::assign_error(error)); to convert without throwing. Similar functions exist on the ip::address_v4 and ip::address_v6 classes. * IPv4 and IPv6 addresses can be converted to and from bytes. The ip::address_v4 and ip::address_v6 classes contain: - A typedef called bytes_type, which is a boost::array<unsigned char, N>. For IPv4, N is 4. For IPv6, N is 16. - A constructor that takes a bytes_type value. - A function to_bytes() that returns a bytes_type value. * Order of endpoint constructor params reversed. The old ipv4::tcp::endpoint class (and udp equivalent) had a constructor that took a port and address in that order. The new ip::tcp::endpoint and ip::udp::endpoint classes have the order reversed, since that is more idiomatic. * Change position of flags param on send_to, receive_from. The order of parameters on the basic_datagram_socket's send_to, async_send_to, receive_from and async_receive_from functions have been changed to put the endpoint before the message flags. Overloads of these functions have been added that do not include the flags parameter, which gives the flags a default value of 0. A similar change has been made for the send, async_send, receive and async_receive functions on basic_datagram_socket and basic_stream_socket. * Don't globally block SIGPIPE if an alternative is available. On Linux, the MSG_NOSIGNAL flag is now passed to the sendmsg function. On Mac OS X, the SOL_SOCKET/SO_NOSIGPIPE socket option is set on all sockets when they are created. The SIGPIPE signal is globally ignored on Solaris. * Reactor implementations run operations until complete. The reactor socket implementation sets non-blocking I/O for asynchronous emulation (non-blocking mode is enabled for a socket the first time an async call is made on that socket). Operations are now restarted automatically if the asynchronous operation returns would_block or try_again, meaning that asynchronous operations are guaranteed never to return these errors. Changes planned for 0.3.7 ========================= * Fix tutorials/examples to not use "C style" code. * Emulation of getaddrinfo/getnameinfo on platforms where it is unavailable or broken. Changes planned for post-0.3.7 ============================== * Iostreams support. * Split_error handler function object. * Overloads of io_service::run() that take a timeout parameter. This is to allow calls to run() to be interleaved with other application code. The run() function will return an enum value indicating why it finished (e.g. timed_out, out_of_work, interrupted, etc). * Polymorphic wrapper for the Dispatcher concept. * Polymorphic wrapper for the Stream concept. * Investigate handler-invocation hooking. * Develop a performance test framework. * Rework reactor implementations to support handler-based custom memory allocation. * New reactor implementations, e.g. /dev/poll and Solaris 10 ports. * Use the Windows API call ConnectEx where available for true asynchronous socket connect operations. * Add better support for line-based protocols. This might take the form of an {async_}read_line function or equivalent. * Add UNIX domain socket support. * Documentation, documentation, documentation. I hope that's everything!

| -----Original Message----- | From: boost-bounces@lists.boost.org | [mailto:boost-bounces@lists.boost.org] On Behalf Of Olaf van der Spek | Sent: 26 April 2006 13:07 | To: boost@lists.boost.org | Subject: Re: [boost] [asio] Draft version uploaded to vault | | On 4/26/06, Christopher Kohlhoff <chris@kohlhoff.com> wrote: | > I hope that's everything! | | Hi, | | Did you consider renaming the library? | For example, to async_io? There was some discussion before about the name, and I think that asio won't do (in use already), but I feel we haven't come up with a good name yet. Async_io, I think, breaks with tradion in having a _ in the library name. I also got the impression that the library handles both what one might call async as well as what one might call sync. So I think we should scratch our heads a bit more for a better name for this useful library. 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

On 4/26/06, Paul A Bristow <pbristow@hetp.u-net.com> wrote:
| Hi, | | Did you consider renaming the library? | For example, to async_io?
There was some discussion before about the name, and I think that asio won't do (in use already), but I feel we haven't come up with a good name yet. Async_io, I think, breaks with tradion in having a _ in the library name. I
Aren't there lots of libs with an underscore in the name already?
also got the impression that the library handles both what one might call async as well as what one might call sync. So I think we should scratch our heads a bit more for a better name for this useful library.

Hi Chris, a couple of questions/notes (I do not have the time right now to download the lastest version from the vault, so may be the docs already have answers for those, but still...) Christopher Kohlhoff wrote:
* Renamed locking_dispatcher to strand.
A strand is defined as a strictly sequential invocation of event handlers (i.e. no concurrent invocation). Use of strands allows execution of code in a multithreaded program without the need for explicit locking (e.g. using mutexes).
Strands may be either implicit or explicit, as illustrated by the following alternative approaches:
- Calling io_service::run() from only one thread means all event handlers execute in an implicit strand, due to the io_service's guarantee that handlers are only invoked from inside run().
- Where there is a single chain of asynchronous operations associated with a connection (e.g. in a half duplex protocol implementation like HTTP) there is no possibility of concurrent execution of the handlers. This is an implicit strand.
- An explicit strand is an instance of asio::strand. All event handler function objects need to be wrapped using asio::strand::wrap() or otherwise posted/dispatched through the asio::strand object.
Does asio always grab a mutex before invoking a strand protected handler, or it guarantees that the same strand is run on the same thread and thus have automatic serialization? Just an implementation detail, i know, but i'm curious.
* Handler-based custom memory allocation.
Many asynchronous operations need to allocate an object to store state associated with the operation. For example, the Win32 implementation needs OVERLAPPED-derived objects to pass to Win32 API functions.
Furthermore, programs typically contain easily identifiable chains of asynchronous operations. A half duplex protocol implementation (e.g. an HTTP server) would have a single chain of operations per client (receives followed by sends). A full duplex protocol implementation would have two chains executing in parallel. Programs should be able to leverage this knowledge to reuse memory for all asynchronous operations in a chain.
Given a copy of a user-defined Handler object h, if asio needs to allocate memory associated with that handler it will execute the code:
void* pointer = asio_handler_allocate(size, &h);
Similarly, to deallocate the memory it will execute:
asio_handler_deallocate(pointer, &h);
These functions are located using ADL. The asio library provides default implementations of the above functions in the global namespace:
void* asio_handler_allocate(size_t, ...); void asio_handler_deallocate(void*, ...);
which are implemented in terms of ::operator new() and ::operator delete() respectively.
The implementation guarantees that the deallocation will occur before the associated handler is invoked, which means the memory is ready to be reused for any new asynchronous operations started by the handler.
I'm not sure if these functions are not enough (or at least have enough parameters). They have no way to know alignment requirement of the allocated object (and thus assume the worst), and the deallocator does not have a size_t parameter (as operator delete has). What about these signatures: template<typename T> T* asio_handler_allocate(size_t, handler_type&); and template<typename T> void asio_handler_deallocate(size_t, handler_type&, T*);
* Support for reference-counted buffers.
The Const_Buffers and Mutable_Buffers concepts have been modified to allow reference counted buffers to be used. In particular:
- The {Const|Mutable}_Buffers::iterator typedef and non-const begin()/end() functions have been removed from the concepts' requirements. Implementations of the concepts now need only provide const_iterator support, and do not need to allow in-place modification of the elements.
- The asio implementation guarantees that at least one copy of the buffers object will be maintained for as long as the system may need access to the data.
- The asio implementation explicitly converts the dereferenced const_iterator into mutable_buffer or const_buffer as needed.
Very good, but what about explicitly guaranteeing that *exactly* one *live* (1) copy of the buffers object will be maintained? I'm sure in practice asio already guarantees that, but specifying it makes it possible to hold the buffer with an auto_ptr-like smart pointer, without the overhead of a shared_ptr if it is not needed. That's all for now... as soon as I have a little more time I hope to be able to try the last version and finish my coroutine lib :) Very good work. (1) that is, if the object ever is copied, only the last copy is ever used again. Other copies can only be destroyed. -- Giovanni P. Deretta

Hi Giovanni, --- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
Does asio always grab a mutex before invoking a strand protected handler, or it guarantees that the same strand is run on the same thread and thus have automatic serialization? Just an implementation detail, i know, but i'm curious.
It uses a mutex, but the mutex is only held while manipulating the linked list of pending handlers. It is not held while the upcall to the handler is made. Having a strand only execute on the one thread sounds like a theoretically possible implementation approach. However, I'm probably going to attempt an implementation that uses a lock-free linked list first. [ ... custom memory allocation ... ]
I'm not sure if these functions are not enough (or at least have enough parameters). They have no way to know alignment requirement of the allocated object (and thus assume the worst), and the deallocator does not have a size_t parameter (as operator delete has). What about these signatures:
template<typename T> T* asio_handler_allocate(size_t, handler_type&);
and
template<typename T> void asio_handler_deallocate(size_t, handler_type&, T*);
I've added the size parameter for deallocate. My initial custom memory implementation done during the review used templates. I later rejected this approach because the types being allocated are completely internal to the library, and I felt they should not be exposed to the user in any way. I also think not requiring the user to define templates makes it simpler to use -- it's closer to defining custom operator new and delete for a class. With respect to alignment, the functions have the same restrictions as ::operator new(std::size_t) and malloc. I'm not sure the increased complexity in supporting other alignment requirements would give much, if any, benefit in practice. All types being allocated this way are complex structures, e.g.: - They contain a copy of the user-defined Handler - On Windows they are OVERLAPPED-derived classes - On non-Windows they will contain pointers to adjacent elements in a linked list. Although I see it as a QoI issue exactly how many allocations occur per operation, in my proposal I plan to at minimum encourage implementations to: - Prefer to do only do one allocation per operation. - Failing that, only do one allocation at a time (i.e. don't allocate a new memory block associated with a handler until the previous one has been deallocated). [ ... reference counted buffer support ... ]
Very good, but what about explicitly guaranteeing that *exactly* one *live* (1) copy of the buffers object will be maintained? I'm sure in practice asio already guarantees that,
Actually, it doesn't. The implementations of asio::async_read and asio::async_write have to keep a copy of the buffers so that the operation can be restarted. This copy is in addition to the copy kept by the lower-level operation (i.e. read_some or write_some). Instead of this, I plan to further tighten up the specification of when and from which threads the implementation is allowed to make calls back into user code, and how the appropriate memory barriers are placed between these calls. In theory this should allow reference counted buffers without needing synchronisation. Cheers, Chris

On 4/26/06, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
Hello all,
Hi Chris, [snipped]
These functions are located using ADL. The asio library provides default implementations of the above functions in the global namespace:
Why is the default implementations in the global namespace? Shouldnt it be in asio namespace? [snipped] -- Felipe Magno de Almeida

Hi Felipe, --- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
Why is the default implementations in the global namespace? Shouldnt it be in asio namespace?
While this stuff was in development, putting it in the global namespace was the only way I could get it to work with some compilers -- Borland C++ 5.6 was the culprit if I remember correctly. However since I had to give up supporting custom memory allocation for that compiler anyway, then yes I should put them into the asio namespace. I just need to put a "using namespace asio" or similar at the point in the implementation where the call to the allocate function is made. I also suppose if I put it into the asio namespace then I can drop the asio_ prefix from the functions. I.e. you would now write: class my_handler { ... void operator()(const asio::error& e) { ... } friend void* handler_allocate(size_t s, my_handler* h) { ... } friend void handler_deallocate(void* p, size_t s, my_handler* h) { ... } }; On a related note, you should be able to customise allocation for all handlers by writing: template <typename Handler> void* handler_allocate(size_t s, Handler* h) { ... } template <typename Handler> void handler_deallocate(void* p, size_t s, Handler* h) { ... } at global scope. Cheers, Chris

On 4/30/06, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
Hi Felipe,
--- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
[snipped]
I also suppose if I put it into the asio namespace then I can drop the asio_ prefix from the functions. I.e. you would now write:
Dropping asio prefix should be thinked more carefully. While the default implementation wouldnt clash with user code, user's customization could collapse with its own free functions. Or even worse, asio could find through ADL functions that wasnt made to be allocation customization for asio. [snipped]
On a related note, you should be able to customise allocation for all handlers by writing:
[snipped]
at global scope.
With ADL it should be in the same namespace as the handlers, so if I have a handler in some namespace it wouldnt call handler_allocate nor handler_deallocate, IIRC.
Cheers, Chris
Thanks, -- Felipe Magno de Almeida

Hi Felipe, Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
Dropping asio prefix should be thinked more carefully. While the default implementation wouldnt clash with user code, user's customization could collapse with its own free functions. Or even worse, asio could find through ADL functions that wasnt made to be allocation customization for asio.
Yep, fair enough - it's probably better to be safe. Although it does raise the question of what to call the functions in the proposal for TR2. Any suggestions? Cheers, Chris

On 5/8/06, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
Hi Felipe,
[snipped]
Yep, fair enough - it's probably better to be safe. Although it does raise the question of what to call the functions in the proposal for TR2. Any suggestions?
It seems to me that there were some discussion about ADL hooking naming convention, IIRC for the range library, in this mailing list. But I didnt read.
Cheers, Chris
Best regards, -- Felipe Magno de Almeida
participants (5)
-
Christopher Kohlhoff
-
Felipe Magno de Almeida
-
Giovanni P. Deretta
-
Olaf van der Spek
-
Paul A Bristow