Date: 2001-11-15 10:41:15
--- In boost_at_y..., scleary_at_j... wrote:
> > From: williamkempf_at_h... [mailto:williamkempf_at_h...]
> > > I'm using the term "exception-neutral" meaning that: for
> > > calls end-user functions, if those functions throw, the library
> > > a usable state.
> Sorry; my miscommunication! "exception-neutral" also implies that
> exceptions thrown by end-user functions are propogated up through
> library code.
OK. This doesn't change most of the analysis.
> You're technically correct. The POSIX standard does not include
> about exceptions, AFAIK. I was just hoping that the definition of
> "cancellation" might cover exceptions, but probably not.
It simply can't, because POSIX is a C standard where there are no
such things as "exceptions".
> > Basically, the implementation must wrap the call in a try/catch
> > that does nothing and we must insure the "once_control" flag is
> > 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
> for was that if init_routine() threw, then call_once() would act as
> it had never been called (i.e., not set once_control), and then
> the exception.
> On the Win32 side, this can be done just by making the temporary
> actual object, so it is released + closed in its destructor.
There are several ways to implement this on all platforms, and it's
trivial everywhere. However, it doesn't change the analysis.
> > 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
> > init_routine().
> My intent is that the exception would indicate failure of
> > Now thread A can handle the error appropriately and
> > things seem OK. However, thread B in the mean time was blocking
> > 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
> > now call the init_routine() itself, but it's extremely unlikely
> > things have changed so that this call won't just throw as well.
> > the call to the init_routine() becomes wasted effort and most
> > consider it a bad thing that the call is made again (especially
> > you have N threads calling boost::call_once where N is a very
> > 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
> error. Personally, I would just log/print/display the exception
> exit. However, just in case someone else has a "non-critical
> the ability to re-attempt construction exists.
There is a very large distinction between "log/print/display the
exception and exit" and what really happens here. User code can wrap
the call in a try/catch block and ignore the error, causing severe
problems here, as being discussed. Considering this a programmer
error isn't a valid idea. This puts too much burden on the
programmer. He must know that this case is unique and that the
program simply must exit (unless it's a very rare circumstance where
another thread might successfully call the init_routine() with out
> > 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
> of, anyway. But not impossible, and not a design flaw if one of
> principles was "all error messages must get to the user".
You're looking at the problem wrong, I think. It is a design flaw
for init_routine() to throw. However, it's not a design flaw for
init_routine() to use a try/catch block to do precisely what you
want. If reporting the error and aborting is the most appropriate
action, which is likely the most prudent action in most cases, then
do this *IN* the init_routine() not after the call. This simplifies
the code tremendously, and insures there's no race conditions where
thread B could try to use shared data that failed to initialize (or
initialize the data a second time which because of side effects can
be even worse) before thread A catches and handles the exception that
On the other hand, if you don't want to abort but want all threads to
know that initialization failed so that they can handle the error
gracefully, then your init_routine() can use shared data to indicate
the failure of the initialization.
In other words, not being able to throw an exception out of
init_routine() doesn't restrict you from doing any for of error
handling you want to do. On the contrary, it insures you can handle
the error any way you wish.
The only way exceptions could be used would be if there were some way
to throw the same exception type thrown by init_routine() for all
threads calling boost::call_once() while only one thread is allowed
to call the init_routine(). This can be hand coded for specific
cases, but I know of no way to do this generically and portably.
> > Generally this just means
> > that you wrap the calls that can throw and set an error condition
> > some "global" state so that all threads know that the
> > 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...
It's not missing functionality, IMHO, and I hope the above explains
it well enough that you agree.
> > > The reason: I'd like to allow singleton constructors to throw.
> > > 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
> > Nor should it, IMHO.
> We need to get the error to the user -- either through exception
> (my idea) or through global data (your idea).
Exceptions can be thrown manually with only minor modifications to
boost::once_flag foo_once = BOOST_ONCE_INIT;
static foo inst;
pfoo = &inst;
foo_init_failed = true;
Now all threads calling foo_instance() get an exception thrown when
initialization fails, but the initialization is only done once no
matter what. The trick is in knowing what exception to throw where
you have some_exception_type() above. It should be the same
exception thrown by the initialization code, but I don't know how
you'd do this across thread boundaries in a generic fashion. That's
why I don't think we're missing functionality.
> > > I think it might be useful to Boost.Threads -- but you might
> > > add it as a separate function, since its implementation may be
> > > 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
> propogate exceptions, or I can do the global data thing in the
> implementation. I prefer a propogating call_once, but it's your
I can't make the call with out someone addressing the issues I've
layed out. If there's a solution that works I'm open to it.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk