Pass receiver to :GC.subscribeDestruction explicitly

Makes the subscriber object be an explicit parameter of
destruction dependency subscribe/unsibscribe method. It makes
possible use of this functions from static methods and to link
other objects.

Targets-blueprint: dependency-driven-resource-deallocation
Change-Id: I2554385bf2ebc6849f09939530a7c090fb745778
This commit is contained in:
Stan Lagun 2016-09-06 23:47:22 -07:00
parent 584571c3d8
commit c47269128b
4 changed files with 76 additions and 67 deletions

View File

@ -12,60 +12,56 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo_log import log as logging
from yaql.language import specs from yaql.language import specs
from yaql.language import yaqltypes from yaql.language import yaqltypes
from murano.dsl import dsl from murano.dsl import dsl
from murano.dsl import helpers from murano.dsl import helpers
LOG = logging.getLogger(__name__)
@dsl.name('io.murano.system.GC') @dsl.name('io.murano.system.GC')
class GarbageCollector(object): class GarbageCollector(object):
@staticmethod @staticmethod
@specs.parameter('target', dsl.MuranoObjectParameter()) @specs.parameter('publisher', dsl.MuranoObjectParameter())
@specs.parameter('subscriber', dsl.MuranoObjectParameter())
@specs.parameter('handler', yaqltypes.String(nullable=True)) @specs.parameter('handler', yaqltypes.String(nullable=True))
def subscribe_destruction(target, handler=None): def subscribe_destruction(publisher, subscriber, handler=None):
caller_context = helpers.get_caller_context() publisher_this = publisher.object.real_this
target_this = target.object.real_this subscriber_this = subscriber.object.real_this
receiver = helpers.get_this(caller_context)
if handler: if handler:
receiver.type.find_single_method(handler) subscriber.type.find_single_method(handler)
dependency = GarbageCollector._find_dependency( dependency = GarbageCollector._find_dependency(
target_this, receiver, handler) publisher_this, subscriber_this, handler)
if not dependency: if not dependency:
dependency = {'subscriber': helpers.weak_ref(receiver), dependency = {'subscriber': helpers.weak_ref(subscriber_this),
'handler': handler} 'handler': handler}
target_this.dependencies.setdefault( publisher_this.dependencies.setdefault(
'onDestruction', []).append(dependency) 'onDestruction', []).append(dependency)
@staticmethod @staticmethod
@specs.parameter('target', dsl.MuranoObjectParameter()) @specs.parameter('publisher', dsl.MuranoObjectParameter())
@specs.parameter('subscriber', dsl.MuranoObjectParameter())
@specs.parameter('handler', yaqltypes.String(nullable=True)) @specs.parameter('handler', yaqltypes.String(nullable=True))
def unsubscribe_destruction(target, handler=None): def unsubscribe_destruction(publisher, subscriber, handler=None):
caller_context = helpers.get_caller_context() publisher_this = publisher.object.real_this
target_this = target.object.real_this subscriber_this = subscriber.object.real_this
receiver = helpers.get_this(caller_context)
if handler: if handler:
receiver.type.find_single_method(handler) subscriber.type.find_single_method(handler)
dds = target_this.dependencies.get('onDestruction', []) dds = publisher_this.dependencies.get('onDestruction', [])
dependency = GarbageCollector._find_dependency( dependency = GarbageCollector._find_dependency(
target_this, receiver, handler) publisher_this, subscriber_this, handler)
if dependency: if dependency:
dds.remove(dependency) dds.remove(dependency)
@staticmethod @staticmethod
def _find_dependency(target, subscriber, handler): def _find_dependency(publisher, subscriber, handler):
dds = target.dependencies.get('onDestruction', []) dds = publisher.dependencies.get('onDestruction', [])
for dd in dds: for dd in dds:
if dd['handler'] != handler: if dd['handler'] != handler:
continue continue

View File

@ -6,6 +6,17 @@ Methods:
Body: Body:
- $.find(Node) - $.find(Node)
foo:
Body:
- trace(foo)
destructionHandler:
Arguments:
- obj:
Contract: $.class(TestGCNode).notNull()
Body:
- trace('Destruction of {0}'.format($obj.value))
.destroy: .destroy:
Body: Body:
- trace($.value) - trace($.value)
@ -37,8 +48,9 @@ Methods:
:TestGCNode: :TestGCNode:
value: B value: B
- $x: new($model) - $x: new($model)
- sys:GC.subscribeDestruction($x, _handler) - sys:GC.subscribeDestruction($x, $this, _handler)
- sys:GC.subscribeDestruction($x.nodes[0], _handler) - sys:GC.subscribeDestruction($x.nodes[0], $this, _handler)
- sys:GC.subscribeDestruction($x.nodes[0], $x, destructionHandler)
- $x: null - $x: null
- sys:GC.collect() - sys:GC.collect()

View File

@ -53,4 +53,6 @@ class TestGC(test_case.DslTestCase):
def test_collect_with_subscription(self): def test_collect_with_subscription(self):
self.runner.testObjectsCollectWithSubscription() self.runner.testObjectsCollectWithSubscription()
self.assertEqual(['Destroy A', 'Destroy B', 'B', 'A'], self.traces) self.assertEqual(
['Destroy A', 'Destroy B', 'Destruction of B', 'B', 'A'],
self.traces)

