Background woes : not very helpful message.

Hi,

Really struggling with something that "works unless..."

I have narrowed it down to after my makeWebRequest call in a background ServiceDelegate (yes, this is a WF).

I have done exhaustive .println() everywhere and I can confirm that everything is as I would expect up until the bug.

EG: I have something like this:

(:background)
class ServiceManagerDelegate extends System.ServiceDelegate {
    function onTemporalEvent() {
    	var comms = new Dostuff();
    	var responseCallback = method(:onReceive);  // set responseCallback to onReceive() method
    	var callData = Storage.getValue("nextCall");
    	comms.doIt(responseCallback,callData);
    }
	function onReceive(responseCode,data) {
		var response = {"type"=>"onTemporalEvent"};
		if(responseCode != 200) {
			response["error"] = responseCode;
		} else {
			response["data"] = data;
		}
		Background.exit(response);
	}
}
/////////////
(:background)
class Dostuff {
 	function initialize() { 	}
    function doIt(callback,details) {
 		var cb = callback;
		System.println("NEXT CALL "+details);
		var identifier = details[0];
		var url = details[1];
		var payload = details[2];
		var isPost = details[3];
		var options =  {
		  	:method => isPost ? Communications.HTTP_REQUEST_METHOD_POST : Communications.HTTP_REQUEST_METHOD_GET,
		   	:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
		};
		System.println("URL: "+ url);
		System.println("PAYLOAD IS: "+payload);
		System.println(payload == null);
		System.println("POST: "+isPost);
		System.println("OPTIONS: "+options);
		Communications.makeWebRequest(url, payload, options, cb);
    }
 }

If I set Storage "nextCall" at the very beginning and leave it, everything is fine. It all works.

But...

As soon as I change the "nextCall" value in Storage I see:

Error: Symbol Not Found Error
Details: Failed invoking <symbol>
Stack: 

Yep, that really is everything - no line number, no clues.

This happens a few seconds after my makeWebRequest so it looks as if it is the method(:receiveCallback) that is failing.

So... 

Has garbage collector cleaned up my callback or something? 

This one is horrid.

G

  • Are you trying to save something to Storage in your background?  You can only read Storage in a background service and not write it.

    I don't see it in the code you posted.  Where are you changing it?

    you also probably want to null check what you get when you read Storage, as it will be null when your app is installed and the background can run as soon as you register the temporal event in that case.

  • All of that is done prior to scheduling the call - nothing is written in background.

    Specifically, I flag call in progress before starting temporal and set the flag to false in onBackgroundData.

    Then, in the foreground, I can call at intervals:

        	if(!callInProgress()) {
        		nextInQueue();
        	}

  • (PS: Yes, I have null checked what I get - it is all good.)

    But to be sure to be sure I've added this in my service delegate...

    if(callData == null) {
        		Background.exit({"type"=>"onTemporalEvent","error"=>"No call data found."});
        	}

  • In case it is relevant:

        function onBackgroundData(data) {
    		var type = data == null ? "" : data["type"];
    		// Code...
    		if("onTemporalEvent".equals(type)) {
    			_nextCallAt = null;
    		}
        }
    	function startTemporal() {
    		// Register for background temporal event as soon as possible.
    		var lastTime = Background.getLastTemporalEventTime();
    		if (lastTime) {	_nextCallAt = lastTime.add(new Time.Duration(5 * 60));	} 
    		else {			_nextCallAt = Time.now();								}
    		Background.registerForTemporalEvent(_nextCallAt);
    	}
        function callInProgress() {
        	return _nextCallAt != null;
        }
    	function nextCallAt() {
        	return _nextCallAt;
        }

  • What I see is kind of odd.  You understand the background can run when the WF isn't running, right?  For example, if you are recording a run with the native app, the background can run, and it's not until you stop/save/discard the run will your WF runs again and onBackgroundData is called.. Since you are using a duration, it will keep running every 5 minutes, and with Time.now() that will only run right away if it's been 5 minutes or more since the last run.

    also, using the background code I see, on onBackground data,

    if you do

    if(data instanceof Number)

    you can see if it's data or an error code.

    It's not really clear what you are trying to do here.  Like when is startTemporal() called?

  • Since you are using a duration, it will keep running every 5 minutes, and with Time.now() that will only run right away if it's been 5 minutes or more since the last run.

    I was specifically trying to avoid that!

    I thought that adding a duration to a moment got a moment?

    https://developer.garmin.com/downloads/connect-iq/monkey-c/doc/Toybox/Time/Moment.html#add-instance_method

    add(duration) ⇒ Toybox::Time::Moment

    var lastTime = Background.getLastTemporalEventTime();
    		if (lastTime) {	_nextCallAt = lastTime.add(new Time.Duration(5 * 60));	} 
    		else {			_nextCallAt = Time.now();								}

    if(data instanceof Number)

    What I've done instead is this:

    function onBackgroundData(data) {
    	Toybox.System.println("ON BACKGROUND DATA CALLED");
    	Toybox.System.println("WITH "+data);
    	

    And... onBackgroundData is never called!

    Like when is startTemporal() called?

    View.onUpdate() calls an app method to check the queue. If there is something queued and no temporal event running (callInProgress() == false) then it can add something to the queue and restart temporal events.

  • Yes, I missed the add.  Sorry.

  • No worries.

    I'm stripping it back to basics to see where it starts crashing.

  • I _think_ (_hope_) it was an artefact of how I was testing. I was in the sim and using the trigger background event command. Only thing is, it seemed to trigger a background event but also left the background event I'd requested running.

    So it fired twice, but by the second one I had cleaned up the handler...

  • Actually, no.

    I had to set request content type headers.