Opened 10 years ago

Closed 10 years ago

Last modified 10 years ago

#8414 closed Bugs (invalid)

flyweights causes weird library initialization error

Reported by: charles quarra <charlls_quarra@…> Owned by: Joaquín M López Muñoz
Milestone: To Be Determined Component: flyweight
Version: Boost 1.53.0 Severity: Showstopper
Keywords: Cc:

Description

the bug is reproduced with the following test case:

g++ main.cpp -o xmain -ldl

main.cpp: #include <dlfcn.h> #include <iostream> int main() {

std::cout << "load: " << std::endl; void *foo = dlopen("./libpluginTest.so", RTLD_NOW); std::cout << "load: library loaded "<< foo << std::endl; if (dlclose(foo) < 0) {

std::cout << "load: library close failed " << std::endl; return 1;

} std::cout << "load: library closed " << std::endl; return 0;

}

--- g++ -I/path/to/boost/includes -fPIC -shared -rdynamic test_plugin.cpp -o libpluginTest.so -L/path/to/boost/libs -lrt

test_plugin.cpp: #include <iostream> #include <boost/flyweight/intermodule_holder.hpp> #include <boost/flyweight.hpp>

typedef boost::flyweight< std::string > SymbolName_t;

SymbolName_t getEmptyStringSymbol() {

SymbolName_t empty(""); return empty;

}

void attribute ((constructor)) library_init() {

std::cout << "Library constructor: " << std::endl;

} void attribute ((destructor)) library_fini() {

std::cout << "Library destructor:" << std::endl;

}

this program outputs the following:

load: Library constructor: address of static cbContainer is: load: library loaded 0x1226030 load: library closed Library destructor:

as this shows, the library destructor is called at program termination, and NOT before dlclose returns, which is the expected behavior. If we comment the function 'getEmptyStringSymbol', the resulting output is:

load: Library constructor: address of static cbContainer is: load: library loaded 0x1b0b030 Library destructor: load: library closed

which is the expected behavior

this bug has been reproduced on the following compilers:

g++-4.6 (Ubuntu/Linaro 4.6.3-10ubuntu1) 4.6.3 20120918 (prerelease) g++-4.7 (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2

Attachments (1)

test_case (1.1 KB ) - added by charles quarra <charlls_quarra@…> 10 years ago.

Download all attachments as: .zip

Change History (9)

by charles quarra <charlls_quarra@…>, 10 years ago

Attachment: test_case added

comment:1 by Joaquín M López Muñoz, 10 years ago

Resolution: invalid
Status: newclosed

Hi Charles,

I've been examing the scenario and these are my conclusions (which lead me to think this is not a bug in Boost.Flyweight, hence I'm closing the ticket, please feel free to reopen if in disagreement):

You're checking the succesfull closing of the dynamic library incorrectly:

    if (dlclose(foo) < 0) {
        ...
    }

when it should be

    if (dlclose(foo) != 0) {
        ...
    }

My thesis is then that dlclose is then failing to close the dynamic lib because, according to the Linux documentation on the command, some other module is sharing some symbol with it. I think the shared symbol is getEmptyStringSymbol and the sharing module is 'libpluginTest.so' again, which, additionally to its being loaded dynamically, it's also linked at build time:

g++ -I/path/to/boost/includes -fPIC -shared -rdynamic test_plugin.cpp -o libpluginTest.so -L/path/to/boost/libs -lrt

My take is that if you omit -o libpluginTest.so everything will behave as you expect.

Best regards,

comment:2 by charles quarra <charlls_quarra@…>, 10 years ago

Hi joaquin,

i don't understand what makes you believe the plugin is being link at build time; main.cpp is the executable doing the module, and it is being built with the simple command: g++ main.cpp -o xmain -ldl as you see, nothing besides the libdl for dlopen and dlclose are being linked.