View File

@ -12,11 +12,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import _weakref import weakref
import mock import mock
from testtools import matchers from testtools import matchers
import unittest
from murano.dsl import dsl from murano.dsl import dsl
from murano.dsl import exceptions from murano.dsl import exceptions
@ -31,57 +30,57 @@ class TestGarbageCollector(base.MuranoTestCase):
def setUp(self): def setUp(self):
super(TestGarbageCollector, self).setUp() super(TestGarbageCollector, self).setUp()
self.mock_subscriber = mock.MagicMock(spec=murano_object.MuranoObject) subscriber = mock.MagicMock(spec=murano_object.MuranoObject)
self.mock_class = mock.MagicMock(spec=murano_type.MuranoClass) subscriber.real_this = subscriber
self.mock_method = mock.MagicMock(spec=murano_method.MuranoMethod)
self.mock_method.name = "mockHandler"
self.mock_class.methods = mock.PropertyMock(
return_value={"mockHandler": self.mock_method})
self.mock_subscriber.type = self.mock_class
self.mock_subscriber.object_id = "1234"
@mock.patch("murano.dsl.helpers.get_caller_context") mock_class = mock.MagicMock(spec=murano_type.MuranoClass)
@mock.patch("murano.dsl.helpers.get_this") mock_method = mock.MagicMock(spec=murano_method.MuranoMethod)
def test_set_dd(self, this, caller_context): mock_method.name = "mockHandler"
this.return_value = self.mock_subscriber mock_class.methods = mock.PropertyMock(
target_0 = mock.MagicMock(spec=dsl.MuranoObjectInterface) return_value={"mockHandler": mock_method})
target_0.object = self.mock_subscriber
target_0.object.real_this = self.mock_subscriber def find_single_method(name):
target_0.object.dependencies = {} if name != 'mockHandler':
raise exceptions.NoMethodFound(name)
mock_class.find_single_method = find_single_method
subscriber.type = mock_class
self.subscriber = mock.MagicMock(spec=dsl.MuranoObjectInterface)
self.subscriber.object = subscriber
self.subscriber.type = subscriber.type
publisher = mock.MagicMock(spec=murano_object.MuranoObject)
publisher.real_this = publisher
self.publisher = mock.MagicMock(spec=dsl.MuranoObjectInterface)
self.publisher.object = publisher
def test_set_dd(self):
self.publisher.object.dependencies = {}
garbage_collector.GarbageCollector.subscribe_destruction( garbage_collector.GarbageCollector.subscribe_destruction(
target_0, handler="mockHandler") self.publisher, self.subscriber, handler="mockHandler")
dep = self.mock_subscriber.dependencies["onDestruction"] dep = self.publisher.object.dependencies["onDestruction"]
self.assertThat(dep, matchers.HasLength(1)) self.assertThat(dep, matchers.HasLength(1))
dep = dep[0] dep = dep[0]
self.assertEqual("mockHandler", dep["handler"]) self.assertEqual("mockHandler", dep["handler"])
self.assertEqual(self.mock_subscriber, dep["subscriber"]()) self.assertEqual(self.subscriber.object, dep["subscriber"]())
@mock.patch("murano.dsl.helpers.get_caller_context") def test_unset_dd(self):
@mock.patch("murano.dsl.helpers.get_this") self.publisher.object.dependencies = (
def test_unset_dd(self, this, caller_context):
this.return_value = self.mock_subscriber
target_1 = mock.MagicMock(spec=dsl.MuranoObjectInterface)
target_1.object = self.mock_subscriber
target_1.object.real_this = self.mock_subscriber
target_1.object.dependencies = (
{"onDestruction": [{ {"onDestruction": [{
"subscriber": _weakref.ref(self.mock_subscriber), "subscriber": weakref.ref(self.subscriber.object),
"handler": "mockHandler" "handler": "mockHandler"
}]}) }]})
garbage_collector.GarbageCollector.unsubscribe_destruction( garbage_collector.GarbageCollector.unsubscribe_destruction(
target_1, handler="mockHandler") self.publisher, self.subscriber, handler="mockHandler")
self.assertEqual( self.assertEqual(
[], self.mock_subscriber.dependencies["onDestruction"]) [], self.publisher.object.dependencies["onDestruction"])
@unittest.skip("WIP") def test_set_wrong_handler(self):
@mock.patch("murano.dsl.helpers.get_caller_context")
@mock.patch("murano.dsl.helpers.get_this")
def test_set_wrong_handler(self, this, caller_context):
this.return_value = self.mock_subscriber
target_2 = mock.MagicMock(spec=dsl.MuranoObjectInterface)
target_2.object = mock.MagicMock(murano_object.MuranoObject)
target_2.object.dependencies = {}
self.assertRaises( self.assertRaises(
exceptions.NoMethodFound, exceptions.NoMethodFound,
garbage_collector.GarbageCollector.subscribe_destruction, garbage_collector.GarbageCollector.subscribe_destruction,
target_2, handler="wrongHandler") self.publisher, self.subscriber, handler="invalidHandler")
self.assertRaises(
exceptions.NoMethodFound,
garbage_collector.GarbageCollector.unsubscribe_destruction,
self.publisher, self.subscriber, handler="invalidHandler")