11 Replies Latest reply on Nov 17, 2010 12:47 PM by Woby

    Module not unloading if embedded font was ever used

    FotoMeister

      So, I have a test app that uses modules with the font embeded. Using ModuleManager I am able to load up the module. Once I call   IModuleInfo.factory.create(), I am then able to setStyle("fontFamily", "BPDiet") and the font does show up. The issue I am now having is that once I have used a font from a module, even if my TextArea is no longer using it (I even tried removing the textArea, and replacing it with a new one), the module will not unload.

       

      I read through this "What We Know About Unloading Modules" and I think I am not leaving any references around.They are loaded using the load() defaults. There is no code (that is used) in the module. The modules are not being added to the display, so they never receive focus.

      Note that I am unable to run the profiler as suggested in the article as I don't have the premium Flash Builder 4. <grrr>


      Note that the first module that is loaded, I can never get to unload, even if I never used the font embedded in it, but all subsequent modules will unload, if I do not use the embedded font. I can live with the first one being pinned as long as I can unload the others that are not in use.

       

      Here is the code from one of my modules:

      <?xml version="1.0" encoding="utf-8"?>
      <mx: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 spark.components.TextArea;
                  [Embed(source='assets/BPDiet.otf', 
                          fontName='BPDiet', 
                             mimeType='application/x-font')]
                  public static var BPDietNormal:Class;
                  
                  public function GetSampleTextArea():TextArea {
                      var SampleTextArea:TextArea = new TextArea();
                      SampleTextArea.text = "Test BPDiet please!";
                      SampleTextArea.setStyle("fontFamily", 'BPDiet');
                      return SampleTextArea;            
                  }
              ]]>
          </fx:Script>
      </mx:Module>

       

      And here is the App that is loading and using the modules:

      <?xml version="1.0" encoding="utf-8"?>
      <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" minWidth="400" minHeight="400">
          <fx:Script>
              <![CDATA[
                  import mx.collections.ArrayCollection;
                  import mx.core.UIComponent;
                  import mx.events.FlexEvent;
                  import mx.events.ModuleEvent;
                  import mx.managers.SystemManager;
                  import mx.modules.IModule;
                  import mx.modules.IModuleInfo;
                  import mx.modules.Module;
                  import mx.modules.ModuleManager;
                  
                  import spark.components.TextArea;
      
                  private var _ta:TextArea = null;
                  protected var _moduleInfo:IModuleInfo;
                  
                  private function LoadFontTextArea(fontSwf:String):void {
                      status.text = "Loading the font pack";
                      _moduleInfo = ModuleManager.getModule(fontSwf);
                      // add some listeners
                      _moduleInfo.addEventListener(ModuleEvent.READY, onModuleReady);
                      _moduleInfo.addEventListener(ModuleEvent.ERROR, moduleLoadErrorHandler);
                      _moduleInfo.addEventListener(ModuleEvent.UNLOAD, moduleUnloadHandler);
                      _moduleInfo.load();
                  }
                  
                  private function onModuleReady(event:ModuleEvent):void {
                      status.text = "The font pack swf Ready \n" + event.module.url;
                      //All I had todo was create the module, then I could access the embeded font by name
                      var fontMod:* = event.module.factory.create();
                      //_ta = fontMod.GetSampleTextArea();
                      //panelToStuff.addElement(_ta);
                      fontMod = null;
                      //fontNameForSample = event.module.url.replace(".swf", "");
                  }
                  
                  private function moduleLoadErrorHandler(event:ModuleEvent):void {
                      status.text = "Font Module Load Error. \n" + 
                          event.module.url + "\n"+ event.errorText;
                  }
                  
                  private function moduleUnloadHandler(event:ModuleEvent):void {
                      status.text = "Font Module Unload Event. \n" + event.module.url;
                  }
      
                  private function fontChangedHandler():void {
                      unloadCurrentFont();
                      LoadFontTextArea(String(availFonts.selectedItem));
                  }
                  
                  private function unloadCurrentFont():void {
                      if (_ta != null) {
                          panelToStuff.removeElement(_ta);
                          _ta = null;
                      }
                      
                      panelToStuff.removeElement(sampleTextArea);
                      sampleTextArea = null;
                      sampleTextArea = new TextArea();
                      sampleTextArea.id = "sampleTextArea";
                      sampleTextArea.text = "Some text for your viewing pleasure. " + colorForBK.toString(16);
                      panelToStuff.addElement(sampleTextArea);                
                      
                      if (_moduleInfo != null)
                      {
                          _moduleInfo.release();
                          //_moduleInfo.unload();
                          _moduleInfo = null;
                      }
                      System.gc();
                  }
                  private function DoNonImportantWork():void {
                      colorForBK -= 0xFAA; 
                      if (colorForBK < 0x0) colorForBK = 0xFFFFFF;
                      var foo:* = {prop1: "yea" + colorForBK.toString(), prop2:"boo" + colorForBK.toString()};
                      var hmm:String = foo.prop1 + " " + foo.prop2;
                      regedFonts = new ArrayCollection(Font.enumerateFonts(false));
                  }
                  
                  [Bindable]
                  private var colorForBK:int = 0xFFFFFF;
                  
                  [Bindable]
                  private var fonts:ArrayCollection = 
                      new ArrayCollection(new Array("AlexandriaFLF.swf", "BPDiet.swf", "ChanpagneFont.swf", "KidsFont.swf"));
                  
                  [Bindable]
                  private var regedFonts:ArrayCollection;
                  
                  [Bindable]
                  private var fontNameForSample:String = "";
              ]]>
          </fx:Script>
          
          <s:VGroup id="panelToStuff">
              <s:HGroup>
                  <s:Label id="status" text="status area" backgroundColor="{colorForBK}"/>
                  <s:VGroup>
                      <s:DropDownList id="availFonts" dataProvider="{fonts}" change="fontChangedHandler()" />
                      <s:Button label="UnLoad" enabled="true" click="unloadCurrentFont()"/>
                      <s:Button label="doSome" enabled="true" click="DoNonImportantWork()"/> 
                  </s:VGroup>
              </s:HGroup>
              <s:HGroup>
                  <s:Button click="fontNameForSample = 'BPDiet';" label="BPDiet"/>
                  <s:Button click="fontNameForSample = 'Champagne';" label="Champagne"/>
                  <s:Button click="fontNameForSample = 'Kids';" label="Kids"/>
              </s:HGroup>
              <s:Label text="{regedFonts.length} reg'ed"/>
              <s:TextArea id="sampleTextArea" fontFamily="{fontNameForSample}" text="Some Sample text for your viewing"/>
          </s:VGroup>
      </s:Application>
      
      

       

      A couple things to note; I am calling System.rc() in the unloadCurrentFont() method just to speed up seeing the SWF unload in the debug console. The DoNonImportantWork() is there to just cause some events to happen and to create some objects that will need to be GC'ed. It also let me know that the fonts are not getting registered in Font.

       

      I'm going to have 30 fonts (and more, that designer is busy) that I will need to be able to dynamically load, but right now, loading them with CSS style modules blows up after about 15 because style modules register the font so I cannot unload the CSS swf.

        • 1. Re: Module not unloading if embedded font was ever used
          Flex harUI Adobe Employee

          Maybe something else is holding onto the TextArea.

          • 2. Re: Module not unloading if embedded font was ever used
            FotoMeister Level 1

            To help eliminate the question of whether the TextArea is being held by something else, I have removed it from the MXML, and now programatically create it. That did not help.

            So, I got the trial version of Flash Builder 4 installed on another machine in the office so that I can use the profiler. (The profiler is pretty cool by the way).


            After a lot of profiing, I found four paths to the module's FlexModuleFactory.

            Two of those paths go to EmbeddedFontRegistry, whose data is static. I could get into it and remove font entry and free up the moduleFactory from there. This is a hack, that entry in/on EmbeddedFontRegistry.font should have been cleaned up by the code removing the fontFamily from the TextArea. (Note that EmbeddedFontRegistry is marked [ExcludeClass], which I assume means I should not really be messing with it.

             

            The other two I cannot get to as they are anonymous. They also don't appear to be referenced, as the Object References shows them both as GC root objects. Here is a screen shot:

            letMyModuleGo.JPG

            I did a search through the sdk code and 'fbs' only shows up as a parameter on the init function of various Marshal support classes, but is not used in the init()

            Anyway, these references to the FlexModuleFactory do not get held if I do not use the embeded font in the module.

            Here is the updated code:

             

            <?xml version="1.0" encoding="utf-8"?>
            <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" minWidth="400" minHeight="400">
                <fx:Script>
                    <![CDATA[
                        import mx.collections.ArrayCollection;
                        import mx.core.EmbeddedFont;
                        import mx.core.EmbeddedFontRegistry;
                        import mx.core.IEmbeddedFontRegistry;
                        import mx.core.UIComponent;
                        import mx.events.FlexEvent;
                        import mx.events.ModuleEvent;
                        import mx.managers.SystemManager;
                        import mx.modules.IModule;
                        import mx.modules.IModuleInfo;
                        import mx.modules.Module;
                        import mx.modules.ModuleManager;
                        import spark.components.TextArea;
            
                        private var _ta:TextArea = null;
                        protected var _moduleInfo:IModuleInfo;
                        
                        private function LoadFontTextArea(fontSwf:String):void {
                            status.text = "Loading the font pack";
                            _moduleInfo = ModuleManager.getModule(fontSwf);
                            // add some listeners
                            _moduleInfo.addEventListener(ModuleEvent.READY, onModuleReady);
                            _moduleInfo.addEventListener(ModuleEvent.ERROR, moduleLoadErrorHandler);
                            _moduleInfo.addEventListener(ModuleEvent.UNLOAD, moduleUnloadHandler);
                            _moduleInfo.load();
                        }
                        
                        private function onModuleReady(event:ModuleEvent):void {
                            status.text = "The font pack swf Ready \n" + event.module.url;
                            //All I had todo was create the module, then I could access the embeded font by name
                            var fontMod:* = event.module.factory.create();
                            fontMod = null;
                        }
                        
                        private function moduleLoadErrorHandler(event:ModuleEvent):void {
                            status.text = "Font Module Load Error. \n" + 
                                event.module.url + "\n"+ event.errorText;
                        }
                        
                        private function moduleUnloadHandler(event:ModuleEvent):void {
                            status.text = "Font Module Unload Event. \n" + event.module.url;
                        }
            
                        private function fontChangedHandler():void {
                            unloadCurrentFont();
                            LoadFontTextArea(String(availFonts.selectedItem));
                        }
                        
                        private function unloadCurrentFont():void {
                            if (_ta != null && _moduleInfo != null) {
                                var fontName:String = _ta.getStyle("fontFamily");
                                _ta.setStyle("fontFamily", "");
                                _ta.validateProperties();
                                if (_ta.textDisplay) {
                                    _ta.textDisplay.validateProperties();
                                }
                                panelToStuff.removeElement(_ta);                
                                _ta = null;
                                //This I should not have to do, but the framework is not doing it
                                var embFontReg:IEmbeddedFontRegistry = EmbeddedFontRegistry.getInstance();
                                var embFonts:Array = embFontReg.getFonts();
                                for each (var curEmbFont:EmbeddedFont in embFonts){
                                    if (curEmbFont.fontName == fontName){
                                        embFontReg.deregisterFont(curEmbFont, _moduleInfo.factory);
                                    }
                                }
                            }
                                            
                            if (_moduleInfo != null)
                            {
                                _moduleInfo.unload();
                                _moduleInfo = null;
                            }
                            System.gc();
                        }
                        
                        private function AddTA():void {
                            _ta = new TextArea();
                            _ta.text = "Some text for your viewing pleasure. " + colorForBK.toString(16);
                            panelToStuff.addElement(_ta);
                        }
                        
                        private function DoNonImportantWork():void {
                            colorForBK -= 0xFAA; 
                            if (colorForBK < 0x0) colorForBK = 0xFFFFFF;
                            var foo:* = {prop1: "yea" + colorForBK.toString(), prop2:"boo" + colorForBK.toString()};
                            var hmm:String = foo.prop1 + " " + foo.prop2;
                        }
                        
                        [Bindable]
                        private var colorForBK:int = 0xFFFFFF;
                        [Bindable]
                        private var fonts:ArrayCollection = 
                            new ArrayCollection(new Array("AlexandriaFLF.swf", "BPDiet.swf", "ChanpagneFont.swf", "KidsFont.swf"));
                    ]]>
                </fx:Script>
                
                <s:VGroup id="panelToStuff">
                    <s:HGroup>
                        <s:Label id="status" text="status area" backgroundColor="{colorForBK}"/>
                        <s:VGroup>
                            <s:DropDownList id="availFonts" dataProvider="{fonts}" change="fontChangedHandler()" />
                            <s:Button label="UnLoad" enabled="true" click="unloadCurrentFont()"/>
                            <s:Button label="AddTA" enabled="true" click="AddTA()"/> 
                            <s:Button label="doSome" enabled="true" click="DoNonImportantWork()"/> 
                        </s:VGroup>
                    </s:HGroup>
                    <s:Button click="_ta.setStyle('fontFamily', 'BPDiet');" label="BPDiet"/>
                </s:VGroup>
            </s:Application>
            

             

            I really think I've reached the end of what I can do. This really seems like a bug.

            • 3. Re: Module not unloading if embedded font was ever used
              Flex harUI Adobe Employee

              You shouldn't need to register the font.  There should only be weak

              references to it.  Did you prove that the TextArea got garbage collected?

              Otherwise the weak-references to the font won't get released.

               

              The "fbs" root is a known profiler issue and can generally be ignored.  The

              "style" root might be worth investigating as well, but first, make sure the

              TextArea and its child RichEditableText are gone.

              • 4. Re: Module not unloading if embedded font was ever used
                FotoMeister Level 1

                Yes both the TextArea and its RichEditableText are gone. Both on the Live Objects tab and the Loitering Objects (which is a comparison between a snapshot just before I loaded the module and add the Text area, and the memory snapshot after I cleaned up the text area (and the embeddedFontRegistry) and unloaded the module (see method unloadCurrentFont() above).

                • 5. Re: Module not unloading if embedded font was ever used
                  Darrell Loverin Level 4

                  I think the issue you are hitting the known leak 2b from "What We Know About Unloading Modules". If you have the profiler, then look to see if a resource bundle is in the loitering objects. You should see a class named en_US$containers_properties in the loitering objects.

                   

                  Another way to find the problem is to compile with -keep and look at the generated code. You don't want a module to load a resource bundle that the main application has not loaded. Compare the "compileResourceBundleNames" of the application vs the module. In your sample app they look like this:

                  Application:

                              compiledResourceBundleNames: [ "collections", "components", "core", "effects", "layout", "skins", "sparkEffects", "styles", "textLayout" ],

                  Module:

                              compiledResourceBundleNames: [ "components", "containers", "core", "effects", "layout", "skins", "styles" ],

                   

                  You can see the module is loading "containers" where the application is not.

                   

                   

                  To fix the problem add resource bundle metadata to your main application:

                      <fx:Metadata>

                          [ResourceBundle("containers")]

                      </fx:Metadata>

                       

                   

                  We really want to fix the resource bundle issue in the Flex Hero release.

                   

                   

                  -Darrell

                  • 6. Re: Module not unloading if embedded font was ever used
                    FotoMeister Level 1

                    Darrell,

                    You are correct that "containers" resource bundle was not in my main app (FontConsumer), I've added the Metadata entry as you prescribed and compared the subsequent generated code. Unfortunately, that did not make my module unload.

                     

                    I also added the -compiler.keep-all-type-selectors as mentioned in 2a, as I saw the default.css being mentioned in the generated files, but that did not help either.

                     

                    Is there anyway to get the profiler to use the generated file locations in the Allocation trace? The two paths to the FlexModuleFactory are both created in the modules _BPDiet_Styles:init(). I looked at the generated code, but I am not seeing any place where the anonymous object is being created. ( in both paths, the GC Root object is the same anonymous object id).

                     

                    My first, ignorant in how flex/flash works, hypothesis was that an anonymous object is being created with all of the local variables of public static function init(fbs:IFlexModuleFactory):void because it is a static function, and that anonymous object is not being GC'ed. Here was my evidence that is fueling this thought.

                     

                    In the two Instances paths for the _BPDient_mx_coree_FlexModuleFactory instance, one named style, the other fbs, both of those names are local variables. (fbs being the parameter passed in, and style being declared).

                     

                    I did some digging and none of the other local variables seem to loitering. I checked this by looking through the loitering object types for the types that are local variables, and none of the other type instances appeared to be pathed to this anonymous object.

                    • 8. Re: Module not unloading if embedded font was ever used
                      Darrell Loverin Level 4

                      The resource bundle addresses one problem but Alex just posted that bug a been written for the font issue. What I tried was a simple test; I just loaded the module and then unloaded it without using the font. I saw the module unload once I added the metadata but not before.

                       

                       

                      -Darrell

                      • 9. Re: Module not unloading if embedded font was ever used
                        FotoMeister Level 1

                        True, I was not having an issue unloading my modules (except the first until you noted 2b) if I did not use the font embedded in them.

                        I'm not sure the bug that has been submitted will fix the problem with the anonymous class in the init() method.

                         

                        I think that it will fix the problem where if I change or remove Text or its font on the screen (like in a TextArea in my case, or in a TextLine in the bug sample code), that the reference to the ModuleFactory does not get removed from the EmbeddedFontRegistry, which is what I was manually doing in my test code.

                        private function unloadCurrentFont():void {
                            if (_ta != null) {
                                var fontName:String = _ta.getStyle("fontFamily");
                                _ta.setStyle("fontFamily", "Arial");
                                _ta.validateProperties();
                                if (_ta.textDisplay) {
                                    _ta.textDisplay.validateProperties();
                                }
                                panelToStuff.removeElement(_ta);                
                                _ta = null;
                                if (_moduleInfo != null)
                                {
                                    //This I should not have to manually remove the font 
                                    //and its ModuleFactory, but the framework is not doing it
                                    var embFontReg:IEmbeddedFontRegistry = EmbeddedFontRegistry.getInstance();
                                    var embFonts:Array = embFontReg.getFonts();
                                    for each (var curEmbFont:EmbeddedFont in embFonts){
                                        if (curEmbFont.fontName == fontName){
                                            embFontReg.deregisterFont(curEmbFont, _moduleInfo.factory);
                                        }
                                    }
                                }
                            }
                         

                         

                        After manually removing the entry from EmbeddedFontRegistry, I am only left with to paths to the ModuleFactory, and they are on that anonymous object.

                        letMyModuleGo2.JPG

                        I hope it does help though.

                        • 10. Re: Module not unloading if embedded font was ever used
                          Flex harUI Adobe Employee

                          The font gets registered whether you render text with it or not and since

                          you can unload the module if you don't render, I don't think that's the

                          problem.  Those references are weak references or don't actually reference

                          the font, just strings and booleans.

                           

                          The bug I filed has been ruled not-a-bug.  It appears that in the test case

                          it will GC the SWF, but not right away.

                           

                          We are still investigating the issue.

                          • 11. Re: Module not unloading if embedded font was ever used
                            Woby

                            I think I have found the solution. Just set the UI from the module to null.

                             

                            /**
                            * Called when module as been removed
                            */

                            private function onRemoved(_oEvent : ModuleEvent) : void
                            {

                                 // Free memory
                                 groupContainer.removeAllElements();                  
                                 playBtn = null;
                                 groupContainer = null;
                            }