Exception when loading same module multiple times

63 views
Skip to first unread message

MrBahr

unread,
Apr 5, 2012, 10:25:45 PM4/5/12
to Granite Data Services Forum
Hi,

I am getting:

TypeError: Error #1009: Cannot access a property or method of a null
object reference.
at org.granite.tide.impl::ComponentStore/addModule()[/Users/
franckwolff/Developments/workspace/graniteds/as3/framework/org/granite/
tide/impl/ComponentStore.as:309]
at org.granite.tide::Tide/addModule()[/Users/franckwolff/Developments/
workspace/graniteds/as3/framework/org/granite/tide/Tide.as:618]
at com.presidioHealth.presidioPerformCoding.vo::ModuleVO/
modEventHandler()[/Work/PresidioPerformClient/PresidioPerformCoding/
src/com/presidioHealth/presidioPerformCoding/vo/ModuleVO.as:136]

if I try to load the same module more than once and add it to the
spring context.

I have an application that uses multiple tabs. Each tab has a set of
modules that can be displayed on that tab. The same module may be
instantiated more than once and put on different tabs.

I do:


public function loadModule(tideContextId:String,
registerWithTide:Boolean,
creationCallback:Function):Object {

this._registerWithTide = registerWithTide;
this._tideContextId = tideContextId;
this._creationCallback = creationCallback;

trace("Loading module with path = " + path);

_moduleAppDomain = new
ApplicationDomain(ApplicationDomain.currentDomain);
_info = ModuleManager.getModule(path);
_info.addEventListener(ModuleEvent.READY, modEventHandler, false,
0, true);
_info.load(_moduleAppDomain);
return null;
}

private function modEventHandler(e:ModuleEvent):void {
/* For MX containers, cast to a DisplayObject. */
_display = _info.factory.create();
(_display as Module).percentWidth = 100;
(_display as Module).percentHeight = 100;
if (this._registerWithTide)
Spring.getInstance().addModule(_display, _moduleAppDomain);
if (this._creationCallback != null)
this._creationCallback(_display);
(_display as IPresidioModule).startupConversation("",null);
}

If I try to load the same module more than once, I get the exception
when the line:

Spring.getInstance().addModule(_display, _moduleAppDomain);

is executed. Any thoughts on what might be causing this or what I can
do to fix it?

Thanks,
Tom

Gabriel De Repentigny

unread,
May 25, 2012, 4:22:52 PM5/25/12
to Granite Data Services Forum
I am having this same issue. Did you ever find out what was causing it
or find a workaround?

Gabriel De Repentigny

unread,
May 25, 2012, 11:59:04 PM5/25/12
to gran...@googlegroups.com
Here's an example of how to reproduce it exactly. It seems to happen when you have one module inside another. If you load the inner module more than once, it fails.

test.mxml:
<s:Application
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    preinitialize="Cdi.getInstance().initApplication()">
      
    <fx:Script>
        <![CDATA[
           
            import org.granite.tide.Tide;
            import org.granite.tide.cdi.Cdi;
           
            private function changeModule(modulePath:String):void {
                if (moduleLoader.url != modulePath) {
                    moduleLoader.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
                    moduleLoader.unloadModule();
                    moduleLoader.loadModule(modulePath);
                }
            }
        ]]>
    </fx:Script>
   
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
   
    <s:Button label="Load Outer Module 1"
              click="changeModule('OuterModule1.swf')"/>
   
    <s:Button label="Load Outer Module 2"
              click="changeModule('OuterModule2.swf')"/>

    <s:ModuleLoader id="moduleLoader"
                    applicationDomain="{new ApplicationDomain(ApplicationDomain.currentDomain)}"
                    ready="Tide.getInstance().addModule(moduleLoader.child, moduleLoader.applicationDomain)"/>

</s:Application>

OuterModule1.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:Module xmlns:fx="http://ns.adobe.com/mxml/2009"
          xmlns:s="library://ns.adobe.com/flex/spark"
          xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Script>
        <![CDATA[
            import org.granite.tide.Tide;
           
            private function changeModule(modulePath:String):void {
                if (moduleLoader.url != modulePath) {
                    moduleLoader.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
                    moduleLoader.unloadModule();
                    moduleLoader.loadModule(modulePath);
                }
            }
        ]]>
    </fx:Script>
   
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
   
    <s:Button label="Load Inner Module"
              click="changeModule('InnerModule.swf')"/>
   
    <s:ModuleLoader id="moduleLoader"
                    applicationDomain="{new ApplicationDomain(ApplicationDomain.currentDomain)}"
                    ready="Tide.getInstance().addModule(moduleLoader.child, moduleLoader.applicationDomain)"/>
