Logging and catching (async) exceptions during invalidation

Sometimes, due to many different reasons, there’re exceptions that happen during the invalidation/validation cycle of the flex SDK. Most times these exceptions end up being unveiled in the very core of the SDK, being not possible to catch them up because their async nature and the place where they happen.

The stacktrace of these exceptiosn, normally, looks like the lines below:

...
at mx.core::UIComponent/validateProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:5807]
at mx.managers::LayoutManager/validateProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\managers\LayoutManager.as:539]
at mx.managers::LayoutManager/doPhasedInstantiation()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\managers\LayoutManager.as:689]
at Function/http://adobe.com/AS3/2006/builtin::apply()
at mx.core::UIComponent/callLaterDispatcher2()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:8633]
at mx.core::UIComponent/callLaterDispatcher()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:8582]

Today, snooping around the UIComponent class, I’ve found a nice unexpected thing. Basically the invalidation pattern in the SDK is built around the LayoutManager and the UIComponent classes. The callLater method is the responsible to delay the execution of certain functionalities to the next frame. callLater implementation relies on the RENDER and ENTER_FRAME frames. The handler for these events is the method callLaterDispatcher.

// At run-time, callLaterDispatcher2() is called
// without a surrounding try-catch.
if (!UIComponentGlobals.catchCallLaterExceptions)
{
    callLaterDispatcher2(event);
}

// At design-time, callLaterDispatcher2() is called
// with a surrounding try-catch.
else
{
    try
    {
        callLaterDispatcher2(event);
    }
    catch(e:Error)
    {
        var callLaterErrorEvent:DynamicEvent = new DynamicEvent("callLaterError");
        callLaterErrorEvent.error = e;
        systemManager.dispatchEvent(callLaterErrorEvent);
    }
}

Here’s the surprise. If UIComponentGlobals.catchCallLaterExceptions is true, the execution of the validation cycle will be wrapped with a try..catch and a DynamicEvent will be dispatched through the systemManager with information about the error.

This opens the doors for at least catch the error and log it. In some cases, depending on the severity of the error and if the application is not too corrupted, we’ll even be able to display a message to the user notifying him the error. This is not a fix to FP-1499 nor FP-444 but under some circumstances it can be really helpful if combined with great logging practices as explained by Tom.

I’ve created a simple class that hooks into the systemManager, listens for the callLaterError event and logs it using the standard SDK logging mechanism. The AsyncErrorLogger is MXML friendly so that it can be instantiated in the root application. It provides an enable property to switch the behavior on and off. Additionally the property redispatchErrorEvent, which by default is false, redispatches the error causing the traditional flash player error dialog to appear (in debugger player versions) and not fail silently.

Here’s an example on how to use it:

<?xml version = "1.0" encoding = "utf-8"?>
<mx:WindowedApplication xmlns:mx = "http://www.adobe.com/2006/mxml" xmlns:utils = "com.adobe.ac.utils.*">

    <mx:Script>
        <![CDATA[
           import mx.logging.LogEventLevel;
           
           //---------------------------------------------
           // THAT'S THE WAY TO SIMULATE THE ASYNC ERROR
           //---------------------------------------------
           
           private var doItChange : Boolean = false;
           
           private function doIt() : void
           {
               doItChange = true;
               invalidateProperties();
           }
           
           override protected function commitProperties() : void{
               super.commitProperties();
               
               if( doItChange )
               {
                   doItChange = false;
                   throw new Error("CallLater exception");
               }
           }
       ]]>
    </mx:Script>

    <mx:TraceTarget level = "{ LogEventLevel.ALL }"/>

    <utils:AsyncErrorLogger/>

    <mx:Button click = "doIt()" label = "Throw exception"/>
</mx:WindowedApplication>

Here’s the code. You can download the class from here as well.

package com.adobe.ac.utils
{
    import flash.events.ErrorEvent;
    import flash.events.EventDispatcher;

    import mx.core.Application;
    import mx.core.UIComponentGlobals;
    import mx.events.DynamicEvent;
    import mx.events.FlexEvent;
    import mx.logging.ILogger;
    import mx.logging.Log;
    import mx.managers.ISystemManager;

    [Event(name="error",type="flash.events.ErrorEvent")]
    public class AsyncErrorLogger extends EventDispatcher
    {
        //------------------------------------------------------------------------
        //
        //  Constants
        //
        //------------------------------------------------------------------------
        private static const CALL_LATER_ERROR:String = "callLaterError";

        private static const LOG:ILogger = Log.getLogger("com.adobe.cairngorm.AsyncErrorLogger");


        //------------------------------------------------------------------------
        //
        //  Properties
        //
        //------------------------------------------------------------------------

        //-------------------------------
        //  systemManager
        //-------------------------------
        private var systemManager:ISystemManager;

        //-------------------------------
        //  logStackTrace
        //-------------------------------

        public var logStackTrace:Boolean = true;

        //-------------------------------
        //  redispatchError
        //-------------------------------
        public var redispatchError:Boolean = false;

        //-------------------------------
        //  enabled
        //-------------------------------

        [Bindable]
        public function get enabled():Boolean
        {
            return UIComponentGlobals.catchCallLaterExceptions;
        }

        public function set enabled(value:Boolean):void
        {
            UIComponentGlobals.catchCallLaterExceptions = value;
        }


        //------------------------------------------------------------------------
        //
        //  Constructor
        //
        //------------------------------------------------------------------------

        public function AsyncErrorLogger(systemManager:ISystemManager = null)
        {
            enabled = true;

            init(systemManager);
        }


        //------------------------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------------------------

        private function init(systemManager:ISystemManager):void
        {
            if (systemManager)
            {
                initializeListener(systemManager);
            }
            else
            {
                var application:Object = Application.application;

                if (!application.initialized)
                {
                    application.addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
                }
                else
                {
                    initializeListener(application.systemManager);
                }
            }
        }

        private function creationCompleteHandler(event:Event):void
        {
            var application:Object = Application.application;
            application.removeEventListener(FlexEvent.INIT_COMPLETE, creationCompleteHandler);

            initializeListener(application.systemManager);
        }

        private function initializeListener(systemManager:ISystemManager):void
        {
            this.systemManager = systemManager;

            if (this.systemManager)
            {
                LOG.debug("AsynErrorLogger initialized.");
                this.systemManager.addEventListener(CALL_LATER_ERROR, errorHandler);
            }
        }

        private function errorHandler(event:DynamicEvent):void
        {
            var error:Error = event.error as Error;

            LOG.error("{0}", error.message);

            if (logStackTrace)
            {
                LOG.error(error.getStackTrace());
            }

            if (redispatchError)
            {
                var errorEvent:ErrorEvent = new ErrorEvent(ErrorEvent.ERROR, false, false, error.getStackTrace(), error.errorID);
                dispatchEvent(errorEvent);
            }
        }
    }
}

2 Comments to “Logging and catching (async) exceptions during invalidation”

  1. [...] Logging and catching (async) exceptions during invalidation in Flex [...]

  2. Shai 23 April 2011 at 6:11 pm #

    Thank you for this script, very helpful !


Leave a Reply