Repeated Comm.makeWebRequest calls causes out of memory exception

I have an app where i call a REST service once every second. At first, when the response only contained two values the app ran just fine. But as I added more values to the response I started to get out of memory exceptions. Right now the response holds four values and the app crashes after five minutes, when it only had three it ran for two hours.

Might this be due to a memory leak in the makeWebRequest or are there any other known issues in that API?
  • I cannot reproduce this problem in the simulator, and I haven't bothered to try with the device. Here is my test code.

    using Toybox.Application as App;
    using Toybox.WatchUi as Ui;
    using Toybox.Graphics as Gfx;
    using Toybox.System as Sys;
    using Toybox.Timer as Timer;
    using Toybox.Communications as Comm;

    class MyBehaviorDelegate extends Ui.BehaviorDelegate
    {
    hidden var _M_view;
    hidden var _M_waiting;
    hidden var _M_count;

    function initialize(view) {
    BehaviorDelegate.initialize();
    _M_view = view;
    _M_waiting = false;
    _M_count = 0;

    _M_view.setText("Press Enter/Tap..");
    }

    hidden var _M_timer;

    function onBack() {
    if (_M_timer != null) {
    _M_timer.stop();
    _M_timer = null;
    }

    return false;
    }

    function onSelect() {
    if (_M_timer == null) {
    _M_timer = new Timer.Timer();
    _M_timer.start(self.method(:onTimer), 1000, true);

    _M_view.setText("Timer..");
    _M_count = 0;
    }
    else {
    _M_timer.stop();
    _M_timer = null;

    _M_view.setText("Press Enter/Tap..");
    _M_view.setCount(null);
    }

    return true;
    }

    function onTimer() {

    // don't issue a new request if there is an outstanding one
    if (_M_waiting) {
    return;
    }

    //var headers = {
    // "Content-Type" => Comm.REQUEST_CONTENT_TYPE_JSON
    //};

    var options = {
    //:method => Comm.HTTP_REQUEST_METHOD_GET,
    //:headers => headers,
    :responseType => Comm.HTTP_RESPONSE_CONTENT_TYPE_JSON
    };

    var params = {
    };

    var url = Lang.format("jsonplaceholder.typicode.com/.../$1$", [ _M_count % 10 + 1 ]);

    Comm.makeWebRequest(url, params, options, method(:onWebResponse));

    _M_waiting = true;
    _M_view.setText("Response..");
    }

    function onWebResponse(code, data) {
    _M_waiting = false;
    _M_view.setText("Timer..");

    _M_count += 1;
    _M_view.setCount(_M_count);

    //Sys.println(code);
    //Sys.println(data);

    //data = null;

    var systemStats = Sys.getSystemStats();
    Sys.println(Lang.format("$1$, $2$, $3$, $4$", [
    _M_count,
    systemStats.freeMemory,
    systemStats.usedMemory,
    1.0 * systemStats.freeMemory / systemStats.totalMemory
    ]));

    }
    }

    class MyView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }

    hidden var _M_text;

    function setText(text) {
    _M_text = text;
    Ui.requestUpdate();
    }

    hidden var _M_count;

    function setCount(count) {
    _M_count = count;
    Ui.requestUpdate();
    }

    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    dc.clear();

    var cx = dc.getWidth() / 2;
    var cy = dc.getHeight() / 2;

    if (_M_text != null) {
    dc.drawText(cx, cy, Gfx.FONT_SMALL, _M_text,
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }

    cy += dc.getFontHeight(Gfx.FONT_SMALL);

    if (_M_count != null) {
    dc.drawText(cx, cy, Gfx.FONT_SMALL, _M_count.toString(),
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    var view = new MyView();
    return [ view, new MyBehaviorDelegate(view) ];
    }
    }


    I've let it run for 13118 sec (3h 40m 18s) and the memory used is the same as it was when the application started.

    13210, 116848, 8280, 0.933828
    13211, 116840, 8288, 0.933764
    13212, 116848, 8280, 0.933828
    13213, 116832, 8296, 0.933700
    13214, 116824, 8304, 0.933636
    13215, 116848, 8280, 0.933828
    13216, 116816, 8312, 0.933572
    13217, 116856, 8272, 0.933892
    13218, 116680, 8448, 0.932485
    Complete
    Connection Finished
    Closing shell and port
  • Thank you for your reply.

    I tried the sample code you provided, but I changed the url to: http://jsonplaceholder.typicode.com/posts and method to POST (since that's what my app use). I also moved the Content-Type parameter into the headers property (otherwise it wont be included in the request). Then I'm seeing the same memory issue as I have.

    using Toybox.Application as App;
    using Toybox.WatchUi as Ui;
    using Toybox.Graphics as Gfx;
    using Toybox.System as Sys;
    using Toybox.Timer as Timer;
    using Toybox.Communications as Comm;

    class MyBehaviorDelegate extends Ui.BehaviorDelegate
    {
    hidden var _M_view;
    hidden var _M_waiting;
    hidden var _M_count;

    function initialize(view) {
    BehaviorDelegate.initialize();
    _M_view = view;
    _M_waiting = false;
    _M_count = 0;

    _M_view.setText("Press Enter/Tap..");
    }

    hidden var _M_timer;

    function onBack() {
    if (_M_timer != null) {
    _M_timer.stop();
    _M_timer = null;
    }

    return false;
    }

    function onSelect() {
    if (_M_timer == null) {
    _M_timer = new Timer.Timer();
    _M_timer.start(self.method(:onTimer), 1000, true);

    _M_view.setText("Timer..");
    _M_count = 0;
    }
    else {
    _M_timer.stop();
    _M_timer = null;

    _M_view.setText("Press Enter/Tap..");
    _M_view.setCount(null);
    }

    return true;
    }

    function onTimer() {

    // don't issue a new request if there is an outstanding one
    if (_M_waiting) {
    return;
    }

    //var headers = {
    // "Content-Type" => Comm.REQUEST_CONTENT_TYPE_JSON
    //};

    var options = {
    :method => Comm.HTTP_REQUEST_METHOD_POST,
    :headers => {
    "Content-Type" => Comm.REQUEST_CONTENT_TYPE_JSON
    }
    };

    var params = {
    "id" => "1",
    "name" => "test"
    };

    //var url = Lang.format("jsonplaceholder.typicode.com/.../$1$", [ _M_count % 10 + 1 ]);
    var url = "jsonplaceholder.typicode.com/posts";

    Comm.makeWebRequest(url, params, options, method(:onWebResponse));

    _M_waiting = true;
    _M_view.setText("Response..");
    }

    function onWebResponse(code, data) {
    _M_waiting = false;
    _M_view.setText("Timer..");

    _M_count += 1;
    _M_view.setCount(_M_count);

    Sys.println(code);
    Sys.println(data);

    //data = null;

    var systemStats = Sys.getSystemStats();
    Sys.println(Lang.format("$1$, $2$, $3$, $4$", [
    _M_count,
    systemStats.freeMemory,
    systemStats.usedMemory,
    1.0 * systemStats.freeMemory / systemStats.totalMemory
    ]));

    }
    }

    class MyView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }

    hidden var _M_text;

    function setText(text) {
    _M_text = text;
    Ui.requestUpdate();
    }

    hidden var _M_count;

    function setCount(count) {
    _M_count = count;
    Ui.requestUpdate();
    }

    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    dc.clear();

    var cx = dc.getWidth() / 2;
    var cy = dc.getHeight() / 2;

    if (_M_text != null) {
    dc.drawText(cx, cy, Gfx.FONT_SMALL, _M_text,
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }

    cy += dc.getFontHeight(Gfx.FONT_SMALL);

    if (_M_count != null) {
    dc.drawText(cx, cy, Gfx.FONT_SMALL, _M_count.toString(),
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    var view = new MyView();
    return [ view, new MyBehaviorDelegate(view) ];
    }
    }



    I think this proves that there is a memory leak in the API.

    How do I report this to Garmin?
  • Thank you for your reply.

    I tried the sample code you provided, but I changed the url to: http://jsonplaceholder.typicode.com/posts and method to POST (since that's what my app use). I also moved the Content-Type parameter into the headers property (otherwise it wont be included in the request). Then I'm seeing the same memory issue as I have.

    using Toybox.Application as App;
    using Toybox.WatchUi as Ui;
    using Toybox.Graphics as Gfx;
    using Toybox.System as Sys;
    using Toybox.Timer as Timer;
    using Toybox.Communications as Comm;

    class MyBehaviorDelegate extends Ui.BehaviorDelegate
    {
    hidden var _M_view;
    hidden var _M_waiting;
    hidden var _M_count;

    function initialize(view) {
    BehaviorDelegate.initialize();
    _M_view = view;
    _M_waiting = false;
    _M_count = 0;

    _M_view.setText("Press Enter/Tap..");
    }

    hidden var _M_timer;

    function onBack() {
    if (_M_timer != null) {
    _M_timer.stop();
    _M_timer = null;
    }

    return false;
    }

    function onSelect() {
    if (_M_timer == null) {
    _M_timer = new Timer.Timer();
    _M_timer.start(self.method(:onTimer), 1000, true);

    _M_view.setText("Timer..");
    _M_count = 0;
    }
    else {
    _M_timer.stop();
    _M_timer = null;

    _M_view.setText("Press Enter/Tap..");
    _M_view.setCount(null);
    }

    return true;
    }

    function onTimer() {

    // don't issue a new request if there is an outstanding one
    if (_M_waiting) {
    return;
    }

    //var headers = {
    // "Content-Type" => Comm.REQUEST_CONTENT_TYPE_JSON
    //};

    var options = {
    :method => Comm.HTTP_REQUEST_METHOD_POST,
    :headers => {
    "Content-Type" => Comm.REQUEST_CONTENT_TYPE_JSON
    }
    };

    var params = {
    "id" => "1",
    "name" => "test"
    };

    //var url = Lang.format("jsonplaceholder.typicode.com/.../$1$", [ _M_count % 10 + 1 ]);
    var url = "jsonplaceholder.typicode.com/posts";

    Comm.makeWebRequest(url, params, options, method(:onWebResponse));

    _M_waiting = true;
    _M_view.setText("Response..");
    }

    function onWebResponse(code, data) {
    _M_waiting = false;
    _M_view.setText("Timer..");

    _M_count += 1;
    _M_view.setCount(_M_count);

    Sys.println(code);
    Sys.println(data);

    //data = null;

    var systemStats = Sys.getSystemStats();
    Sys.println(Lang.format("$1$, $2$, $3$, $4$", [
    _M_count,
    systemStats.freeMemory,
    systemStats.usedMemory,
    1.0 * systemStats.freeMemory / systemStats.totalMemory
    ]));

    }
    }

    class MyView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }

    hidden var _M_text;

    function setText(text) {
    _M_text = text;
    Ui.requestUpdate();
    }

    hidden var _M_count;

    function setCount(count) {
    _M_count = count;
    Ui.requestUpdate();
    }

    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    dc.clear();

    var cx = dc.getWidth() / 2;
    var cy = dc.getHeight() / 2;

    if (_M_text != null) {
    dc.drawText(cx, cy, Gfx.FONT_SMALL, _M_text,
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }

    cy += dc.getFontHeight(Gfx.FONT_SMALL);

    if (_M_count != null) {
    dc.drawText(cx, cy, Gfx.FONT_SMALL, _M_count.toString(),
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    var view = new MyView();
    return [ view, new MyBehaviorDelegate(view) ];
    }
    }


    I think this proves that there is a memory leak in the API.

    How do I report this to Garmin?
  • Former Member
    Former Member
    Thank you for taking the time to investigate and find this bug. I have created a ticket to investigate.

    -Coleman
  • We have a fix for this that will be included in the 2.2.2 SDK release, which I expect will be available sometime before the end of the year.