Ticket Created
over 3 years ago

WERETECH-12052

Conflict/race between makeWebRequest and Storage.setValue called from onUpdate on a watchface.

Hi, I was able to reproduce the following problem (both in simulator and in a Descent MK1 using SDK 3.2.5 and 4.0.6):

If the function setValue is called from onUpdate while makeWebRequest is still waiting for the webserver to return data,

the callback function is "lost" (and not called).

The following code reproduce the problem 100% of the time.



using Toybox.Application;
using Toybox.Background;
using Toybox.System;
using Toybox.WatchUi;
using Toybox.Time;
using Toybox.Communications;
using Toybox.Application.Storage;

(:background)
class PiServiceDelegate extends Toybox.System.ServiceDelegate {
  function initialize() {
    System.ServiceDelegate.initialize();
  }
  function onTemporalEvent() {
    // That URI takes about 3 seconds to answer
    var uri = "https://wb.elaine.fi/pi_slow";
    System.println("Before query");
    var options = {
      :methods => Communications.HTTP_REQUEST_METHOD_GET,
      :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
    };
    Communications.makeWebRequest(uri, null, options,
                                  method(:pi_received));
  }
  function pi_received(code, data) {
    System.println(code);
    System.println(data);
    if (code == 200) {
      Background.exit(data);
    }
  }
}

(:background)
class PiWatch extends Application.AppBase {
  public function initialize() {
    AppBase.initialize();
  }
  function onBackgroundData(data) {
    System.println("onBackgroundData: " + data);
  }
  function getServiceDelegate() {
    return [new PiServiceDelegate()];
  }
  function getInitialView() {
    Background.registerForTemporalEvent(new Time.Duration(5 * 60));
    return [new PiWatchView(), new PiWatchDelegate()];
  }
}

class PiWatchView extends WatchUi.WatchFace {
  public function initialize() {
    WatchFace.initialize();
  }
  public function onUpdate(dc) {
    System.println("onUpdate");
    // On simulator, onUpdate is ran every seconds
    // So we will call Storage.setValue between the http request
    // and the result
    // On watch same problem exists
    Storage.setValue("foo", "bar");
    // Note that problem does not exists if Storage.setValue
    // is called in onPartialUpdate
  }
}

class PiWatchDelegate extends WatchUi.WatchFaceDelegate {
  public function initialize() {
    WatchFaceDelegate.initialize();
  }
}


Here is the output:


onUpdate
onUpdate
onUpdate
Background: Before query
onUpdate
onUpdate
onUpdate
Error: Symbol Not Found Error
Details: Failed invoking <symbol>
Stack:
onUpdate
onUpdate
onUpdate



If I comment the setValue line, I get the expected result:

