Barrels : Circular Dependency Error

Hi,

I am getting a " Failed invoking <symbol> Circular Dependency Error" message while using a barrel within my app. What can cause this other than the obvious: having your app and barrel depend on each other? This is my first time using barrels. My app is using a single barrel. It has dependencies with Toybox and my barrel. My barrel only has dependency on Toybox. So I see no no obvious circular dependency here.

Thanks
Jesus

  • I now know what is causing the error. I am going to report a bug.

    My app has a dependency on my barrel. The code of my barrel is shown below. My barrel has a global variable initialized to an instance of InitClass, which triggers the execution of the corresponding constructor when the module is first loaded. This constructor registers a sensorData listener callback.

    If I comment out the call to Sensor.registerSensorDataListener() then I do not get the circular dependency error. This seems to be a problem with calling that particular method and not the entire Sensor module. I know this because if I replace the call to Sensor.registerSensorDataListener() with the following line of code

        System.println(Sensor.SENSOR_BIKEPOWER);

    Then the code compiles fine and I do not get the circular dependency error.

    module MyBarrel
    {
        (:MySubModule)
    
        module MySubModule
        {
            using Toybox.Sensor;
    
            var options = {
                :period => 1,
                :accelerometer => {
                    :enabled => true,
                    :sampleRate => 25,
                    :includePitch => true
                },
                :heartBeatIntervals => {
                    :enabled => false
                }
            };
    
            var init = new InitClass();
    
            class InitClass
            {
                function initialize()
                {
                    Sensor.registerSensorDataListener(method(:callback), options);
                }
    
                function callback(sensorData)
                {
                }
            }
        }
    }

  • I could swear that I've replied to this topic this week. No matter, because my initial take on it was wrong anyway.

    The dependency is there, but it probably isn't what you think. As far as I can tell, the cycle is between `MySubModule.options` and `MySubmodule.init`. Here is the sequence of events that leads to the cycle being detected by the runtime system:

    1. The application references a symbol in MyBarrel.MySubModule
    2. The system loads the MyBarrel module and initializes all of its data (nothing)
    3. The system loads the MyBarrel.MySubModule module and initializes all of its data data (options and init)
    4. I'm not sure of the order that things get initialized, so the system may or may not initialize options, and then it tries to initialize `init`.
    5. To initialize `init`, the system allocates a `MyBarrel.MySubModule.InitClass` instance, and then calls `initialize` on it.
    6. The `InitClass.initialize` method references `MyBarrel.MySubmodule.options`, so the runtime system tries to access it... but that access fails because initialization of all of the members in `MySubModule` has not completed yet.

    I've verified this by reducing the app down to the following test case.

    // MyBarreo/MyBarrel.mc
    using Toybox.System;
    
    module MyBarrel
    {
        var options = {
        };
    
        var init = new InitClass();
    
        class InitClass
        {
            function initialize()
            {
                System.println(options);
            }
        }
    }

    // MyApp/source/MyApp.mc
    using Toybox.Application;
    using Toybox.WatchUi;
    using MyBarrel;
    
    class MyApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
            System.println(MyBarrel.init); // force the barrel modules to be loaded
            return [ new WatchUi.View() ];
        }
    }

    If you remove the reference to `options` in `InitClass.initialize`, the error goes away. If you move the declaration of `options` into `InitClass` or into `InitClass.initialize`, the dependency issue goes away as well.

    I don't *personally* see this as a bug. The VM is detecting a dependency cycle when initializing data. It could be argued that this error should only occur if `options` is known to not be initialized, but I'm pretty sure that would require adding additional fields to indicate that an object has been initialized (i.e., more memory wasted by the VM).

    I would *personally* argue that using a module initializer to automatically register for sensor events is a clever trick, but it is probably not the best way to code this. I think it would be much cleaner for the module/barrel user to have control over when the sensor events are enabled and disabled via module function.

    Regardless of how I feel about the code, the easy fix is to move the declaration of `options` into `InitClass.initialize` and be done with it.