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:
parent
584571c3d8
commit
c47269128b
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue