The CreatorCon Call for Content is officially open! Get started here.

RESTResponseV2 async timeout

Che Pazzo
ServiceNow Employee
ServiceNow Employee

I am running an async execution of sn_ws.RESTMessageV2:

var request = new sn_ws.RESTMessageV2(name, method);

var response = request.executeAsync();

response.waitForResponse(60);

In this case, if the response takes longer than 60s, it throws a `com.glide.ecc.ECCResponseTimeoutException` error. This is *not* a standard javascript Error object (e.g. it uses `class` instead of `name`).

Is there any documentation anywhere about this? The best I can find is in the Scripting Outbound REST doc that reads: "Might throw exception timing out waiting for response in ECC queue.", which isn't very helpful.

A walk through the properties shows the following list, so I can piece together how to handle them, but if there is actual documentation out there, that would be better.

properties:

  • getClass
  • wait
  • localizedMessage
  • printStackTrace
  • getLocalizedMessage
  • notifyAll
  • initCause
  • getMessage
  • cause
  • message
  • getCause
  • notify
  • addSuppressed
  • setStackTrace
  • getStackTrace
  • hashCode
  • equals
  • toString
  • getSuppressed
  • stackTrace
  • suppressed
  • class
  • fillInStackTrace

thanks,

-MikeB

9 REPLIES 9

Hi, 

Were you able to resolve this issue. I'm also getting timeouts for response and need a way to handle it. Does Servicenow ignores REST responses that come after specified time ?

Che Pazzo
ServiceNow Employee
ServiceNow Employee

 

Only the workaround that I posted (ex.getClass()). There is still zero documentation that I can find. For other errors, I can use if (ex instanceof ReferenceError) {}. I guess it's not so bad. you just end up with code that looks like this:

 

 

catch (ex) {
    // known error states with non-fatal workarounds
    if (ex.getClass() == 'class com.glide.ecc.ECCResponseTimeoutException') {
        maybe_try_again();
    }
    if (ex instanceOf ReferenceError) {
        deal_with_this();
    }
    // rethrow error
    throw ex;
}

My biggest issue is with the lack of documentation.

 

Agree, i raised HI support ticket and was in 6hrs call explaining the problem ..still no luck :(. Retry is one option but, if the end point is always sending response with delays we will enter into a loop. At some point we need to stop the retries.

Using waitForResponse(XXX seconds); is an option but this is blocking threads and waiting for XXX seconds to send transaction out which is really weird to observe but that's what we are seeing in my test cases and HI support is not able to answer be the behavior. 

Thanks for the quick response.

Also, i added following to my catch block

catch(ex) {
responseBody = "An error occurred during ebonding update transaction";
status = '500';
}

with the above thing added is not giving any advantage because after executeAsync() is fired it just waits for response and if no response if found it's just quitting ...no trace of what is happening.

Not sure how you were able to catch the exception on ECC timeout.

Che Pazzo
ServiceNow Employee
ServiceNow Employee

response.waitForResponse(timeoutSecs) throws the error.

Also, remember that executeAsync is not fully async. response.getBody() is also blocking and there is no way to pass in a callback. If you want true Async, you need to use a WF or ScriptAction. Async allows your code to continue, but any attempt to read the response is blocking.

 

In my case, I wasn't trying to extend or do anything about the Timeout, I was only trying to verify that it failed due to timeout as opposed to some other reason, then recast the error so anyone calling my script could use standard if (ex instanceof) to figure out what to do next. 

 

Here is an example class that I created to get around the various ways the REST message could fail.

MyClass.prototype = {

    initialize: function() {
        // Sometimes the RESTMessage Response appears to have been successful (response.haveError() is false) and response.getBody() returns
        // a string that is not actually something returned by the server (I wonder if these are stdErr messages).
        // This is a list of such known messages.
        // If the ResponseBody is ever one of these strings, we can assume that something has failed and need to act accordingly.
        //
        // Personally, I think this is a bug in RestMessage because it should be returned as an error.
        // Will have to investigate if this behavior is fixed in RestMessageV2. 
        this.errorMessages = [
            'Socket timeout',
        ];
    };

    sendRestMessage: function(method, uri, content) {
        var errorMsg;
        var request;
        var response;
        var responseBody;
        var responseData;
        this.log.debug("sendRestMessage("+JSON.stringify({method:method, uri:uri, content:content})+")");
        request = this._createRestMessage(method, uri, content);
        response = request.execute();
        this.log.info("[sendRestMessage] Sending a "+method+" request to "+request.getEndpoint());
        responseBody = this._handleRestResponse(response);
        responseData = this._parseResponseBody(responseBody);
        return responseData;
    },


    _handleRestResponse: function(response, timeoutSecs) {
        var errorMsg;
        var errorCode;
        this.log.debug("_handleRestResponse("+JSON.stringify({response:response})+")");
        if (timeoutSecs === undefined) {
            timeoutSecs = this.asyncWaitSeconds;
        }
        try {
            // if using a MID server query the ECC queue for results.
            // if not using a MID server, this will return immediately.
            // For async requests, this will throw an error:
            //   `com.glide.ecc.ECCResponseTimeoutException`,
            // but this is not a standard Javascript `Error` object. It is something else.
            // I can find no documentation on it.
            response.waitForResponse(timeoutSecs);
        }
        catch (error) {
            // if this is an ECCResponseTimeoutException, then we want to recast as an CustomErrors.Error
            if (error.getClass() == 'class com.glide.ecc.ECCResponseTimeoutException') {
                errorMsg = '[_handleRestResponse] '+error.getMessage();
                this.log.error(errorMsg);
                throw new CustomErrors.TimeoutError(errorMsg);
            }
            // otherwise, rethrow it
            throw error;
        }
        responseBody = response.getBody();
        this.log.debug("[_handleRestResponse] Response: "+responseBody);
        if (response.haveError()) {
            errorCode = response.getStatusCode();
            errorMsg = "[_handleRestResponse] RESTResponseV2 returned: "+response.getErrorMessage()+"\n"+
                       "Server returned: "+responseBody;
            this.log.error(errorMsg);
            throw CustomErrors.generateError(errorMsg, errorCode);
        }
        if (!responseBody) {
            // this will ensure that the responseBody is properly parsed.
            // e.g. DELETE returns an empty body
            responseBody = '{}';
        }
        // see comment at top where this.errorMessages is defined.
        if (this.errorMessages.indexOf(responseBody) !== -1) {
            errorMsg = "[_handleRestResponse] "+responseBody;
            this.log.error(errorMsg);
            throw new CustomErrors.RESTError(errorMsg);
        }
        return responseBody;
    },

    _parseResponseBody: function(jsonString) {
        var parsedResults = { };
        this.log.debug("parsedResults("+JSON.stringify({jsonString:jsonString})+")");
        try {
            parsedResults = JSON.parse(jsonString);
        }
        catch (error) {
            // Throwing new error because existing error is: "SyntaxError: Unexpected token: o"
            // which is not terribly descriptive and somewhat misleading, so it's best to just
            // not include it.
            errorMsg = '[_parseResponseBody] Server returned non-JSON result: ' + jsonString;
            throw new CustomErrors.ParsingError(errorMsg);
        }
        return parsedResults;
    },


    type: 'MyClass'
};