
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