</s:Module>

OuterModule2.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:Module xmlns:fx="http://ns.adobe.com/mxml/2009"
          xmlns:s="library://ns.adobe.com/flex/spark"
          xmlns:mx="library://ns.adobe.com/flex/mx">
    <s:Label text="outer module 2"/>
</s:Module>

InnerModule.mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:Module xmlns:fx="http://ns.adobe.com/mxml/2009"
          xmlns:s="library://ns.adobe.com/flex/spark"
          xmlns:mx="library://ns.adobe.com/flex/mx">
    <s:Label text="inner module"/>
</s:Module>

In this example, if you launch the application, then perform the following sequence, you will trigger the error:

1. Load the first outer module (click Load Outer Module 1)
2. Load the inner module (click Load Inner Module)
3. Unload the first outer module (click Load Outer Module 2)
4. Repeat steps 1 and 2

I've tracked down where the null reference originally comes from, and it is ClassUtil.forName line 199:

clazz = Class(domain.getDefinition(name));

For some reason, casting domain.getDefinition(name) to Class returns null when the InnerModule is being loaded the second time. It's not obvious to me why that's happening. The application domain contains the class definition at that point or else ReferenceError would be thrown.

Gabriel De Repentigny

unread,
May 26, 2012, 4:31:28 PM5/26/12
to gran...@googlegroups.com
I've looked into this further, and I now understand what's going on. It turns out that modules inside modules isn't the only way to trigger the problem. It's actually related to module caching.

Say you have ModuleLoader A which loads Module A and ModuleLoader B which loads Module B. ModuleLoader A is at the top-level of the application. ModuleLoader B is inside Module A.

If you load Module A, then load Module B, setting applicationDomain on both ModuleLoaders to be "new ApplicationDomain(ApplicationDomain.currentDomain)", moduleLoaderB.applicationDomain contains the definitions of all classes in the top-level application as well as those in Module A and Module B.

If you then unload Module A (moduleLoaderA.unloadModule()), then use the module loaders to load Module A and Module B again, moduleLoaderB.applicationDomain contain NO class definitions. Inside Module B, however, the ApplicationDomain.currentDomain DOES contain the correct class definitions. But if you try to access Module B's applicationDomain by using moduleLoaderB.applicationdomain (e.g., which you might do from inside Module A to pass the applicationDomain to Tide), then you will be unable to access any class definitions inside Module B.

HERE'S THE REASON THIS HAPPENS:
When you tell Flex to load Module B the second time, it does NOT load it again using Loader.load(). Instead it creates a copy of it from its cache which *uses whatever applicationDomain you assigned to it originally*! This means the applicationDomain you created for it the second time never gets used and contains no class definitions!

HERE'S HOW TO FIX IT:
Option 1:
Unload Module B before loading it the second time. For example, call moduleLoaderB.unloadModule(). This will allow you to set the applicationDomain again the second time you load it.
Option 2:
Keep a reference to the original applicationDomain lying around somewhere and pass it to Tide the second time you load the module.

Gabriel

Gabriel De Repentigny

unread,
May 26, 2012, 9:03:32 PM5/26/12
to gran...@googlegroups.com
One more thought: be careful when preloading modules using ModuleManager.getModule + IModuleInfo.load as per http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf64277-7ffd.html#WS2db454920e96a9e51e63e3d11c0bf64277-7feb

That will cause the application domain of the modules to be set at the time they are preloaded. When you later display one of the modules using a ModuleLoader, the application domain that you tell the ModuleLoader to use will be ignored.

You can still preload modules, but you need to manually provide an application domain to IModuleInfo.load, keep track of that application domain somehow, and provide it to Tide.addModule when you finally display the module.


Gabriel

On Thursday, April 5, 2012 10:25:45 PM UTC-4, MrBahr wrote:
Reply all
Reply to author
Forward
0 new messages