Re: [boost] [pool] segfault (fun with static initialization ordering)

_______________________________________ De: boost-bounces@lists.boost.org [boost-bounces@lists.boost.org] En nombre de Chris Newbold [Chris.Newbold@mathworks.com] Enviado el: viernes, 11 de julio de 2008 16:25 Para: 'boost@lists.boost.org' Asunto: Re: [boost] [pool] segfault (fun with static initialization ordering) [...]
Right. The way that singleton_default guarantees that instance() is called during dynamic initialization is by [analysis of singleton machinery]
That's it.
* Where the problem lies can be found by looking at the usage of singleton_pool by fast_pool_allocator: notice that singleton_pool is used *only* when fast_pool_allocator::allocate or ::deallocate are invoked. So, it is perfectly possible to construct a fast_pool_allocator without that forcing the compiler to construct the underying singleton_pool *yet*. And this is what's happening indeed, the sequence of objects construction we're having is this:
1. owned_base::pool_ begins construction 1.1 call of new pool_lii() inside pool::pool 1.1.1 a fast_pool_allocator is cted inside pool_lii ctor. 1.2 pool_ii ends construction 2. pool_ ends construction 3. the singleton instance associated to singleton_pool<...>is cted before main() because fast_pool_allocator uses it later (singleton guarantee).
I'm not sure I buy this explanation entirely.
owned_base::pool_ is a global object. As described above, so is object_creator.
The test case consists of a single translation unit, and inside that translation unit, the declaration of object_creator precedes that of owned_base::pool_ (because object_creator is declared by a Boost.Pool header which is included before the application-level code which declares owned_base).
Following the fairly clear rules about initialization order for namespace-scoped global objects, I would have expected the compiler to construct object_creator first (and therefore the pool) before constructing owned_base::pool_.
These precedence rules do not apply to class template static data: quoting the standard (latest draft at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2691.pdf), section 3.6.2: "Dynamic initialization of a non-local object with static storage duration is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects with ordered initialization defined within a single translation unit shall be initialized in theorder of their definitions in the translation unit." That is, only *ordered* file-scope objects are subject to the precedence rule you're referring to. Class template static data are not ordered by definition (except if belonging to a full specialization). So, the sequence I proposed above is consistent AFAICS. The following is a case where the precende rule *cannot* be applied for logical reasons: template<typename T> struct foo{ static int x; }; template<typename T> int foo<T>::x=T::x; struct bar { static int x; }; int bar::x=666; static int x=foo<bar>::x; You see, foo<T>::x is defined before bar::x yet it can only be initialized after bar::x has already been constructed. In the original case we're dealing with there are no dependency constraints like this, yet the sequence I proposed a post ago is to the best of my knowledge consistent with the rules of C++ (and we're seemingly observing it in at least a compiler). BTW, have you tried the fix I was proposing? Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of JOAQUIN M. LOPEZ MUÑOZ Sent: Friday, July 11, 2008 11:35 AM
These precedence rules do not apply to class template static data: quoting the standard (latest draft at http://www.open- std.org/jtc1/sc22/wg21/docs/papers/2008/n2691.pdf), section 3.6.2:
Ah! This is just the sort of explanation I was looking for, but could not find--I guess my copy of the C++ standard has gone stale. Based on this, it certainly would appear that GCC's behavior is conformant and that the combination of fast_pool_allocator and singleton_default presumes behavior which is unspecified.
BTW, have you tried the fix I was proposing?
Not yet. I'm on vacation this week, but I will try it as soon as possible. My only concern is whether referencing singleton_default from the constructor for fast_pool_singleton will guarantee proper initialization ordering. 3.6.2 doesn't really shed any light on how ordered and unordered initialization may be coupled. There isn't, for example, any expressed guarantee that non-locals will be initialized prior to first reference. -Chris

Chris Newbold <Chris.Newbold <at> mathworks.com> writes:
From: boost-bounces <at> lists.boost.org [mailto:boost-bounces <at>
lists.boost.org]
On Behalf Of JOAQUIN M. LOPEZ MUÑOZ Sent: Friday, July 11, 2008 11:35 AM
BTW, have you tried the fix I was proposing?
Not yet. I'm on vacation this week, but I will try it as soon as possible. My only concern is whether referencing singleton_default from the constructor for fast_pool_singleton will guarantee proper initialization ordering.
3.6.2 doesn't really shed any light on how ordered and unordered initialization may be coupled. There isn't, for example, any expressed guarantee that non-locals will be initialized prior to first reference.
There is no such guarantee indeed, but the fix does NOT rely on initialization rules for non-local objects, but on the initialization rules for *local* objects with static storage duration (6.7/4): the singleton is a local static object inside the function singleton_default<T>::instance(), which is explicitly invoked inside fast_pool_allocator ctors via singleton_pool<...>is_from() (if you apply the fix, that is). The non-local object involved in singleton_default<T>, namely singleton_default<T>::create_object is there to guarantee that singleton_default<T>::instance() is invoked, and thus the singleton created in dynamic initialization time *if no one has done it before*. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Joaquin M Lopez Munoz Sent: Tuesday, July 15, 2008 9:42 AM
There is no such guarantee indeed, but the fix does NOT rely on initialization rules for non-local objects, but on the initialization rules for *local* objects with static storage duration (6.7/4): the singleton is a local static object inside the function singleton_default<T>::instance(), which is explicitly invoked inside fast_pool_allocator ctors via singleton_pool<...>is_from() (if you apply the fix, that is).
Quite right. I now agree your suggested fix is completely sound. And, in fact, making your suggested fix re-orders the construction of the pool and allocator objects as expected. I tried the experiment with GCC 4.1.2. The next step is for me to audit the remainder of the pool library and see if there are other clients of singleton_pool which are subject to the same potential failure mode. Are there any "better" suggestions out there for handling the singleton needs of the pool library? -Chris
participants (3)
-
Chris Newbold
-
Joaquin M Lopez Munoz
-
JOAQUIN M. LOPEZ MUÑOZ