Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-11-14 12:13:13


--- In boost_at_y..., scleary_at_j... wrote:
> > From: williamkempf_at_h... [mailto:williamkempf_at_h...]
> >
> > --- In boost_at_y..., scleary_at_j... wrote:
> > > My current singleton would look very different once I remove
that
> > > restriction (by using an exception-neutral equivalent of
> boost::call_once).
> > > We might just want to start from scratch.
> >
> > What do you mean by "an exception-neutral equivalent of
> > boost::call_once"? Is this something Boost.Threads needs to look
at?
>
> I'm using the term "exception-neutral" meaning that: for library
code that
> calls end-user functions, if those functions throw, the library is
still in
> a usable state.

This has been discussed then. The "callback" passed to
boost::call_once must not throw. The description you supply for
POSIX in an e-mail doesn't make much sense to me.

"The function pthread_once() is not a cancellation point. However, if
init_routine() is a cancellation point and is canceled, the effect on
once_control is as if pthread_once() was never called."

This quote is out of context so I assume once_control is the
pthread_once_t instance passed to pthread_once(). Let's look at this
carefully. First, cancellation is not the same as an exception,
though the POSIX standard is written such that cancellation can be
implemented in terms of exceptions. No gaurantees, however, and even
if cancellation is implemented via exceptions it's a single exception
type. So other exceptions aren't addressed. So, let's see what
happens if we implement boost::call_once (or any other similar
concept) to follow the logic described above.

Basically, the implementation must wrap the call in a try/catch block
that does nothing and we must insure the "once_control" flag is not
changed to indicate that the init_routine() was called if an
exception is thrown. Simple enough to do on all platforms. Now
let's look at what this means as far as behavior.

Thread A makes a call to boost::call_once and the init_routine()
throws so execution continues as if boost::call_once was never
called. Thread A has no indication that initialization failed and
continues on it's merry way assuming that it has. Expect a crash to
follow shortly.

OK, let's assume that either boost::call_once returns an error or
that we manually set things up to be able to recognize the failure of
init_routine(). Now thread A can handle the error appropriately and
things seem OK. However, thread B in the mean time was blocking in
it's own call to boost::call_once. Since the call in thread A is
supposed to behave as if it was never called this means that B must
now call the init_routine() itself, but it's extremely unlikely that
things have changed so that this call won't just throw as well. So
the call to the init_routine() becomes wasted effort and most would
consider it a bad thing that the call is made again (especially if
you have N threads calling boost::call_once where N is a very large
number and/or init_routine() is a lengthy/expensive routine that
nears completion before the exception is encountered).

In general it's a design flaw to allow init_routine() to throw. In
the specific case of cancellation it may be appropriate, since it's
highly unlikely that all other threads also calling boost::call_once
will also be canceled. But in this case I'd either make
boost::call_once a cancellation point (I'm not sure why it's not in
POSIX) or insure an error code is returned to indicate the
init_routine() failed. For nearly every other exception type a huge
tar ball is encountered that can't be addressed in a universal
fashion. Instead specific handling of this situation should be
addressed by individual init_routines(). Generally this just means
that you wrap the calls that can throw and set an error condition in
some "global" state so that all threads know that the initialization
failed but only the first call is executed.

> The reason: I'd like to allow singleton constructors to throw. At
least in
> my code, most of my singleton constructors can fail.

That doesn't mean that the init_routine() that is called through
boost::call_once() and "constructs" the singleton needs to throw.
Nor should it, IMHO.
 
> Making boost::call_once exception-neutral for Win32 is simple
enough; but
> I'm not too sure about the Posix side of things; it just depends on
whether
> or not the pthread_once is exception-neutral. In addition to being
> exception-neutral, I think call_once/pthread_once must agree not to
set the
> flag if an exception is thrown from the user function.

I think I've covered this well enough above.
 
> I think it might be useful to Boost.Threads -- but you might want
to just
> add it as a separate function, since its implementation may be less
> efficient by-hand if we can't use pthread_once.

I'm not convinced we need or should even consider this. Have I
swayed your thinking or am I missing something?

Bill Kempf


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