Reimplemented corolocal so as to implement __init__, local function calling, and, amazing, also leak fixing. Fixes #41.
This commit is contained in:
		@@ -1,30 +1,50 @@
 | 
			
		||||
import weakref
 | 
			
		||||
 | 
			
		||||
from eventlet import greenthread
 | 
			
		||||
 | 
			
		||||
__all__ = ['get_ident', 'local']
 | 
			
		||||
 | 
			
		||||
def get_ident():
 | 
			
		||||
    """ Returns ``id()`` of current greenlet.  Useful for debugging."""
 | 
			
		||||
    return id(greenthread.getcurrent())
 | 
			
		||||
 | 
			
		||||
# TODO: The base threadlocal class wants to call __init__ on itself for every new thread that associates with it; our corolocal doesn't do this, but should for 100% compatibility.  The implementation in _threading_local.py is so janky....
 | 
			
		||||
class local(object):
 | 
			
		||||
    """Coroutine equivalent of threading.local class."""
 | 
			
		||||
    def __getattribute__(self, attr, g=get_ident):
 | 
			
		||||
        try:
 | 
			
		||||
            d = object.__getattribute__(self, '__dict__')
 | 
			
		||||
            return d.setdefault('__objs', {})[g()][attr]
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            raise AttributeError(
 | 
			
		||||
                "No variable %s defined for the thread %s"
 | 
			
		||||
                % (attr, g()))
 | 
			
		||||
 | 
			
		||||
    def __setattr__(self, attr, value, g=get_ident):
 | 
			
		||||
        d = object.__getattribute__(self, '__dict__')
 | 
			
		||||
        d.setdefault('__objs', {}).setdefault(g(), {})[attr] = value
 | 
			
		||||
# the entire purpose of this class is to store off the constructor
 | 
			
		||||
# arguments in a local variable without calling __init__ directly
 | 
			
		||||
class _localbase(object):
 | 
			
		||||
    __slots__ = '_local__args', '_local__greens'
 | 
			
		||||
    def __new__(cls, *args, **kw):
 | 
			
		||||
        self = object.__new__(cls)
 | 
			
		||||
        object.__setattr__(self, '_local__args', (args, kw))
 | 
			
		||||
        object.__setattr__(self, '_local__greens', weakref.WeakKeyDictionary())
 | 
			
		||||
        if (args or kw) and (cls.__init__ is object.__init__):
 | 
			
		||||
            raise TypeError("Initialization arguments are not supported")
 | 
			
		||||
        return self        
 | 
			
		||||
        
 | 
			
		||||
def _patch(thrl):
 | 
			
		||||
    greens = object.__getattribute__(thrl, '_local__greens')
 | 
			
		||||
    # until we can store the localdict on greenlets themselves,
 | 
			
		||||
    # we store it in _local__greens on the local object
 | 
			
		||||
    cur = greenthread.getcurrent()
 | 
			
		||||
    if cur not in greens:
 | 
			
		||||
        # must be the first time we've seen this greenlet, call __init__
 | 
			
		||||
        greens[cur] = {}
 | 
			
		||||
        cls = type(thrl)
 | 
			
		||||
        if cls.__init__ is not object.__init__:
 | 
			
		||||
            args, kw = object.__getattribute__(thrl, '_local__args')
 | 
			
		||||
            thrl.__init__(*args, **kw)
 | 
			
		||||
    object.__setattr__(thrl, '__dict__', greens[cur])
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def __delattr__(self, attr, g=get_ident):
 | 
			
		||||
        try:
 | 
			
		||||
            d = object.__getattribute__(self, '__dict__')            
 | 
			
		||||
            del d.setdefault('__objs', {})[g()][attr]
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            raise AttributeError(
 | 
			
		||||
                "No variable %s defined for thread %s"
 | 
			
		||||
                % (attr, g()))
 | 
			
		||||
class local(_localbase):
 | 
			
		||||
    def __getattribute__(self, attr):
 | 
			
		||||
        _patch(self)
 | 
			
		||||
        return object.__getattribute__(self, attr)
 | 
			
		||||
 | 
			
		||||
    def __setattr__(self, attr, value):
 | 
			
		||||
        _patch(self)
 | 
			
		||||
        return object.__setattr__(self, attr, value)
 | 
			
		||||
 | 
			
		||||
    def __delattr__(self, attr):
 | 
			
		||||
        _patch(self)
 | 
			
		||||
        return object.__delattr__(self, attr)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
import weakref
 | 
			
		||||
from eventlet.green import thread
 | 
			
		||||
from eventlet import greenthread
 | 
			
		||||
from eventlet import event
 | 
			
		||||
import eventlet
 | 
			
		||||
from eventlet import corolocal
 | 
			
		||||
 | 
			
		||||