onUpdate
onUpdate
onUpdate
onUpdate
Background: Before query
onUpdate
onUpdate
onUpdate
Background: 200
Background: {pi=>3.140000}
onBackgroundData: {pi=>3.140000}
onUpdate
onUpdate
onUpdate
  • Hi,

    I just tested adding a onPowerBudgetExceeded.

    It is not triggered on the setValue (while waiting for callback), and error still shows up.

    But it is triggered by long running code in onPartialUpdate (loop + square root)

    (just to mention, the setValue run propertly, I can look at the data, and the error show when the server answers (after the 3 sec))

    Here is the code (and output bellow)

    using Toybox.Application;
    using Toybox.Background;
    using Toybox.System;
    using Toybox.WatchUi;
    using Toybox.Time;
    using Toybox.Communications;
    using Toybox.Application.Storage;

    (:background)
    class PiServiceDelegate extends Toybox.System.ServiceDelegate {
      function initialize() {
        System.ServiceDelegate.initialize();
      }
      function onTemporalEvent() {
        // That URI takes about 3 seconds to answer
        var uri = "">https://wb.elaine.fi/pi_slow";
        System.println("Before query");
        var options = {
          :methods => Communications.HTTP_REQUEST_METHOD_GET,
          :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
        };
        Communications.makeWebRequest(uri, null, options,
                                      method(:pi_received));
      }
      function pi_received(code, data) {
        System.println(code);
        System.println(data);
        if (code == 200) {
          Background.exit(data);
        }
      }
    }

    (:background)
    class PiWatch extends Application.AppBase {
      public function initialize() {
        AppBase.initialize();
      }
      function onBackgroundData(data) {
        System.println("onBackgroundData");
        System.println(data);
      }
      function getServiceDelegate() {
        return [new PiServiceDelegate()];
      }
      function getInitialView() {
        Background.registerForTemporalEvent(new Time.Duration(5 * 60));
        return [new PiWatchView(), new PiWatchDelegate()];
      }
    }

    class PiWatchView extends WatchUi.WatchFace {
      var i = 1;
      public function initialize() {
        WatchFace.initialize();
      }
      public function onUpdate(dc) {
        System.println("onUpdate " + i.format("%d"));
        Storage.setValue("foo", "bar");
        i += 1;

      }

      public function onPartialUpdate(dc) {
        System.println("onPartialUpdate " + i.format("%d"));
        i += 1;
        for(var j = 0; j < 6000; j++) {
          var k = Math.sqrt(j);
        }
      }
    }

    class PiWatchDelegate extends WatchUi.WatchFaceDelegate {
      public function initialize() {
        WatchFaceDelegate.initialize();
      }
      public function  onPowerBudgetExceeded(pi) {
        System.println("budget exceeded");
        System.println("average: " + pi.executionTimeAverage.format("%f"));
        System.println("limit: " + pi.executionTimeLimit.format("%f"));
      }
    }

    output:

    onUpdate 1
    onUpdate 2
    onUpdate 3
    onUpdate 4
    onUpdate 5
    onUpdate 6
    onUpdate 7
    onUpdate 8
    onUpdate 9
    onUpdate 10
    Background: Before query
    onUpdate 11
    onUpdate 12
    onUpdate 13

    Error: Symbol Not Found Error
    Details: Failed invoking <symbol>
    Stack:
    onUpdate 14
    onUpdate 15
    onUpdate 16
    onUpdate 17
    onUpdate 18
    onUpdate 19
    onPartialUpdate 20
    [...]
    onPartialUpdate 30
    onPartialUpdate 31
    onPartialUpdate 32
    onUpdate 33
    onPartialUpdate 34
    onPartialUpdate 35
    onPartialUpdate 36
    [...]
    onPartialUpdate 89
    onPartialUpdate 90
    onPartialUpdate 91
    onPartialUpdate 92
    budget exceeded
    average: 675.306030
    limit: 30.000000
    onPartialUpdate 93
    onUpdate 94
  • I haven't tested it but PiWatchDelegate should have onPowerBudgetExceeded function and because there is no it Symbol Not Found Error is thrown
  • OnUpdate is called every second in a watchface on a real device for 10 seconds after it leaves low power mode (onExitSleep is first called.)  You can switch the sim to every minute with the checkbox for Settings>Low Power.

  • Hi Jim,

    Well, it is also what I do in real apps (save data in onBackgroundData for later use).

    (That said, onUpdate is called only once a minute on the watch (only simulator call it every sec)).

    The code I've uploaded is the minimal test case to a problem I had and even if it's not a good practice, it is still a real bug.

    The reason I used setValue, in onUpdate, is when some informations are not (yet ?) available because of the dual-CPU issue on 5X/MK1/... So I had to store it (heartzones in my case) when I have a result.

    Jean

  • The way I typically do this, is in onBackgroundData, I save "data" in a location the view can see it, and also put it in Application.Storage so the data is cached for the next time the app runs.  The setValue isn't a cheap operation, as the file system is involved and when onUpdate is being called every second, that can have an impact.

    You can instead do the setValue in onStop, but there you need a gate so that only happens when onStop is called by the main app and not the background service, but that's the cheapest way to use setValue - it's only called once even if the app runs for hours.

    And since it's doing file system IO, you do not ever want to do setValue with onPartialUpdate.  It will exceed your power budget.