4

I've created simple shared library that contains singleton class and I expect this class to behave accordingly, i.e. it will be a real singleton for all applications that will use it. But in fact it turned out that it works differently. Each application that uses my shared library creates its own instance of singleton which doesn't fit my plan at all.

This is code of the shared library:

singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

#if defined _WIN32 || defined __CYGWIN__
  #ifdef BUILDING_DLL
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllexport))
    #else
      #define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #else
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllimport))
    #else
      #define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #endif
  #define DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define DLL_PUBLIC
    #define DLL_LOCAL
  #endif
#endif

class DLL_PUBLIC Singleton
{
public:   
    static Singleton &instance();
    int test();

private:
    Singleton();
    int m_num;
};

#endif

singleton.cpp

#include "singleton.h"

Singleton &Singleton::instance()
{
    static Singleton singleton;
    return singleton;
}

Singleton::Singleton() :
    m_num(0)
{ }

int Singleton::test()
{
  return ++m_num;
}

compiling and linking as following:

g++ -c -pipe -fPIC -o singleton.o singleton.cpp
g++ -rdynamic -export-dynamic -shared -Wl,-soname,libSingletonLib.so.1 -o libSingletonLib.so.1.0.0 singleton.o

Small testing utility:

main.cpp

#include <stdio.h>
#include "singleton.h"

int main(int argc, char *argv[])
{    
    int num = Singleton::instance().test();
    printf("num: %d", num);
    getchar();

    return 0;
}

and compiling and linking options:

g++ -c -pipe -g -std=gnu++11 -Wall -W -fPIC -o main.o main.cpp
g++ -Wl -o ../SingletonTest main.o   -L.. -lSingletonLib 

Now I run 2 instances of the test application and so I expect both of them will use the singleton and the number will increment. But unexpectedly the output is:

first instance:

./SingletonTest 
num: 1

second instance:

./SingletonTest
num: 1

Note: the first application still runs when the second one started.

As I understand each instance of the application creates different instance of the singleton.

How can I avoid this situation so all the instances that had linked with the same shared library will use the only one singleton?

I use:

  • Ubuntu 18.04

  • gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0

Update: Ok, it looks that I have to use shared memory or some interprocess communication. I've never worked with that before so I'll rephrase the question: How can I use the one and only one singleton for several process? How can I put it in shared memory or whatever?

folibis
  • 12,048
  • 6
  • 54
  • 97
  • 1
    Perhaps by putting your singleton in a shared memory and find a way to keep your shared memory alive as long as you need it – Gojita Mar 27 '19 at 08:46
  • I think you need some sort of IPC – Mayur Mar 27 '19 at 08:46
  • https://stackoverflow.com/q/19373061/10035556 – Mayur Mar 27 '19 at 08:52
  • Sorry @Mayur, I don't understand what should I do by this link. – folibis Mar 27 '19 at 08:56
  • 3
    Possible duplicate of [Sharing same variable between more than one independent programs in Linux](https://stackoverflow.com/questions/10684499/sharing-same-variable-between-more-than-one-independent-programs-in-linux) – user1810087 Mar 27 '19 at 08:57
  • 1
    @folibis This link shows that `Each process has its own address space, meaning that there is never any memory being shared between processes (unless you use some inter-process communication library or extensions).` – Mayur Mar 27 '19 at 08:58
  • @user1810087 Closing as a duplicate of a closed question feels ill advised. – Passer By Mar 27 '19 at 09:00
  • 1
    ^ Same link I have shared – Mayur Mar 27 '19 at 09:03
  • I really don't understand how to use that. How can I put the singleton in the shared memory? the links didn't answer the question. – folibis Mar 27 '19 at 09:08
  • My apologies, I didn't read your question correctly. – Passer By Mar 27 '19 at 09:14
  • On Linux you cannot to it directly with in the *.so and have to use some sort oft IPC. As in my link some methods were mentioned. If boost is an option: [boost interprocess](https://www.boost.org/doc/libs/1_69_0/doc/html/interprocess.html), [boost::interprocess::shared_ptr](https://www.boost.org/doc/libs/1_69_0/doc/html/interprocess/interprocess_smart_ptr.html#interprocess.interprocess_smart_ptr.shared_ptr) or [this](https://www.boost.org/doc/libs/1_69_0/doc/html/interprocess/managed_memory_segments.html#interprocess.managed_memory_segments.managed_memory_segment_features.unique) – user1810087 Mar 27 '19 at 09:23
  • Another good answer: https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam – Erik Alapää Mar 27 '19 at 09:44

1 Answers1

1

Ok, after a long search I guess I've found a solution (or perhaps workaround). For some reason, I completely overlooked that shared library shares code but doesn't share data. Each application which uses the same shared library creates its copy of the singleton. So I understand that I need to use IPC as suggested in the comments (thanks @ Gojita and @Mayur).

That's the code I came to:

#include <new>
#include <errno.h>

...

Singleton &Singleton::instance()
{
    //static Singleton instance;
    //return instance;
    static Singleton* instance = getSharedMemory();
    return *instance;
}

Singleton * Singleton::getSharedMemory()
{
    Singleton * instance = nullptr;
    bool already_exists = false;

    int shm = shm_open("my_memory", O_CREAT | O_RDWR | O_EXCL, 0777);
    if(shm == -1)
    {
        if(errno == EEXIST)
        {
            already_exists = true;
            shm = shm_open("my_memory", O_CREAT | O_RDWR , 0777);
        }
        if(shm == -1)
        {
            perror("shm_open error");
            return nullptr;
        }
    }

    void *addr = mmap(nullptr, sizeof(Singleton) + 1, PROT_WRITE | PROT_READ, MAP_SHARED, shm, 0);
    if(!already_exists)
    {
        if (ftruncate(shm, sizeof(Singleton) + 1) == -1)
        {
            perror("ftruncate error");
            return nullptr;
        }
        instance = new(addr) Singleton;
    }
    else
        instance = reinterpret_cast<Singleton *>(addr);


    return instance;
}

notes: in the code below I create shared memory area (named "my_memory"). If the memory has already allocated (flag O_EXCL does the work) I just use it, otherwise I set its size (using ftruncate) and create my singleton using placing new in the memory. As a bonus - you can exit the application, then enter it again and use the singleton as is - the class still remains in the memory. The memory can be freed by calling munmap and shm_unlink or by rebooting the OS.

folibis
  • 12,048
  • 6
  • 54
  • 97