ActiveState Code

Recipe 159143: A ThreadedProxy wrapper


You want to wrap a general object to make any acess to it thread safe. The ThreadedProxy class accomplishes that.

needs Python 2.2

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""
History:
version 1.2:
- callables and non-callables have distinct classes, now.
- Added a simple factory function TProxy.
"""

#Import modules.
from thread import allocate_lock as Lock


class TObjectProxy(object):
    """The TObjectProxy class wrapper."""

    def __init__(self, obj, lock = None):
        """The initializer."""
        if lock is None:
            lock = Lock()
        super(TObjectProxy, self).__init__(obj, lock)
        #I'm paranoid => attributes are private.
        super(TObjectProxy, self).__setattr__('_TObjectProxy__obj', obj)
        super(TObjectProxy, self).__setattr__('_TObjectProxy__lock', lock)

    def __repr__(self):
        lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
        obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
        cls = super(TObjectProxy, self).__getattribute__('__class__')
        return "%s<%r, %r>" % (cls.__name__, obj, lock)

    #Every attribute get/set is wrapped between locks.
    def __getattribute__(self, name):
        lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
        #Marshall everything else to wrapped object
        obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
        lock.acquire()
        try:
            ret = TProxy(getattr(obj, name), lock)
        finally:
            lock.release()
        return ret

    def __setattr__(self, name, value):
        obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
        lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
        lock.acquire()
        try:
            setattr(obj, name, value)
        finally:
            lock.release()

    def __delattr__(self, name):
        obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
        lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
        lock.acquire()
        try:
            delattr(obj, name)
        finally:
            lock.release()


class TCallableProxy(TObjectProxy):
    """The TCallableProxy class wrapper."""

    def __init__(self, handler, lock = None):
        """The initializer."""
        if lock is None:
            lock = Lock()
        if callable(handler):
            super(TCallableProxy, self).__init__(handler, lock)
        else:
            raise TypeError("Object not callable.", handler)

    def __call__(self, *args, **kwargs):
        #Get obj and lock => Note how super call uses TObjectProxy.
        obj = super(TObjectProxy, self).__getattribute__('_TObjectProxy__obj')
        lock = super(TObjectProxy, self).__getattribute__('_TObjectProxy__lock')
        lock.acquire()
        try:
            ret = obj(*args, **kwargs)
        finally:
            lock.release()
        return ret


def TProxy(obj, lock = RLock()):
    """A factory function wrapping an object in a thread-safe wrapper."""
    if callable(obj):
        return TCallableProxy(obj, lock)
    else:
        return TObjectProxy(obj, lock)

Discussion

I end up writing a lot of boilerplate code to make acesses to an object thread-safe. So I started musing how could one do this in general way. The strategy is simple: define __getattribute__ and __setattr__ and put a lock around everything that smells of change. This includes wrapping the returned attribute in ThreadedProxy since it can be mutable or it can be a callable changing the object (e.g. think of append for a list).

There are caveats with this solution. Every attribute acess is slowed down. There are locks around everything. It is prolly very easy to enter in a starvation condition. And these are the ones that I can remember off the top of my head. Still, I derived the intelectual satisfaction of pulling this coup off, and in many conditions it can substitute boilerplate boring code.

Have fun! Gonçalo Rodrigues

Sign in to comment