Boost logo

Boost :

From: Joaquin M Lopez Munoz (joaquin_at_[hidden])
Date: 2008-07-08 18:05:37


Chris Newbold <Chris.Newbold <at> mathworks.com> writes:

> Pool's fast_pool_allocator class uses singleton_pool to manage a single
underlying pool based on the
> requested size. Singleton_pool in turn uses singleton_default (found in
pool/detail/singleton.hpp)
> which tries _really_ hard to force the compiler to construct the controlled
instance at just the right time.
>
> What's happening in your test case is that the namespace-scoped
owned_base::pool instance (line 187 in
> sh_owned_base_nt.hpp) is actually being constructed by the compiler _before_
the namespace-scoped
> initializer object (called create_object, at line 95 in
pool/detail/singleton.hpp). That in turn
> results in the compiler destructing the singleton pool _before_ destructing
your owned_base::pool
> instance. Thus the memory containing the list nodes of the two std::list
instanced contained in
> owned_base::pool is has been freed by the time the list destructors run.
>
> I'm a little hard-pressed to make a call as to whether the problem is with
GCC's initialization ordering or
> whether the "cleverness" in pool/detail/singleton.hpp is simply bankrupt and
is trying to guarantee
> something which the language standard cannot.
>
> The namespace-scoped initializer generated in pool/detail/singleton.hpp
lexically precedes the
> declaration of the client in sh_owned_base_nt.hpp. Since this test case is a
single translation unit,
> this should in theory ensure that the former is constructed before the latter.
However, there's some
> indirection and function-scoped static objects in play here, too, which may
render that guarantee meaningless.
>
> I'd be interested to get some additional eyes on the code in
pool/detail/singleton.hpp (there's a decent
> comment there about how it's supposed to work) and see if there can be a
reasonable expectation for it to
> work on a conforming compiler...

Hello, I did look hard into pool/detail/singleton.hpp in the
past because it deals with a problem that I also had to face in
the implementation of Boost.Flyweight. These are my conclusions
wrt to pool/detail/singleton.hpp and with respect to the particular
problem you're now studying:

* pool/detail/singleton.hpp claims that singleton_default<T>::instance()
is automatically called before main() if no user code does it
explicitly. Strictly speaking, the implementation does not
guarantee this, but only that singleton_default<T>::instance()
will be called during the so-called dynamic initialization phase
of the program startup sequence. For all practical purposes,
however, this is equivalent to the orignal claim, so the problem
does not lie here.

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

This sequence is consistent with the guarantees provided by
singleton_default<T>. The problem lies in the design of
fast_pool_allocator: as it stands, the construction
of a fast_pool_allocator does *not* force the prior construction
of the internal singleton instance, and this is a mistake.
If my analysis is correct, to fix the problem one need only
ensure that his construction order is preserved for instance by
explicitly using singleton_pool<...> inside fast_pool_allocator
ctors like this:

    fast_pool_allocator()
    {
      singleton_pool<fast_pool_allocator_tag, sizeof(T),
        UserAllocator, Mutex, NextSize>::is_from(0);
    }

    template <typename U>
    fast_pool_allocator(
        const fast_pool_allocator<U, UserAllocator, Mutex, NextSize> &)
    {
      singleton_pool<fast_pool_allocator_tag, sizeof(T),
        UserAllocator, Mutex, NextSize>::is_from(0);
    }

If you try this please let me know the result. Hope this helps,

Joaquín M López Muñoz
Telefónica, Investigación y Desarrollo


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