function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Ben MeyersBen Meyers 

Scheduled apex randomly stops rescheduling

I use the Schedulable class to schedule apex code to run every minute.  At random times the apex is not rescheduled and the process stops. Below is the code used.  Any thoughts? 

public class ScheduleSalesInvoiceStatus implements ScheduleDispatcherSalesInvoiceStatus.IScheduleDispatchedSalesInvoiceStatus
{
    public static string query = 'select id, quote__r.status, c2g__PaymentStatus__c, quote__c, c2g__Opportunity__c from c2g__codaInvoice__c where Quote__c != null AND ((c2g__PaymentStatus__c = \'Part Paid\' and quote__r.status != \'Invoice Part Paid\') OR (quote__r.status != \'Credited\' AND quote__r.status != \'Credit Requested\' AND c2g__PaymentStatus__c = \'Paid\' AND quote__r.status != \'Invoice Paid\'))';
    
    public void execute(SchedulableContext sc)
    {
        system.abortJob(sc.getTriggerID());    // Always abort the job on completion
        
        //Run key batch
        SaleInvoiceStatusBatch batch = new SaleInvoiceStatusBatch(query);
        Database.executeBatch(batch, 1);
    }

    public static String GetSchedulerExpression(Datetime dt)
    {
        return ('' + dt.second() + ' ' + dt.minute() + ' ' + dt.hour() + ' ' + dt.day() + ' ' + dt.month() + ' ? ' + dt.year());
    }

    private static Boolean ScheduledInContext = false;

    public static void StartScheduler()
    {
        if(ScheduledInContext) return;

        ScheduledInContext = true;
        
        List<CronTrigger> jobs = [SELECT Id, CronJobDetail.Name, State, NextFireTime
                                  FROM CronTrigger where CronJobDetail.Name='Sales Invoice Status Schedule Job'];
        if(jobs.size() > 0 && jobs[0].state != 'COMPLETED' && jobs[0].state != 'ERROR' && jobs[0].state != 'DELETED')
        {
            return;    // Already running
        }

        Set<String> activejobstates = new Set<String>{'Queued','Processing','Preparing'};
        List<AsyncApexJob> apexjobs = [Select ID, ApexClass.Name from AsyncApexJob
                                           where ApexClass.Name = 'SaleInvoiceStatusBatch' And Status in :activejobstates];
        if(apexjobs.size() > 0)
        {
            return;  // The batch is running
        }
        
        System.schedule('Sales Invoice Status Schedule Job',
              GetSchedulerExpression(DateTime.Now().addMinutes(2)),
              new ScheduleDispatcherSalesInvoiceStatus());
    }

}

global class ScheduleDispatcherSalesInvoiceStatus Implements Schedulable
{
    public Interface IScheduleDispatchedSalesInvoiceStatus
    {
        void execute(SchedulableContext sc);
    }
    
    global void execute(SchedulableContext sc)
    {
        Type targettype = Type.forName('ScheduleSalesInvoiceStatus');   
        if(targettype != null)
        {
            IScheduleDispatchedSalesInvoiceStatus obj = (IScheduleDispatchedSalesInvoiceStatus)targettype.NewInstance();
            obj.execute(sc);   
        }
    }
}

