Boost logo

Boost :

From: scleary_at_[hidden]
Date: 2001-11-14 13:26:55


> From: williamkempf_at_[hidden] [mailto:williamkempf_at_[hidden]]
>
> > 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.

Sorry; my miscommunication! "exception-neutral" also implies that any
exceptions thrown by end-user functions are propogated up through the
library code.

> 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.

Sorry about the context -- I was going to include it and forgot. You are
right about once_control.

You're technically correct. The POSIX standard does not include anything
about exceptions, AFAIK. I was just hoping that the definition of
"cancellation" might cover exceptions, but probably not.

> 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.

This is a result of my miscommunication above. The behaviour I was heading
for was that if init_routine() threw, then call_once() would act as though
it had never been called (i.e., not set once_control), and then propogate
the exception.

On the Win32 side, this can be done just by making the temporary Mutex an
actual object, so it is released + closed in its destructor.

> 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().

My intent is that the exception would indicate 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).

Most of the time, an init_routine() failure is indicitive of some fatal
error. Personally, I would just log/print/display the exception message and
exit. However, just in case someone else has a "non-critical singleton",
the ability to re-attempt construction exists.

> In general it's a design flaw to allow init_routine() to throw.

As I said, I think it would be a fatal error -- in every case I can think
of, anyway. But not impossible, and not a design flaw if one of your
principles was "all error messages must get to the user".

> 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.

I was considering this, but it seems like a work-around for missing
functionality. Maybe not, maybe this is just what I should do...

> > 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.

We need to get the error to the user -- either through exception propogation
(my idea) or through global data (your idea).

> > 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?

It's your decision at this point. We can either write a call_once that can
propogate exceptions, or I can do the global data thing in the singleton
implementation. I prefer a propogating call_once, but it's your call.

        -Steve


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