from tests import LimitedTestCase
 | 
			
		||||
from tests import LimitedTestCase, skipped
 | 
			
		||||
 | 
			
		||||
class Locals(LimitedTestCase):
 | 
			
		||||
    def passthru(self, *args, **kw):
 | 
			
		||||
@@ -18,6 +20,7 @@ class Locals(LimitedTestCase):
 | 
			
		||||
        self.results = []
 | 
			
		||||
        super(Locals, self).tearDown()
 | 
			
		||||
 | 
			
		||||
    @skipped  # cause it relies on internal details of corolocal that are no longer true
 | 
			
		||||
    def test_simple(self):
 | 
			
		||||
        tls = thread._local()
 | 
			
		||||
        g_ids = []
 | 
			
		||||
@@ -37,3 +40,72 @@ class Locals(LimitedTestCase):
 | 
			
		||||
        self.failUnlessRaises(AttributeError, lambda: tls.value)
 | 
			
		||||
        evt.send("done")
 | 
			
		||||
        eventlet.sleep()
 | 
			
		||||
 | 
			
		||||
    def test_assignment(self):
 | 
			
		||||
        my_local = corolocal.local()
 | 
			
		||||
        my_local.a = 1
 | 
			
		||||
        def do_something():
 | 
			
		||||
            my_local.b = 2
 | 
			
		||||
            self.assertEqual(my_local.b, 2)
 | 
			
		||||
            try:
 | 
			
		||||
                my_local.a
 | 
			
		||||
                self.fail()
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                pass
 | 
			
		||||
        eventlet.spawn(do_something).wait()
 | 
			
		||||
        self.assertEqual(my_local.a, 1)
 | 
			
		||||
        
 | 
			
		||||
    def test_calls_init(self):
 | 
			
		||||
        init_args = []
 | 
			
		||||
        class Init(corolocal.local):
 | 
			
		||||
            def __init__(self, *args):
 | 
			
		||||
                init_args.append((args, eventlet.getcurrent()))
 | 
			
		||||
        
 | 
			
		||||
        my_local = Init(1,2,3)
 | 
			
		||||
        self.assertEqual(init_args[0][0], (1,2,3))
 | 
			
		||||
        self.assertEqual(init_args[0][1], eventlet.getcurrent())
 | 
			
		||||
        
 | 
			
		||||
        def do_something():
 | 
			
		||||
            my_local.foo = 'bar'
 | 
			
		||||
            self.assertEqual(len(init_args), 2, init_args)
 | 
			
		||||
            self.assertEqual(init_args[1][0], (1,2,3))
 | 
			
		||||
            self.assertEqual(init_args[1][1], eventlet.getcurrent())
 | 
			
		||||
            
 | 
			
		||||
        eventlet.spawn(do_something).wait()
 | 
			
		||||
        
 | 
			
		||||
    def test_calling_methods(self):
 | 
			
		||||
        class Caller(corolocal.local):
 | 
			
		||||
            def callme(self):
 | 
			
		||||
                return self.foo
 | 
			
		||||
        
 | 
			
		||||
        my_local = Caller()
 | 
			
		||||
        my_local.foo = "foo1"
 | 
			
		||||
        self.assertEquals("foo1", my_local.callme())
 | 
			
		||||
        
 | 
			
		||||
        def do_something():
 | 
			
		||||
            my_local.foo = "foo2"
 | 
			
		||||
            self.assertEquals("foo2", my_local.callme())
 | 
			
		||||
            
 | 
			
		||||
        eventlet.spawn(do_something).wait()        
 | 
			
		||||
            
 | 
			
		||||
        my_local.foo = "foo3"
 | 
			
		||||
        self.assertEquals("foo3", my_local.callme())
 | 
			
		||||
        
 | 
			
		||||
    def test_no_leaking(self):
 | 
			
		||||
        refs = weakref.WeakKeyDictionary()
 | 
			
		||||
        my_local = corolocal.local()
 | 
			
		||||
        class X(object):
 | 
			
		||||
            pass
 | 
			
		||||
        def do_something(i):
 | 
			
		||||
            o = X()
 | 
			
		||||
            refs[o] = True
 | 
			
		||||
            my_local.foo = o
 | 
			
		||||
            
 | 
			
		||||
        p = eventlet.GreenPool()
 | 
			
		||||
        for i in xrange(100):
 | 
			
		||||
            p.spawn(do_something, i)
 | 
			
		||||
        p.waitall()
 | 
			
		||||
        del p
 | 
			
		||||
        # at this point all our coros have terminated
 | 
			
		||||
        self.assertEqual(len(refs), 1)
 | 
			
		||||
        
 | 
			
		||||
		Reference in New Issue
	
	Block a user