0

Problem

I have been trying to create a Thread Safe SingletonClass as a module that can be reused by importing as a module in other python scripts. More like import and use it as a metaclass for the class that you want to make it as singleton.

But however when I run my program I run into a deadlock situation where other classes that import the module waits for some one before them who had used it as a metaclass to release. I understand I am missing a great piece of basics of python related to importing and namespace possibly and thats the reason I am unable to understand this. I have gone through the following articles before about singleton in general but I think this problem is related to imports than anything else.

Link : Creating a singleton in Python

Would be great if someone can point out the mistake in this and give a possible method to have it as a reusable module / give links to external article that can help me solve this. Let me know if this idea of mine will work or is it flawed. That will really help understand the basics properly.

My folder structure and corresponding code is given below.

The following is my folder structure with the corresponding code in each file.

.
├── A.py
├── B.py
├── C.py
└── ThreadSafeSingleton.py

0 directories, 4 files

Code

ThreadSafeSingleton.py

        import threading
        import logging

        logger= logging.getLogger(__name__)


        class ThreadSafeSingletonClass(type):
            # Taken from: https://stackoverflow.com/a/51897195 :)
            _instances = {}
            _singleton_lock = threading.Lock()
            def __call__(cls, *args, **kwargs):
                # double-checked locking pattern
                logger.error("ThreadSafeSingletonClass : Inside __call__ of ThreadSafeSingleton")
                logger.error("ThreadSafeSingletonClass : Called By"+str(cls))
                if cls not in cls._instances:
                    logger.error("ThreadSafeSingletonClass : Trying to get lock")
                    with cls._singleton_lock:
                        logger.error("ThreadSafeSingletonClass : Got lock")
                        if cls not in cls._instances:
                            logger.error("ThreadSafeSingletonClass : Object of type" + str(cls) + " being Created")
                            cls._instances[cls] = super(ThreadSafeSingletonClass, cls).__call__(*args, **kwargs)
                logger.error("ThreadSafeSingletonClass : Returning created Instance"+str(cls))
                return cls._instances[cls]

A.py

        import logging 
        from ThreadSafeSingleton import ThreadSafeSingletonClass
        from B import B

        logger=logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)

        class A(metaclass = ThreadSafeSingletonClass):
            def __init__(self):
                logger.error("A : Inside init of A")
                logger.error("A : Creating object of B")
                b=B()

        logger.error("A: Creating object of A as part of main")
        A()
        logger.error("A: Returned back to A now")
        logger.error("A: Exiting...")

B.py


        from ThreadSafeSingleton import ThreadSafeSingletonClass

        import C
        import logging

        logger = logging.getLogger(__name__)

        class B(metaclass=ThreadSafeSingletonClass):
            def __init__(self):
                logger.error("B: Inside Init of B")
                logger.error("B: Trying to create object of C")
                c=C.C()

C.py

        from ThreadSafeSingleton import ThreadSafeSingletonClass

        class C(metaclass = ThreadSafeSingletonClass):
            def __init__(self):
                logger.error("C: We are at C Object")

Output

(env) duplex@Test:~/test/Test$ python A.py 
A: Creating object of A as part of main
ThreadSafeSingletonClass : Inside __call__ of ThreadSafeSingleton
ThreadSafeSingletonClass : Called By<class '__main__.A'>
ThreadSafeSingletonClass : Trying to get lock
ThreadSafeSingletonClass : Got lock
ThreadSafeSingletonClass : Object of type<class '__main__.A'> being Created
A : Inside init of A
A : Creating object of B
ThreadSafeSingletonClass : Inside __call__ of ThreadSafeSingleton
ThreadSafeSingletonClass : Called By<class 'B.B'>
ThreadSafeSingletonClass : Trying to get lock

and it's stuck there. I am unable to understand since A is a separate singleton and B is a separate singleton, why is creation of B object dependant on someone else to release the lock ? If this is not the right way , then how can the objective of Singleton Class being reusable module be achieved.

Objective The objective is to have a singleton metaclass as a module so that any class which wants to achieve singleton just has to have it imported as part of its module and use it as a metaclass rather than defining the class each time in the each module file.

Background I am learning design patterns and have been working out on Singleton Patterns in Python. Currently I am trying to have a Thread Safe Singleton metaclass defined in a module which in turn is supposed to be imported by other classes as their own metaclass.

ram
  • 133
  • 6

1 Answers1

1

You are inside ThreadSafeSingleton.__call__ until A() returns the object. So if you call this function nested inside another call, you have to use a separate lock for each class.

class ThreadSafeSingletonClass(type):
    # Taken from: https://stackoverflow.com/a/51897195 :)
    _instances = {}
    _instance_locks = {}
    _singleton_lock = threading.Lock()
    def __call__(cls, *args, **kwargs):
        # double-checked locking pattern
        logger.debug("ThreadSafeSingletonClass : Inside __call__ of ThreadSafeSingleton")
        logger.debug(f"ThreadSafeSingletonClass : Called By {cls}")
        if cls not in cls._instances:
            logger.debug("ThreadSafeSingletonClass : Trying to get lock")
            with cls._singleton_lock:
                lock = cls._instance_locks.setdefault(cls, threading.Lock())
            with lock:
                logger.debug("ThreadSafeSingletonClass : Got lock")
                if cls not in cls._instances:
                    logger.debug(f"ThreadSafeSingletonClass : Object of type {cls} being Created")
                    cls._instances[cls] = super(ThreadSafeSingletonClass, cls).__call__(*args, **kwargs)
                    with cls._singleton_lock:
                        del cls._instance_locks[cls]
        logger.debug(f"ThreadSafeSingletonClass : Returning created Instance {cls}")
        return cls._instances[cls]

btw. the correct logging method is debug.

Daniel
  • 42,087
  • 4
  • 55
  • 81
  • Thanks.Now it makes sense to me. So basically the class ThreadSafeSingletonClass itself is a singleton because it being a module. And hence it didnt allow subsequent class to acquire and create object from it. Now we create a new dictionary store to store class instances and handle them separately . However, am getting error now with the code :- `File "ThreadSafeSingleton.py", line 22, in __call__ lock = _instance_locks.setdefault(cls, threading.Lock()) NameError: name '_instance_locks' is not defined` – ram Dec 25 '19 at 10:20
  • Since ThreadSafeSingleTon Class is being used as global store for maintaining singleton. cls reference in some places in your code was to be changed as `if cls not in __class__._instances:` , `class_instance_lock = __class__._instance_locks.setdefault(cls, threading.Lock())` , `__class__._instances[cls] = super(ThreadSafeSingletonClass, cls).__call__(*args, **kwargs)`, `del __class__._instance_locks[cls]` , `return __class__._instances[cls]` . cls was replaced with `__class__` . It works for me. Can you please edit your response if this is right. So that I can mark yours as the answer. – ram Dec 25 '19 at 10:37
  • `__class__` being the reference to the `ThreadSafeSingletonClass` and `cls` being reference to the class calling the `ThreadSafeSingletonClass`. – ram Dec 25 '19 at 10:42