
Hi Matt, --- Matt Vogt <mattvogt@warpmail.net> wrote:
Ok, here's what I would like to see.
First, I want to supply a handler for errors, to the demuxer. I want this handler to be called back when required, with error code that occurred, and the socket to which error pertains.
Something like void my_error_handler(asio::error e, shared_ptr<socket> s) { // clean up resources associated with this socket }
Then I want to ignore error handling completely in mainline code.
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)); } ... }; <snip>
The connection object is automatically destroyed, and the socket closed, when there are no more operations associated with it.
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.
EOF is only in the normal sequence of events for some protocols, and even then often only at certain points in the protocol. E.g. with HTTP you can have responses terminated by EOF, but an early EOF is an error if a content length is specified. Therefore I prefer it to be explicitly handled in those cases, rather than the other way around.
In this case, the situation can still be handled explicitly by the caller, but they would make their decision based on socket.eof(), rather than inspection of a resulting error-code.
An implementation of what you propose would, I think, require too great a coupling between the socket and the completion handler for it to be useful generally (since I believe handling EOF as a non-error is the exception rather than the rule). 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. <snip>
I think both of the currently available error-handling options are inferior. C-style error return codes have long been shown to be inadequate, and the exception throwing option is rendered almost useless because the thrown object is an error code. This means you essentially need a catch for every operation in order to know the context, and it can't be used for aynch operations.
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"));
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. <snip>
One idea that seems useful is the compile-time stream composition Hugo Duncan implemented in his giallo library. It allows composing sequences of data handlers-and-possibly-transformers, similar to the Boost Iostreams Library. You can see an example of it here: http://tinyurl.com/cmfby
[Split for gmane: <http://cvs.sourceforge.net/viewcvs.py/giallo/giallo/ libs/net/example/http_server.cpp?rev=1.10&view=auto> ]
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. Cheers, Chris