Move .init call into separate load phase

Before this commit .init method were called as the last step
of second phase initialization. This is also the phase when
forward references are get resolved.

Because load phases work recursively second phase of load
for the nested objects will happen before the outer counterparts.
If those nested objects were to access their owners in the .init
method it can cause exception to be thrown because forward
reference of the owner were not yet resolved.

This commits moves .init calls out of the second phase. Now
during the second phase we collect everything that need to
be called and call it after the complete graph load.
This also guarantees that we call .init only on the object graph
that can be loaded (since it already loaded by that time)

Change-Id: Ie86655bcc668be880a7b97695835ccddb9adda0f
This commit is contained in:
Stan Lagun 2016-08-02 16:29:31 -07:00
parent d6456f00d2
commit 01725515bc
5 changed files with 51 additions and 18 deletions

View File

@ -52,7 +52,6 @@ class MuranoObject(dsl_types.MuranoObject):
self.__parents[name] = known_classes[name] = obj
else:
self.__parents[name] = known_classes[name]
self.__initialized = False
@property
def extension(self):
@ -67,8 +66,6 @@ class MuranoObject(dsl_types.MuranoObject):
self.__extension = value
def initialize(self, context, params, used_names=None):
if self.__initialized:
return
context = context.create_child_context()
context[constants.CTX_ALLOW_PROPERTY_WRITES] = True
object_store = helpers.get_object_store()
@ -134,19 +131,20 @@ class MuranoObject(dsl_types.MuranoObject):
if method:
filtered_params = yaql_integration.filter_parameters(
method.body, **params)
with helpers.with_object_store(object_store.parent_store):
self.__extension = method.invoke(
yield lambda: method.invoke(
self, filtered_params[0], filtered_params[1], context)
for parent in self.__parents.values():
parent.initialize(context, params, used_names)
for t in parent.initialize(context, params, used_names):
yield t
if not object_store.initializing and init:
def run_init():
context[constants.CTX_ARGUMENT_OWNER] = self.real_this
with helpers.with_object_store(object_store.parent_store):
init.invoke(self.real_this, (), init_args,
context.create_child_context())
self.__initialized = True
if not object_store.initializing and init:
yield run_init
@property
def object_id(self):

View File

@ -99,6 +99,7 @@ class InitializationObjectStore(ObjectStore):
self._initializing = False
self._root_owner = root_owner
self._keep_ids = keep_ids
self._initializers = []
@property
def initializing(self):
@ -136,11 +137,19 @@ class InitializationObjectStore(ObjectStore):
if context is None:
context = self.executor.create_object_context(obj)
obj.initialize(context, parsed['properties'])
def run_initialize():
self._initializers.extend(
obj.initialize(context, parsed['properties']))
run_initialize()
if owner is self._root_owner:
self._initializing = False
obj.initialize(context, parsed['properties'])
run_initialize()
finally:
if owner is self._root_owner:
self._initializing = False
with helpers.with_object_store(self.parent_store):
for fn in self._initializers:
fn()
return obj

View File

@ -370,9 +370,9 @@ def get_class_factory_definition(cls, murano_class):
args = tuple(dsl.to_mutable(arg, engine) for arg in args)
kwargs = dsl.to_mutable(kwargs, engine)
with helpers.contextual(__context):
__context[constants.CTX_NAMES_SCOPE] = \
murano_class
return helpers.evaluate(cls(*args, **kwargs), __context)
__context[constants.CTX_NAMES_SCOPE] = murano_class
result = helpers.evaluate(cls(*args, **kwargs), __context)
__receiver.object.extension = result
try:
fd = specs.get_function_definition(

View File

@ -74,6 +74,19 @@ Methods:
Body:
Return: new(:ConstructionFromInit).out.nodes[1]
testReferenceAccessFromInit:
Body:
- $model:
:Node:
value: rootNode
nodes:
- childNode
- :NodeWithReferenceAccess:
value: childNode
id: childNode
- $.out: new($model, $this)
---
Name: Node
@ -130,3 +143,12 @@ Methods:
nodes: [childNode1]
id: childNode2
- $.out: new($model, $this)
---
Name: NodeWithReferenceAccess
Extends: Node
Methods:
.init:
Body: $.find(Node).nodes.select(trace($.value))

View File

@ -67,3 +67,7 @@ class TestConstruction(test_case.DslTestCase):
def test_nested_new_loads_in_separate_store(self):
res = self._runner.testNestedNewLoadsInSeparateStore()
self.assertIsInstance(res, dsl.MuranoObjectInterface)
def test_reference_access_from_init(self):
self._runner.testReferenceAccessFromInit()
self.assertEqual(2, self.traces.count('childNode'))