Commit 9708c0e0 authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #1022 from gevent/issue1020

Speed up gevent.local
parents 13d860ae 143137a7
...@@ -54,6 +54,12 @@ ...@@ -54,6 +54,12 @@
- Monkey-patching after the :mod:`ssl` module has been imported now - Monkey-patching after the :mod:`ssl` module has been imported now
prints a warning because this can produce ``RecursionError``. prints a warning because this can produce ``RecursionError``.
- :class:`gevent.local.local` objects are now between 3 and 5 times faster
getting, setting and deleting attributes on CPython (the fastest
access occurs when ``local`` is not subclassed). This involved
implementing more of the attribute protocols directly. Please open
an issue if you have any compatibility problems. See :issue:`1020`.
1.2.2 (2017-06-05) 1.2.2 (2017-06-05)
================== ==================
......
This diff is collapsed.
...@@ -517,6 +517,8 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})): ...@@ -517,6 +517,8 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})):
self.assertEqual(sig.keywords, gevent_sig.keywords, func_name) self.assertEqual(sig.keywords, gevent_sig.keywords, func_name)
self.assertEqual(sig.defaults, gevent_sig.defaults, func_name) self.assertEqual(sig.defaults, gevent_sig.defaults, func_name)
if not hasattr(TestCase, 'assertRaisesRegex'):
TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp
main = unittest.main main = unittest.main
_original_Hub = gevent.hub.Hub _original_Hub = gevent.hub.Hub
......
...@@ -5,6 +5,16 @@ from gevent import monkey; monkey.patch_all() ...@@ -5,6 +5,16 @@ from gevent import monkey; monkey.patch_all()
from threading import local from threading import local
from threading import Thread
class ReadProperty(object):
"""A property that can be overridden"""
# A non-data descriptor
def __get__(self, inst, klass):
return 42 if inst is not None else self
class A(local): class A(local):
...@@ -12,7 +22,12 @@ class A(local): ...@@ -12,7 +22,12 @@ class A(local):
path = '' path = ''
type_path = 'MyPath'
read_property = ReadProperty()
def __init__(self, obj): def __init__(self, obj):
super(A, self).__init__()
if not hasattr(self, 'initialized'): if not hasattr(self, 'initialized'):
self.obj = obj self.obj = obj
self.path = '' self.path = ''
...@@ -36,20 +51,140 @@ class MyLocal(local): ...@@ -36,20 +51,140 @@ class MyLocal(local):
self.sentinel = Sentinel() self.sentinel = Sentinel()
created_sentinels.append(id(self.sentinel)) created_sentinels.append(id(self.sentinel))
class WithGetattr(local):
def __getattr__(self, name):
if name == 'foo':
return 42
return super(WithGetattr, self).__getattr__(name)
class GeventLocalTestCase(greentest.TestCase): class GeventLocalTestCase(greentest.TestCase):
# pylint:disable=attribute-defined-outside-init,blacklisted-name
def setUp(self):
del deleted_sentinels[:]
del created_sentinels[:]
tearDown = setUp
def test_create_local_subclass_init_args(self):
with self.assertRaisesRegex(TypeError,
"Initialization arguments are not supported"):
local("foo")
with self.assertRaisesRegex(TypeError,
"Initialization arguments are not supported"):
local(kw="foo")
def test_local_opts_not_subclassed(self):
l = local()
l.attr = 1
self.assertEqual(l.attr, 1)
def test_cannot_set_delete_dict(self):
l = local()
with self.assertRaises(AttributeError):
l.__dict__ = 1
with self.assertRaises(AttributeError):
del l.__dict__
def test_delete_with_no_dict(self):
l = local()
with self.assertRaises(AttributeError):
delattr(l, 'thing')
def del_local():
with self.assertRaises(AttributeError):
delattr(l, 'thing')
t = Thread(target=del_local)
t.start()
t.join()
def test_slot_and_type_attributes(self):
a = A(Obj())
a.initialized = 1
self.assertEqual(a.initialized, 1)
# The slot is shared
def demonstrate_slots_shared():
self.assertEqual(a.initialized, 1)
a.initialized = 2
greenlet = Thread(target=demonstrate_slots_shared)
greenlet.start()
greenlet.join()
self.assertEqual(a.initialized, 2)
# The slot overrides dict values
a.__dict__['initialized'] = 42
self.assertEqual(a.initialized, 2)
# Deleting the slot deletes the slot, but not the dict
del a.initialized
self.assertFalse(hasattr(a, 'initialized'))
self.assertIn('initialized', a.__dict__)
# We can delete the 'path' ivar
# and fall back to the type
del a.path
self.assertEqual(a.path, '')
with self.assertRaises(AttributeError):
del a.path
# A read property calls get
self.assertEqual(a.read_property, 42)
a.read_property = 1
self.assertEqual(a.read_property, 1)
self.assertIsInstance(A.read_property, ReadProperty)
# Type attributes can be read
self.assertEqual(a.type_path, 'MyPath')
self.assertNotIn('type_path', a.__dict__)
# and replaced in the dict
a.type_path = 'Local'
self.assertEqual(a.type_path, 'Local')
self.assertIn('type_path', a.__dict__)
def test_attribute_error(self):
# pylint:disable=attribute-defined-outside-init
a = A(Obj())
with self.assertRaises(AttributeError):
getattr(a, 'fizz_buzz')
def set_fizz_buzz():
a.fizz_buzz = 1
greenlet = Thread(target=set_fizz_buzz)
greenlet.start()
greenlet.join()
with self.assertRaises(AttributeError):
getattr(a, 'fizz_buzz')
def test_getattr_called(self):
getter = WithGetattr()
self.assertEqual(42, getter.foo)
getter.foo = 'baz'
self.assertEqual('baz', getter.foo)
def test_copy(self): def test_copy(self):
a = A(Obj()) a = A(Obj())
a.path = '123' a.path = '123'
a.obj.echo = 'test' a.obj.echo = 'test'
b = copy(a) b = copy(a)
"""
Copy makes a shallow copy. Meaning that the attribute path # Copy makes a shallow copy. Meaning that the attribute path
has to be independent in the original and the copied object because the # has to be independent in the original and the copied object because the
value is a string, but the attribute obj should be just reference to # value is a string, but the attribute obj should be just reference to
the instance of the class Obj # the instance of the class Obj
"""
self.assertEqual(a.path, b.path, 'The values in the two objects must be equal') self.assertEqual(a.path, b.path, 'The values in the two objects must be equal')
self.assertEqual(a.obj, b.obj, 'The values must be equal') self.assertEqual(a.obj, b.obj, 'The values must be equal')
...@@ -59,10 +194,17 @@ class GeventLocalTestCase(greentest.TestCase): ...@@ -59,10 +194,17 @@ class GeventLocalTestCase(greentest.TestCase):
a.obj.echo = "works" a.obj.echo = "works"
self.assertEqual(a.obj, b.obj, 'The values must be equal') self.assertEqual(a.obj, b.obj, 'The values must be equal')
def test_copy_no_subclass(self):
a = local()
setattr(a, 'thing', 42)
b = copy(a)
self.assertEqual(b.thing, 42)
self.assertIsNot(a.__dict__, b.__dict__)
def test_objects(self): def test_objects(self):
""" # Test which failed in the eventlet?!
Test which failed in the eventlet?!
"""
a = A({}) a = A({})
a.path = '123' a.path = '123'
b = A({'one': 2}) b = A({'one': 2})
...@@ -90,13 +232,15 @@ class GeventLocalTestCase(greentest.TestCase): ...@@ -90,13 +232,15 @@ class GeventLocalTestCase(greentest.TestCase):
getattr(my_local, 'sentinel') getattr(my_local, 'sentinel')
# Create and reference greenlets # Create and reference greenlets
greenlets = [gevent.spawn(demonstrate_my_local) for _ in range(5)] greenlets = [Thread(target=demonstrate_my_local) for _ in range(5)]
for t in greenlets:
t.start()
gevent.sleep() gevent.sleep()
self.assertEqual(len(created_sentinels), len(greenlets)) self.assertEqual(len(created_sentinels), len(greenlets))
for g in greenlets: for g in greenlets:
assert g.dead assert not g.is_alive()
gevent.sleep() # let the callbacks run gevent.sleep() # let the callbacks run
if greentest.PYPY: if greentest.PYPY:
gc.collect() gc.collect()
...@@ -110,15 +254,12 @@ class GeventLocalTestCase(greentest.TestCase): ...@@ -110,15 +254,12 @@ class GeventLocalTestCase(greentest.TestCase):
import gc import gc
gc.collect() gc.collect()
del created_sentinels[:]
del deleted_sentinels[:]
count = 1000 count = 1000
running_greenlet = None running_greenlet = None
def demonstrate_my_local(): def demonstrate_my_local():
for i in range(1000): for _ in range(1000):
x = MyLocal() x = MyLocal()
self.assertIsNotNone(x.sentinel) self.assertIsNotNone(x.sentinel)
x = None x = None
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment