
Christopher Kohlhoff <chris <at> kohlhoff.com> writes:
But we've still got the problem of composed operations. Perhaps what you want could be achieved by wrapping the socket in an application class that automatically created a new handler function object with the appropriate error handling. E.g.:
template <typename Stream> class stream_wrapper { public: stream_wrapper(..., function<void(const error&)> f); ... template <typename Const_Buffers, typename Handler> void async_read_some(const Const_Buffers& bufs, Handler h) { stream_.async_read_some(bufs, split_error(f_, h)); } ... };
Yes, that's not a bad idea. Of course, you don't want to rewrite all the forwarding functions with any regularity, so it's probably better to do this with a Handler object, constructed with an error-handling function and then supplied with the success-handling function for each new operation. Is split_error documented somewhere?
Well, that's subtle. I'm not sure if it's a good idea or not.
This approach removes the need for explicit resource management (i.e. closing the socket) which I think is, on balance, a good thing.
Yes, I guess it is, once enable_shared_from_this is in your working vocabulary.
Let's assume that the socket implementation contained a boolean data member indicating whether EOF had been reached. Firstly, this member would have to be updated upon completion of an operation. There's no guarantee that this will occur in any particular thread if multiple threads call demuxer::run, so some synchronisation (and associated cost) would be required. Secondly there is no guarantee that the original socket object still exists at the time the completion handler is delivered.
Well, that sounds like an insurmountable problem :)
For the synchronous operations you can create your own error handler object to add the required context, e.g.:
socket.connect(endpoint, throw_my_error("connect"));
Perhaps it's been discussed, but is there any particular reason you wouldn't want to define an 'asio::socket_error' subclass of asio::error? Attaching the relevant shared_ptr<socket> to the error code would make some error- handling strategies simpler.
If it were a pair of error-code and socket, and could be used asynchronously, it would be useful, but propagating the exception outside demuxer::run seems messy, since multiple threads could be calling 'run'.
In respect of exceptions and demuxer::run, I hope I have defined the behaviour clearly. Specifically, the demuxer cannot know about all exception types, so exceptions are allowed to safely propagate to outside the run call where they can be handled. This only affects the thread where the exception is raised. After handling the exception, that thread may immediately call demuxer::run again to return to the pool.
Sorry if I'm missing the point, but I don't see what your saying here. If there are some exceptions that you cannot deal with, why should that prevent you from handling the ones you do know about? If users supply handlers that throw, they will need to deal with the resulting exceptions manually. I don't see how anything is simplified by requiring the user to deal with library exceptions in the same place (the call to demuxer::run)...
Asio has something vaguely similar in its stream layers. For example the ssl::stream template can be layered over a stream socket like so:
typedef ssl::stream<stream_socket> ssl_stream_socket;
Maybe this can be extended to support more complex composition scenarios.
Yes, that seems plausible. Matt