0

I have a program that uses a singleton. This program loads a shared object library at runtime. This library also makes use of the same singleton. The problem is, that when accessing the singleton from the library, a new instance of the singleton is created.

The program is linked with -rdynamic, I use -fPIC for both and the loading happens like this:

std::shared_ptr<Module> createModuleObject(const std::string& filename)
{
    if (!fs::exists(filename))
        throw std::runtime_error("Library not found: " + std::string(filename));

    struct export_vtable* imports;
    void *handle = dlopen(filename.c_str(), RTLD_LAZY | RTLD_GLOBAL);

    if (handle) {
        imports = static_cast<export_vtable*>(dlsym(handle, "exports"));
        if (imports)
            return std::shared_ptr<Module>(imports->make());
        else 
            throw std::runtime_error("Error trying to find exported function in library!");
    } else
        throw std::runtime_error("Error trying to load library: " + std::string(filename));
}

The library exports a class like this:

Module* make_instance()
{
    return new HelloWorld();
}
struct export_vtable 
{
    Module* (*make)(void);
};
struct export_vtable exports = { make_instance };

and that class makes use of the singleton.

This is how the singleton is created (Configuration.cpp):

std::unique_ptr<Configuration> Configuration::instance_(nullptr);
std::once_flag Configuration::onlyOnceFlag_;

Configuration& Configuration::instance()
{
    if (instance_ == nullptr)
    {
        std::cout << "INSTANCE IS NULL, CREATING NEW ONE" << std::endl;
        std::call_once(Configuration::onlyOnceFlag_,
                    [] {
                            Configuration::instance_.reset(new Configuration());
                       });
    }

    return *Configuration::instance_;
}    

Both the program and the library link against the Configuration.cpp. If I omit that from the library, I get an undefined symbol error when trying to access the singleton.

Anyone got an idea how to solve this? Thank you very much!

Pfaeff
  • 105
  • 7
  • 1
    That's one of the reasons why singletons are a bad idea. They don't work as intended with dynamically linked libraries. Also rather stick to [Scott Meyer's Singleton Pattern](https://stackoverflow.com/questions/1008019/c-singleton-design-pattern). – user0042 Sep 13 '17 at 08:55
  • There is nothing wrong with singletons. There are two issues with this particular implementation: 1) You gave up control over singleton lifetime. Creating singleton at first call of getting instance method is never a good idea. 2) Your library creates its own singleton instead of requesting singleton created by parent application. To fix this `instance()` method should be imported from main application. P.S. Scott Meyer's Singleton Pattern should be called Antipattern. – user7860670 Sep 13 '17 at 09:00
  • It should be possible to re-use the same singleton inside the library though, shouldn't it? The problem seems to be the singleton inside the library shadowing the other one. Having a `setConfiguration()` method or something in the library is also not an option for me, since it kinda defeats the purpose. **EDIT:** what do you mean by "imported from main application"? – Pfaeff Sep 13 '17 at 09:05
  • The issue right now is that your library creates its own copy of `Configuration::instance_`, `Configuration::onlyOnceFlag_;` and `Configuration::instance()`. This happens because it links against the `Configuration.cpp`. You should link only main executable against it, export `Configuration::instance()` from main executable and link shared library plugin to main executable. This way no duplication will occur. – user7860670 Sep 13 '17 at 09:23
  • How do I "import `Configuration::instance()` from main executable"? If I just omit linking against the Configuration.cpp, I get the above mentioned symbol lookup error. – Pfaeff Sep 13 '17 at 09:25
  • You need to make sure that `Configuration::instance()` in main executable is visible (using attribute visibility = default or dllexport). And then link plugin shared library to main executable, just like if it was a shared library (i.e. -lmain_executable). – user7860670 Sep 13 '17 at 09:30
  • Linking the plugin shared library to the main executable would render the loading at runtime useless, wouldn't it? The main program shouldn't be dependent on the implementation of a plugin or have I misunderstood you? – Pfaeff Sep 13 '17 at 09:35
  • It seems to work when making the instance variable a static variable inside the scope of the `instace()` method as you mentioned. Then it even doesn't seem to matter if I use `RTLD_LOCAL` or `RTLD_GLOBAL`. Why is that? – Pfaeff Sep 13 '17 at 11:02
  • No, I'm talking about the opposite: linking main executable to shared library. It will make shared library dependent on the main executable. And it won't interfere with (optional) dynamic loading of shared library plugin into main executable. – user7860670 Sep 13 '17 at 18:40

1 Answers1

1

Here's how I solved it for https://github.com/kvahed/codeare/blob/master/src/core/ReconStrategy.hpp After having loaded the shared object I assign the instance of the global singleton Workspace to the loaded class in the dll. All classes in https://github.com/kvahed/codeare/tree/master/src/modules are derived from ReconStrategy and in shared objects. The good thing is that this code is portable.

When constructing such a ReconStrategy this happens:

ReconContext::ReconContext (const char* name) {
    m_dlib = LoadModule ((char*)name);
  if (m_dlib) {
    create_t* create = (create_t*) GetFunction (m_dlib, (char*)"create");
    m_strategy = create();
    m_strategy->Name (name);
    m_strategy->WSpace (&Workspace::Instance());
    } else {
      m_strategy = 0;
    }
  }
}

The key line here is m_strategy->WSpace (&Workspace::Instance());

Kaveh Vahedipour
  • 3,412
  • 1
  • 14
  • 22
  • That stops working as soon as you have a second singleton that tries accessing the first one (for example a Logging singleton). Then you have to have an assignment method for that too. It kinda defeats the purpose. – Pfaeff Sep 13 '17 at 09:09
  • Could please someone take a second to wait for a response before down voting a post that is meant to help. I will never understand where this motivation comes from. I only down vote malicious intent and great negligence. To the point this is valgrind tested code and runs all day on MRI machines without memory leaks and such. What I offer above allows every shared object to have its local workspace for private Matrices and a global for the reconstruction service to pass it on to reconstruction steps down the chain. Seriously, I won't get some folks. – Kaveh Vahedipour Sep 13 '17 at 09:21
  • So you would bundle up all singletons in a single workspace object and then assign that to your library? What if the singletons need to access each other? Let's say a logging singleton trying to determine the log level from a configuration singleton – Pfaeff Sep 13 '17 at 09:30
  • This is exactly what is done above. The shared object's local workspace singleton to each reconstruction strategy can access the global singleton to transfer matrices back and forth if needed. At end of the life time of the shared object, the local singleton is destructed and all local matrices freed. I have not a single crash or unintended behaviour in many years. Intiially I was considering this a hack myself, I am a physicist by training and suspicious of every line of code I write, but have arrived to actually like the solution. – Kaveh Vahedipour Sep 13 '17 at 09:34