2

I have a nightly batchable job in my SFDC org that queries a remote SFDC org for account and opportunities meeting certain criteria. Works great but every so often, I get System.calloutException read timed out but if I run it again, it works fine. So, I thought I would detect this situation and implement an automatic retry mechanism.

The code is a modest adaptation of the batch class iterable pattern from the SFDC doc

global class CustomIterable implements Iterator<Account>{ 

   public CustomIterable(){ 
      // do authentication
      // do REST GET SOQL
      // do Json.deserialize
   }   
   global boolean hasNext(){ 
       // details not important
   }    
   global Account next(){ 
       // details not important, gets next Account; handles queryMore
   } 
}

global class foo implements iterable<Account>{
  global Iterator<Account> Iterator(){
    return new CustomIterable();
  }
}

global class batchClass implements Database.batchable<Account>{ 
   global Iterable<Account> start(Database.batchableContext info){ 
       return new foo(); 
   }   
   global void execute(Database.batchableContext info, List<Account> scope){ 
       // details not important 
   }     
   global void finish(Database.batchableContext info){     
   } 
}

So, in this pattern,

  1. the constructor for CustomIterable (which does the HTTP callout) does not get invoked ...
  2. until after the start method() has returned and before the execute() method is called (that is, SFDC is invoking the iterator() method in foo ...
  3. So, if a callout exception occurs, there is no place to catch the exception to do a retry! Note the batch finish() method is never called.

I'm not happy with the alternatives:

Option 1 -- Schedule the job to run every hour. These jobs, in their constructor, look to see if there was a successful job for the day, and if yes, abort themselves. If not, they throw themselves 'over the top' to see if they can complete the mission.

Option 2 -- Have the batch job's constructor do a 'dry run' to see if the remote SOQL times out and if yes, schedule itself again for a few minutes later, otherwise crosses its fingers that the 'real' iterable won't time out a few milliseconds later.

Is there some other pattern that is more robust?

Edit

I'm an idiot. I can catch the exception in the iterator() method and should be able to retry from there; I can't get the exception back to the batch class to do a retry at the top level

2

Here's the answer (for the benefit of others)

global class CustomIterable implements Iterator<Account>{ 

   private List<Account> aList = new List<Account> ();
   public CustomIterable(){ 
      // do authentication
      // do REST GET SOQL
      // do Json.deserialize into aList
   }

   public CustomIterable(Boolean isDoNothing) {}  // alternate constructor

   global boolean hasNext(){ 
       // details not important
   }    
   global Account next(){ 
       // details not important, gets next Account; handles queryMore
   } 
}

global class foo implements iterable<Account>{
  private BatchClass bc;
  global foo(BatchClass bc) {this.bc = bc;}  // batchClass instance to signal retry req'd
  global Iterator<Account> Iterator(){
    try {return new CustomIterable();}  // this could throw a calloutException
    catch (System.calloutException e) { 
       if (e.getmessage().containsIgnoreCase('read timed out')) 
           this.bc.isRetryRequired = true;  // tell batch class retry is required
       return new CustomIterable(true);  // build an iterable that returns an empty list
     }
  }
}

global class batchClass implements Database.batchable<Account>, Database.stateful{ 
   global Boolean isRetryRequired = false;
   global Iterable<Account> start(Database.batchableContext info){ 
       return new foo(this);  // pass in 'this' so iterable can let us know if retry required 
   }   
   global void execute(Database.batchableContext info, List<Account> scope){ 
       // details not important; if timeout occurs, iterable returns scope of size 0 
   }     
   global void finish(Database.batchableContext info){     
      if (this.retryRequired)
         // use system.schedule to schedule batch class at x minutes from Datetime.now()
   } 
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.