so, if i understand correctly, there are pending calls for these events on the io_service queue, and when the socket is deleted, these calls are not removed?
I meant there might be pending *callbacks* - of the operations that had already completed. The asio guarantees that every outstanding async. call will complete with calling your handler, so the handlers cannot be just thrown-away. So if the operation was aborted, your handler is called with some error_code, but if it succeeded - you'll get error_code == 0.
if so, how do i fix this? or more generally: how can i safely release an object holding a socket while there may be outstanding i/o operations?
You can just close() the socket - this will abort all the oustanding operations. (When calling close() keep in mind that asio socket instance is not threadsafe.) I believe, the simplest and the safest way to *release* the things is to let your object (with its socket) to "die" automatically: just bind you object's shared_ptr to the handler you pass to ASIO, and after the last handler is called, your object (and the socket) will be deleted. This is exactly the pattern used in all the exapmples.
i had assumed that socket::cancel() was the right solution to this, but it seems this is not the case...
cancel() won't work on some Windows versions, so in your case it's better to use close().