the command line 'g++ -I/path/to/boost/includes -fPIC -shared -rdynamic test_plugin.cpp -o libpluginTest.so -L/path/to/boost/libs -lrt' it is the one building and linking the plugin. Since it is a single translation unit, the linking can be done at once. The -o libxxxx.so is not required but it is a cosmetic choice to avoid having to link a library called a.out

i hope this helps

comment:3 by Joaquín M López Muñoz, 10 years ago

Yep, my fault, I skimmed through the test case and thought the referred to commandline was the one used for building the main executable.

OK, so we have dlclose failing for no apparent reason (can you confirm dlclose indeed returns something !=0?) Boost.Flyweight creates static objects for the value factory using this so-called holder:

template<typename C>
struct static_holder_class:holder_marker
{
  static C& get()
  {
    static C c;
    return c;
  }
};

which looks suspiciously similar to the problem described at

http://stackoverflow.com/questions/11050693/dlclose-doesnt-work-with-factory-function-complex-static-in-function

Could you try using --no-gnu-unique as desribed in the post above?

comment:4 by charles quarra <charlls_quarra@…>, 10 years ago

that option is only available at build's time of the compiler, is not a run-time option. However that problem description looks pretty similar to what we are seeing here.

So you are saying that this is a compiler bug/mis-feature?

comment:5 by charles quarra <charlls_quarra@…>, 10 years ago

I installed the gold plugin and build the plugin with this:

g++ -I/path/to/boost/includes -fPIC -shared -rdynamic -Wl,--no-gnu-unique test_plugin.cpp -o libpluginTest.so -L/path/to/boost/libs -lrt

$ ld --version GNU gold (GNU Binutils for Ubuntu 2.22.90.20120924) 1.11

running the program produces the expected result: the library is closed before dlclose returns:

load: Library constructor: address of static cbContainer is: load: library loaded 0x2237030 Library destructor: load: library closed

so this is indeed a bug or misfeature of ld. I have no idea why this is the default behavior.

comment:6 by Joaquín M López Muñoz, 10 years ago

Seems like a bug to me, rather than default behavior. Unfortunately GCC is not my area of expertise. Let's do a final try: you can resort to a user-defined holder with a slightly different behavior wrt static construction:

template<typename C>
struct GCC_dyn_holder_class:boost::flyweights::holder_marker
{
  static C c;
  static C& get()
  {
    return c;
  }
};

template<typename C> C GCC_dyn_holder_class<C>::c;

struct GCC_dyn_holder:boost::flyweights::holder_marker
{
  template<typename C>
  struct apply
  {
    typedef GCC_dyn_holder_class<C> type;
  };
};

typedef boost::flyweight<std::string,GCC_dyn_holder> SymbolName_t;

Does this change anything?

comment:7 by charles quarra <charlls_quarra@…>, 10 years ago

your sample with GCC_dyn_holder, using gold linker *WITHOUT* the --no-gnu-unique option, make dlclose behave *as expected*. But it seems to me that this kind of static holder (variable instead of function local) is not shared across modules. What do you think this implies?

comment:8 by Joaquín M López Muñoz, 10 years ago

OK, we're getting somewhere :-)

But it seems to me that this kind of static holder (variable instead of function local) is not shared across modules.

In reality, moving c from function-static to global-static has nothing to do with module sharing, but it breaks some promises the lib makes as for when the flyweight factory is initialized (technically, during the so-called dynamic initialization phase of the program startup sequence.) Anyway, if you're not declaring any flyweight-dependent static variable in your module, then you're safe.

Now, about module sharing. As far as I know this does not make a difference (furthermore, given the symbol sharing capabilities of GCC you probably don't need anything like intermodule_holder.) But you can find out by yourself: just independently create two SymbolName_ts in different modules with the same content and check whether they're equal (which happens if they both point internally to the same value, hence sharing factory.) It is important that the two objects be created independently (that is, not one copied from the other) for the test to be valid.

Good luck,

Note: See TracTickets for help on using tickets.