> I guess this semi-rant can be summarized as: asio needs documentation on best practices/patterns/guidelines for life-time management of handlers.

With this I agree. It has taken me quite some time to become familiar enough with asio to be able to write correct services for it. I had to read the (sparse) documentation a number of times, and made numerous mistakes.

On 30 January 2018 at 22:08, Gavin Lambert via Boost-users <boost-users@lists.boost.org> wrote:
On 31/01/2018 03:03, Stian Zeljko Vrba wrote:
 > It is not valid to have more than one outstanding async read on an asio io object at a time

Although I'm not doing this, this restriction is mentioned in the documentation only for composite operations (free-standing async_read) but not for members on objects (async_read_some()).

There is actually no technical restriction from having multiple pending reads -- even for standalone async_read.  You can do it, and it will behave "correctly".

The trouble is that this correct behaviour is not *useful* behaviour. If you have multiple pending reads on the same object then it means the OS is free to scatter the data some here, some there, and it becomes impossible to make sense of the data arrival order, which renders stream sockets fairly useless.  (It *is* something you can sensibly do with message-based sockets, though -- but it's still unusual because application-layer protocols usually aren't written to react well to things being processed out of expected order.)

Multiple writes are the same -- there's no technical reason why you can't, but usually it's nonsensical to actually do it since the data can end up interleaved in strange and unexpected ways at the other end.

So the limit of one outstanding read and one outstanding write at a time is a practical one, not a technical one.

So asio leaves handling of difficult (edge-)cases to all users instead of offering a user-friendly opt-in solution, such as: each i/o object tracks posted, but not-yet-executed handlers. When cancel on the object is called, it would traverse the list and update error codes.

There is no list to traverse.  There can't be, due to the nature of MPMC queues.

Besides, if an operation did actually execute correctly, it's usually more useful to report that success even if a cancel occurred later -- after all, the bytes were actually read or transmitted, and it may be important to know that so that you know what you need to send next.

(Unrelated: individual operations cannot be canceled (e.g., read, but not write); this is a glaring design omission from my POV. I needed that in another project.)

This is generally an OS limitation.

It's also very standard in concurrent programming that requests to cancel are just that: requests.  The request is free to be ignored if the task has already completed, even if the callback hasn't been invoked yet, and especially if the callback might already be executing.

It's simply not possible to do it any other way.

Yes, that's another gotcha when you have outstanding both reads and writes. Esp. tricky to discover and fix when only, say, read, fails due to broken pipe, but there's no data to send so that also write() fails in the forseeable future. Then io_service just sits and hangs there waiting for the write handler to return...

If there's no data to write then you don't have a pending write to begin with.  Write operations are only started when you actually have data to send, and typically complete very quickly (with the exception of pipes that are full) -- typically only read (and listen) operations are left pending for long periods while waiting for incoming data.

.. I guess this semi-rant can be summarized as: asio needs documentation on best practices/patterns/guidelines for life-time management of handlers.

It has examples.

Does weak_ptr protect against an analogue of the "ABA" problem: Say I have a permanent weak_ptr to an alive shared_ptr. Then the shared_ptr gets destroyed. Then another shared_ptr of the same type gets created, but both the object and the control block get the same addresses as the previous instances (not unthinkable with caching allocators). How will lock() on the existing weak_ptr behave? Intuitively, it should return null, but will it? Does the standard say anything about this?

Yes, it will reliably return nullptr.  It is not possible for a new object to have the same control block address as some prior object as long as any weak_ptrs to the original object exist.

Essentially both the control block and the object are refcounted; a shared_ptr holds a count of both the object and the control block, while a weak_ptr holds a count of the control block alone.

Boost-users mailing list