Best Practices when designing Pyro objects related to Threads

Here are my rules-of-thumb which may help others who are designing Pyro remote objects:

  1. Never design threads such that they use the same reference to a Pyro proxy. This is a documented no-no and leads to deadlocks. If you stick to rule 1 then this will not be a problem. (Update: this is no longer a requirement since Pyro 3.8, because they contain automatic thread locking inside the proxy)
  2. Avoid keeping persistent references to Pyro proxies in long-running servers. If the socket connection gets closed then the remote object will stop working. A more robust approach re-acquires the proxy each time it is used. There is a trade off here between robustness and performance.
  3. Be careful about using Pyro.core.SynchronizedObjBase. This class synchronizes access around the entire remote object method. If the method engages in a conversation with another server a deadlock is possible. The alternative is to derive from Pyro.core.ObjBase and use semaphores around the data accesses that must be synchronized.

  4. Rules of thumb for using Python semaphores in Pyro.
    1. Use the Python threading.BoundedSemaphore class for locking.

    2. Always Use try, finally to ensure the acquired semaphore gets released.
    3. Use a different semaphore for each object that needs to be synced, but only acquire one lock at a time. For example...

def GetPhoneAndEmail(self, user):
    # Get phone
    self.phoneLock.acquire()
    try:
        phone = self.phones[user]
    finally:
        self.phoneLock.release()
    
    # Get email
    self.emailLock.acquire()
    try:
        email = self.emails[user]
    finally:
        self.emailLock.release()

    return phone, email
  1. It is sometimes not necessary to synchronize simple reads of remote data objects. For example...

def GetCurrentLogins(self):
    # Just returning current state, no lock needed
    return self.logins.keys()
  1. When iterating over a sequence of shared data, always acquire/release around the entire iteration. Even if you are only reading the data. For example...

def GetAdmins(self):
    admins = []

    self.userLock.acquire()
    try:
        for user in self.users:
            if user.privileges == ADMIN:
                admins.append(user)
    finally:
        self.userLock.release()

    return admins
  1. Do not implement a pathological conversation. When two or more remote objects talk, it is possible to implement a deadlock scenario even if all other practices you are following are good. For example...

class Obj1:
    def GetFoosLikeABar(self):
        foos = []
        obj2Proxy   = self.GetObj2Proxy()

        self.fooLock.acquire()
        try:
            for foo in self.foos:
                if obj2Proxy.IsFooLikeABar(foo):
                    foos.append(foo)
        finally:
            self.fooLock.release()
        return foos


class Obj2:
    def IsFooLikeABar(self, foo):
        self.barLock.acquire()
        try:
            for bar in self.bars:
                if foo == bar:
                    return True
        finally:
            self.barLock.release()
        return False

    def ComposeBars(self):
        modBars = []
        obj1Proxy = self.GetObj1Proxy()

        self.barLock.acquire()
        try:
            foos = obj1Proxy.GetFoosLikeABar() # <--- Deadlock
            for bar in self.bars:
                modBars.append(self.ModBar(bar, foos))
        finally:
            self.barLock.release()
        return modBars

-Jason

PyroContrib/ThreadBestPractices (last edited 2010-01-06 01:12:17 by IrmenDeJong)