global class SaleInvoiceStatusBatch implements Database.Batchable<sObject>, Database.AllowsCallouts
{
    private string query = '';
    
    public SaleInvoiceStatusBatch(string query)
    {
        this.query = query;
    }
    
    global Database.QueryLocator start(Database.BatchableContext BC)
    {
        return Database.getQueryLocator(query);
    }
    
    /*
     * Updates quote status to invoice paid, part-paid
     * */
    global void execute(Database.BatchableContext BC, List<c2g__codaInvoice__c> invoices)
    {
        Set<ID> oppIds = new Set<ID>();
        Set<Id> invoiceIds = new Set<Id>();
       
        List<Quote> quotes = new List<Quote>();
        for (c2g__codaInvoice__c i : invoices)
        {
            try
            {
                Quote q = new Quote();
                q.Id = i.quote__c;
                if (i.c2g__PaymentStatus__c == 'Paid')
                {
                    q.Status = 'Invoice Paid';
                    oppIds.add(i.c2g__Opportunity__c);
                    invoiceIds.add(i.Id);
                }
                else if (i.c2g__PaymentStatus__c == 'Part Paid')
                {
                    q.Status = 'Invoice Part Paid';
                }

                quotes.add(q);
            }
            catch(Exception e)
            {
                Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
                String[] toAddresses = new String[] {'ben.meyers@pkware.com'};
                  mail.setToAddresses(toAddresses);
                mail.setSenderDisplayName('Quote status update error');
                mail.setSubject('The following exception has occurred: ' + e.getMessage());
                mail.setPlainTextBody(e.getMessage());
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
            }
        }

        //List of tasks to Complete
        Map<Id, Task> tToUpdateList = new Map<Id, Task>();
        
        //List of Opportunities to update Paid Date
        List<Opportunity> oToUpdateList = new List<Opportunity>();

        // If there are any opportunities to update, then proceed.
        if (!oppIds.isEmpty())
        {
            // Get tasks on Opportunity being closed, update and close if still open
            for (Task t : [select isclosed, subject, status from task t where whatid in :oppIds and t.isclosed = false
                           and (t.type = 'AR Collection' or t.type = 'Maintenance Courtesy' or t.type = 'Maintenance Collection') for update])
            {
                // Otherwise, mark the task as completed.
                t.status = 'Completed';
                
                if (!tToUpdateList.containsKey(t.Id))
                {
                    tToUpdateList.put(t.id, t);
                }
            }
            
            //Add oppids to update list
            for (Id oppId : oppIds)
            {
                Opportunity o = new Opportunity();
                o.Id = oppId;
                o.Paid_Date__c = System.today();
                oToUpdateList.add(o);
            }
        }
        
        // If there are any invoices to update, then proceed.
        if (!invoiceIds.isEmpty())
        {
            // Get tasks on Opportunity being closed, update and close if still open
            for (Task t : [SELECT isclosed, subject, status FROM task t WHERE whatid in: invoiceIds AND t.isclosed = false
                           AND (t.type = 'AR Collection' OR t.type = 'Maintenance Courtesy' OR t.type = 'Maintenance Collection') FOR UPDATE])
            {
                // Otherwise, mark the task as completed.
                t.status = 'Completed';
                
                //update t;
                if (!tToUpdateList.containsKey(t.Id))
                {
                    tToUpdateList.put(t.id, t);
                }
            }
            
            //Open AR opportunity
            Set<ID> openAROppIds = new Set<ID>();
            for (Opportunity o : [SELECT fforce_invoice__c FROM opportunity WHERE fforce_invoice__c IN: invoiceIds])
            {
                openAROppIds.add(o.Id);
            }
            
            for (Task t : [SELECT isclosed, subject, status FROM task t WHERE whatid IN: openAROppIds AND t.isclosed = false
                           AND (t.type = 'AR Collection' OR t.type = 'Maintenance Courtesy' OR t.type = 'Maintenance Collection') FOR UPDATE])
            {
                // Otherwise, mark the task as completed.
                t.status = 'Completed';
                
                //update t;
                if (!tToUpdateList.containsKey(t.Id))
                {
                    tToUpdateList.put(t.id, t);
                }
            }
        }
        
        //Update opportunities
        if (!oToUpdateList.isEmpty())
        {
            update oToUpdateList;
        }
        
        //Update tasks
        if (!tToUpdateList.isEmpty())
        {
            update tToUpdateList.values();
        }
        
        //Update list of quotes
        if (!quotes.isEmpty())
        {
            //Set recursion flag and skip validation flag
            //Recursion.flag = true;
            //QuoteStatusActions.SetSkipValidation(true);
            
            update quotes;    
            
            //Set recursion flag and skip validation flag
            //Recursion.flag = false;
            //QuoteStatusActions.SetSkipValidation(false);
        }
    }
    
    global void finish(Database.BatchableContext BC)
    {
        if (!Test.isRunningTest())
            ScheduleSalesInvoiceStatus.StartScheduler();
    }
}
ShashankShashank (Salesforce Developers) 
There is an interesting discussion around it here: http://salesforce.stackexchange.com/questions/15003/scheduled-job-does-not-execute-though-shows-in-gui