4

In my controller I want to know when my batch job has fully finished running; not just the execute method but also the finish. I have tried the following and both seem to return true when execute has run:

Attempt #1

String status = getJob().Status;
Boolean hasFinished = (status != null && status != 'Aborted' && status != 'Completed' && !status.startsWith('Failed'));

Attempt #2

AsyncApexJob job = getJob();
Boolean hasFinished = job != null && job.CompletedDate == null;

Am I right? Or are these supposed to be inclusive of finish?

The reason for this question is because I am doing some record insertion in finish, but the records are not there yet when hasFinished becomes true.

6
  • You are missing some statuses. Why not just use status='completed'
    – Eric
    Commented Apr 26, 2017 at 4:13
  • @Eric Because it doesn't work...
    – sfdcfox
    Commented Apr 26, 2017 at 4:23
  • @sfdcfox so you can't query status. Or is the op trying to query status within the batch which would mean it is never finished when the query is executed? I query batch to see if they have completed and it works for me. Maybe completed is not correct status or I misunderstanding what you mean by does not work
    – Eric
    Commented Apr 26, 2017 at 4:27
  • @Eric I built a POC that shows that once status is completed, the finish method may still be running for up to a minute or more afterwards...
    – sfdcfox
    Commented Apr 26, 2017 at 4:31
  • 1
    @sfdcfox ahhh I see. Interesting. Time to revisit my implementation although actual completeness was not critical just that it was marked as complete.
    – Eric
    Commented Apr 26, 2017 at 4:34

1 Answer 1

6

So, all my experimentation has come down to this: it appears that the standard interface is technically lying to you; the finish method runs at its leisure sometime after the Completed status occurs and the CompletedDate field is populated. If you want the true completed status, you'll have to come up with an arrangement beforehand, something that your batch and your polling method agree upon.

The best I can come up with is to acquire a database lock somewhere and synchronize off of that. For example, once you detect a Completed status, query for an account with the name set to the jobId of the job that finished. In the finish method, immediately insert an account with the name of that jobId, then do your normal processing. Given that arrangement, when your polling code gets a lock on the account, you'll know that finish terminated.

My POC started a batch on kickoff, and then outputted the current status every 10 seconds. The job itself waited for about 50 seconds on each phase. Code follows:

public class q171641 implements Database.Batchable<Integer> {
    Id jobId;

    public String[] log { get; set; }

    public q171641() {
        log = new String[0];
    }

    public void startJob() {
        jobId = Database.executeBatch(this, 1);
    }

    public Integer[] start(Database.BatchableContext context) {
        fiftySeconds();
        return new Integer[] { 1 };
    }

    public void execute(Database.BatchableContext context, Integer[] scope) {
        fiftySeconds();
    }

    public void finish(Database.BatchableContext context) {
        fiftySeconds();
    }
    void fiftySeconds() {
        DateTime start = DateTime.now();
        while(DateTime.now().getTime()-start.getTime()<50000);
    }

    public void checkJobStatus() {
        AsyncApexJob job = [SELECT TotalJobItems, JobItemsProcessed, CompletedDate, Status FROM AsyncApexJob WHERE Id = :jobId];
        log.add(job.JobItemsProcessed+'/'+job.TotalJobItems+'/'+job.Status+'/'+job.CompletedDate);
    }
}

<apex:page controller="q171641" action="{!startJob}">
    <apex:form id="form">
        <span style="white-space: pre">
            <apex:repeat value="{!log}" var="l">
                <div>
                {!l}
                </div>
            </apex:repeat>
        </span>
        <apex:actionPoller action="{!checkJobStatus}" interval="10" reRender="form"/>
    </apex:form>
</apex:page>

Typical output:

0/0/Preparing/null

0/0/Preparing/null

0/0/Preparing/null

0/0/Preparing/null

0/1/Processing/null

0/1/Processing/null

0/1/Processing/null

0/1/Processing/null

1/1/Completed/(some date)

1/1/Completed/(some date)

1/1/Completed/(some date)

1/1/Completed/(some date)

author's note: actual log for finish appears at this time

1/1/Completed/(some date)

2
  • Then what I'm going to have to do is move my finish logic up to execute and run it only in the last chunk.
    – Mossi
    Commented Apr 27, 2017 at 2:32
  • @Mossi Like I said, a record lock trick might work, but yeah, you should probably finish in an execute method.
    – sfdcfox
    Commented Apr 27, 2017 at 3:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .