Boost logo

Boost :

From: Christopher Kohlhoff (chris_at_[hidden])
Date: 2005-12-22 05:48:42


Hi Matt,

--- Matt Vogt <mattvogt_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk