Scheduled Script Handling of Server Restarts

Occasionally, a scheduled script failure may occur due to an application server restart. This could be due to a NetSuite update or maintenance, or an unexpected failure of the execution environment. Restarts can terminate a script execution forcefully at any moment. Therefore, your scripts should account for restarts and be able to recover from an unexpected interruption.

In SuiteScript 2.x, in the event of an unexpected system failure, the script is restarted from the beginning. The NetSuite system can automatically detect when a scheduled script is forcefully terminated and restart the script as soon as the resources required to run the script are available. Note that in SuiteScript 2.x, yields and recovery points are not manually scripted. Therefore, handling a restart situation is simpler.

In your scheduled script, you can detect a restarted execution by examining the type attribute of the context argument. The script is being restarted if the value of this argument is (context.type === "aborted"). For example,

          /**
 * @NApiVersion 2.x
 * @NScriptType ScheduledScript
 */
define([], function(){
    return {
        execute: function (context)
        {
            if (context.type === 'aborted')
            {
                // I might do something differently
            }
            .
            .
            .
        }
    }
}); 

        

For more information about the context argument, see context.InvocationType.

The following example scripts demonstrate how restarts impact scheduled script execution:

Example: A Problematic Scheduled Script

A common use case for scheduled scripts is to perform a search and then process the results.

In this example, the script updates each sales order in the system. The filter1 filter ensures that the search returns exactly one entry per sales order. However, if the script is forcefully interrupted during its processing and then restarted, some sales orders might be updated twice.

To prevent data issues that result from re-processing, the script should use idempotent operations. This means that any operation handled by the script can repeat itself with the same result (for example, prevent creating duplicate records). Or, the script must be able to recover (for example, by creating a new task to remove duplicates).

This example script does not use idempotent operations, and a large number of sales orders could be updated twice (which may result, for example, in doubling a price on a sales order from a repeated operation).

          /**
 * @NApiVersion 2.x
 * @NScriptType ScheduledScript
 */
define(['N/search', 'N/record'], 
    function(search, record){
        return {
            execute: function (context)
            {
                var filter1 = search.createFilter({
                    name: 'mainline', 
                    operator: search.Operator.IS, 
                    values: true
                });
                var srch = search.create({
                    type: search.Type.SALES_ORDER, 
                    filters: [filter1], 
                    columns: []
                    });
 
                var pagedResults = srch.runPaged();
 
                pagedResults.pageRanges.forEach(function(pageRange){
                    var currentPage = pagedResults.fetch({index: pageRange.index});
                    currentPage.data.forEach(function(result){
                        var so = record.load({
                            type: record.Type.SALES_ORDER, 
                            id: result.id
                        });
                    //UPDATE so FIELDS>
                    so.save()
                    });
                });
            }
        }
    }); 

        

To improve a script like this, see Example: A Robust Scheduled Script and Example 5: A Robust Map/Reduce Script Example.

Example: A Robust Scheduled Script

The following example script shows how to prevent duplicate processing when a scheduled script execution is restarted. This script uses custbody_processed_flag on the sales order. It is a custom boolean field that must be previously created in the UI. When the sales order is processed, the field is set to true. The search then contains an additional filter that excludes flagged sales orders. When the script execution is restarted, only the sales orders which have not been updated are processed.

          /**
 * @NApiVersion 2.x
 * @NScriptType ScheduledScript
 */
define(['N/search', 'N/record'], 
    function(search, record){
        return {
            execute: function (context)
            {
                var filter1 = search.createFilter({
                    name: 'mainline', 
                    operator: search.Operator.IS,
                    values: true
                });
                var filter2 = search.createFilter({
                    name: 'custbody_processed_flag', 
                    operator: search.Operator.IS, 
                    values: false
                });
                var srch = search.create({
                    type: search.Type.SALES_ORDER, 
                    filters: [filter1, filter2], 
                    columns: []
                });
 
                var pagedResults = srch.runPaged();
 
                pagedResults.pageRanges.forEach(function(pageRange){
                    var currentPage = pagedResults.fetch({index: pageRange.index});
                    currentPage.data.forEach(function(result){
                        var so = record.load({
                            type: record.Type.SALES_ORDER, 
                            id: result.id
                        });
                    // UPDATE so FIELDS
                    so.setValue({
                        fieldID: 'custbody_processed_flag', 
                        value: true
                    });
                    so.save()
                  });
               });
            }
        }
    }); 

        

Related Topics

General Notices