[SalesForce] How to implement retry apex logic only till 3 times per transaction

I have written a retry mechanism for batch which triggers when the batch records fails with UnableToLockRow exception, we will call this batch from the finish methods with the failed records.

I need to retry "RetryBatchApex" batch only three times per transaction. for eg, if an exception from batch triggers this "RetryBatchApex" batch, then this batch "RetryBatchApex" should run only three times. after which we will save the records to exception log.

I dont know how to implement the retry logic, I thought of using a static counter, but that will reset as soon as the batch "RetryBatchApex" is again triggered from the finish method of "RetryBatchApex".

this is my batch class, I have commented the line where my retry check should go. Any ideas?

global class RetryBatchApex implements Database.Batchable<sObject>, Database.Stateful {

    private List<SObject> failedSObjects;

    global RetryBatchApex(List<SObject> failedSObjects) {
        this.failedSObjects = failedSObjects;
    }

    global Iterable<sObject> start(Database.BatchableContext param1) {
        return failedSObjects;
    }

    global void execute(Database.BatchableContext param1, List<Object> param2) {

    }

    global void finish(Database.BatchableContext param1) {

        List<SObject> newFailedObjects = new List<SObject>();
        if (failedSObjects != null && !failedSObjects.isEmpty()) {
            if (failedSObjects.size() + Limits.getDMLRows() < Limits.getLimitDMLRows()) {
                List<Database.upsertResult> resultList = Database.upsert(failedSObjects, false);
                List<Exception_Log__c> exceptionList = new List<Exception_Log__c>();
                for (Integer i = 0; i < failedSObjects.size(); i++) {
                    Database.upsertResult sr = resultList[i];
                    if (!sr.success) {
                        for (Database.Error err : sr.getErrors()) {
                            // add the ids sObject which failed again.
                            // We can try retrying upto 3 times.
                            if (err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW) {
                                newFailedObjects.add(failedSObjects[i]);
                            }
                        }
                    }
                }
            }
        }
        // implement retry logic here so that the RetryBatchApex runs only three times in a transaction and not more that that
        if (!newFailedObjects.isEmpty()) {
            Database.executeBatch(new RetryBatchApex(newFailedObjects));
        }
    }


}

Best Answer

You're right that a static variable won't work, as your batches are all going to execute in separate contexts. What you can do (since you already implement Database.Stateful) is store the current retry count as an instance variable, check it before you spawn a new batch, and then increment and pass the count to the newly-spawned batch:

private Integer retryCount;

global RetryBatchApex(List<SObject> failedSObjects) {
    this.failedSObjects = failedSObjects;
    this.retryCount = 0;
}

global RetryBatchApex(List<SObject> failedSObjects, Integer retries) {
    this.failedSObjects = failedSObjects;
    this.retryCount = retries;
}

Then, in finish(), you check whether or not to spawn a new batch, and give it the existing retry count + 1:

    if (!newFailedObjects.isEmpty() && retryCount < 3) {
        Database.executeBatch(new RetryBatchApex(newFailedObjects, retryCount++));
    }
Related Topic