From 92116a0cc935160ad6006c60c549b8a257142397 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Mon, 27 Feb 2017 05:13:33 -0800 Subject: [PATCH] Prevent double-subscribes with registry decorator This adds a simple flag to object instances to avoid multiple calls to 'subscribe' for a class with multiple children that use the 'has_registry_receivers' decorator. The registry manager protects from multiple subscriptions of the same function by storing callbacks based on IDs, but this is necessary to eliminate the duplicate debug logging statements. Change-Id: I38e0a5f4c361435ae4af6c16a407191b1e722e37 --- neutron/callbacks/registry.py | 6 ++++++ neutron/tests/unit/callbacks/test_registry.py | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/neutron/callbacks/registry.py b/neutron/callbacks/registry.py index df836217f47..e0647d0ae29 100644 --- a/neutron/callbacks/registry.py +++ b/neutron/callbacks/registry.py @@ -80,6 +80,11 @@ def has_registry_receivers(cls): def replacement_new(cls, *args, **kwargs): instance = orig_new(*args, **kwargs) + if getattr(instance, '_DECORATED_METHODS_SUBSCRIBED', False): + # Avoid running this logic twice for classes inheriting other + # classes with this same decorator. Only one needs to execute + # to subscribe all decorated methods. + return instance for name, unbound_method in inspect.getmembers(cls): if (not inspect.ismethod(unbound_method) and not inspect.isfunction(unbound_method)): @@ -91,6 +96,7 @@ def has_registry_receivers(cls): for resource, event in _REGISTERED_CLASS_METHODS[func]: # subscribe the bound method subscribe(getattr(instance, name), resource, event) + setattr(instance, '_DECORATED_METHODS_SUBSCRIBED', True) return instance cls.__new__ = classmethod(replacement_new) return cls diff --git a/neutron/tests/unit/callbacks/test_registry.py b/neutron/tests/unit/callbacks/test_registry.py index 178f255a715..3758c7a97af 100644 --- a/neutron/tests/unit/callbacks/test_registry.py +++ b/neutron/tests/unit/callbacks/test_registry.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +import mock + from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources @@ -30,6 +32,18 @@ class ObjectWithDecoratedCallback(object): self.counter += 1 +@registry.has_registry_receivers +class AnotherObjectWithDecoratedCallback(ObjectWithDecoratedCallback): + + def __init__(self): + super(AnotherObjectWithDecoratedCallback, self).__init__() + self.counter2 = 0 + + @registry.receives(resources.NETWORK, [events.AFTER_DELETE]) + def callback2(self, *args, **kwargs): + self.counter2 += 1 + + class CallBacksManagerTestCase(base.BaseTestCase): def test_decorated_inst_method_receives(self): @@ -49,3 +63,10 @@ class CallBacksManagerTestCase(base.BaseTestCase): registry.notify(resources.NETWORK, events.AFTER_DELETE, self) self.assertEqual(4, i1.counter) self.assertEqual(1, i2.counter) + + def test_object_inheriting_others_no_double_subscribe(self): + with mock.patch.object(registry, 'subscribe') as sub: + AnotherObjectWithDecoratedCallback() + # there are 3 methods (2 in parent and one in child) and 1 + # subscribes to 2 events, so we expect 4 subscribes + self.assertEqual(4, len(sub.mock_calls))