0

Error:First error: Delete failed. First exception on row 0 with id a2A0R000003T2DMUA0; first error: ENTITY_IS_DELETED, entity is deleted: []

Note: Above error occurs to me as a concurrency Issue.

Objects Involved: Static_List_Member__c (Junction of Contact and static_List__c(Custom Contact List))

Scheduled Class:

global class FIMCBatchSchedule implements schedulable {
    global Id singleRunId {get;set;} // staticlist id for a single run 
    global List<Id> largeIds {get;set;}
    global List<Id> smallIds {get;set;}
    global String batchScope {get;set;}
    global  FIMCBatchSchedule(Id runId ){
        this.singleRunId = runId;
        smallIds = new List<Id>();
        largeIds = new List<Id>();
        batchScope  = System.Label.FIMCBatchScope;
    }

    global void execute(SchedulableContext sc){
        try{
            List<AggregateResult> aggList = new  List<AggregateResult>();
            if(String.isNotBlank(singleRunId)){ // if its a onetime one list run querry by singleRunId
                aggList = [select count(Id) from Static_List_Member__c where static_List__c=: singleRunId AND Company_Link__c!= null AND Contact_Name__r.Email!=Null AND Company_Name__c !='Unknown Employer' AND Contact_Status__c!='Inactive' GROUP By static_List__c];
                if(aggList.size()>0){
                    splitAggrList(aggList);
                } // null check aggList
            }
            else{ // if schedule class set the singleRunId to null so we querry for all fimc lists
                aggList = [select count(Id) cnt, static_List__c slId from Static_List_Member__c where static_List__r.Fimc_List__C= true AND Company_Link__c!= null AND Contact_Name__r.Email!=Null AND Company_Name__c !='Unknown Employer' AND Contact_Status__c!='Inactive' GROUP By static_List__c];
                if(aggList.size()>0){
                    splitAggrList(aggList);
                } // null check aggList
            }
            // route to appropriate async calls
            dispatchAsyncCalls(largeIds,smallIds);
        }
        catch(Exception e){
            GenericErrorHandlerUtil.sendErrorLogs('Exception in FIMCEmailDataCls',' Exception Message:'+ e.getMessage() +'\n' +'Stack Trace:'+e.getStackTraceString());        }
    }
    // slips into two list one for queueable processing and o
    global void splitAggrList(List<AggregateResult> aggList){
        for(AggregateResult agr :aggList ){
            if((Decimal)agr.get('cnt')>=4999){
                // call batch with Id's
                largeIds.add(String.valueOf(agr.get('slId')));
            }else{
                // call queueable with Id's
                smallIds.add(String.valueOf(agr.get('slId')));
            }
        }
    }
    // routes to appropriate async calls
    global void dispatchAsyncCalls(List<Id> largeIds,List<Id> smallIds){
        // call batch chain
        if(largeIds.size()>0){
            //1:isChainNeeded 2: totalIdsToProcess 3:isTrigger 4:triggerDeleteList
            FIMCBatchDelete chain = new FIMCBatchDelete(true, largeIds,False, null );
            Database.executeBatch(chain,integer.valueof(batchScope));
        }
        // call queue class
        if(smallIds.size()>0 && smallIds.size()<49){
            ID jobID = System.enqueueJob(new FIMCAsyncCall(smallIds));   
        }else if(smallIds.size()>=49){
            GenericErrorHandlerUtil.sendErrorLogs('No of Small lists exeeded 49 FROM FIMCBatchSchedule', 'Please check the number of small FIMC lists: List that has list members>4999 exceded 49'+'small list size'+smallIds.size() );
        }
    }
}

What this above scheduled class does is it looks for size of each static list and groups into largelists and small lists and this is where the branching of logic begins, For small list a queueable class is called(Which fires a future call for each list) . For large list they are processed as batches. The reason for branching is to reduce the processing time in total as batch apex takes longer to begin and execute compared to future calls.

Data : I have 17 small lists and 3 large lists in my org

Expected behavior: Now when ever the schedule class is queued up it should hand off 17 lists to queueable and 3 lists to Batch processing. This is hapenning as expected when the schedule is called to run once.

Unexpected Behavior: When the schedule is called to run at intervals(See the schedules below), the first run looks good but after first run even though my soql aggregate is returning 20 rows(17 small and 3 large) the final list sizes are 34 and 6 and it keeps getting multiplied going forward(Based on debug logs).

Schedules:

System.schedule('Schedule FIMC Logic', '0 0 0,2,4,6,8,10,12,14,16,18,20,22 * * ?', new FIMCBatchSchedule(null));

or

System.schedule('Scheduled Job 1', '0 0 * * * ?', new FIMCBatchSchedule(null));
System.schedule('Scheduled Job 2', '0 30 * * * ?', new FIMCBatchSchedule(null));

Question: What would cause this unexpected behavior? Whats even weird is I'm creating new instance for these lists in the constructor they are still piling up. Can this be a bug or Am I missing something here?

  • 1
    Not related to your error, you could write the CRON string as 0 0 0/2 * * ? where 0/2 means "every two hours starting from midnight". – sfdcfox Oct 1 '18 at 17:58
  • Thanks @sfdcfox . I remember getting an error when trying to use something like that in the past but will try and let you know! – codeyinthecloud Oct 1 '18 at 18:11
  • 3
    The / notation isn't supported for seconds or minutes, which is probably what you tried (e.g. people try to schedule every 30 minutes, which isn't supported). It works normally for the other parameters. – sfdcfox Oct 1 '18 at 18:20
2

I was able to fix the issue by moving the creation of new instances to execute method instead of constructor.

global void execute(SchedulableContext sc){
    try{
        smallIds = new List<Id>();
        largeIds = new List<Id>();
        List<AggregateResult> aggList = new  List<AggregateResult>();
        if(String.isNotBlank(singleRunId)){ // if its a onetime one list run querry by singleRunId
            aggList = [select count(Id) from Static_List_Member__c where static_List__c=: singleRunId AND Company_Link__c!= null AND Contact_Name__r.Email!=Null AND Company_Name__c !='Unknown Employer' AND Contact_Status__c!='Inactive' GROUP By static_List__c];
            if(aggList.size()>0){
                splitAggrList(aggList);
            } // null check aggList
        }
        else{ // if schedule class set the singleRunId to null so we querry for all fimc lists
            aggList = [select count(Id) cnt, static_List__c slId from Static_List_Member__c where static_List__r.Fimc_List__C= true AND Company_Link__c!= null AND Contact_Name__r.Email!=Null AND Company_Name__c !='Unknown Employer' AND Contact_Status__c!='Inactive' GROUP By static_List__c];
            if(aggList.size()>0){
                splitAggrList(aggList);
            } // null check aggList
        }
        // route to appropriate async calls
        dispatchAsyncCalls(largeIds,smallIds);
    }
    catch(Exception e){
        GenericErrorHandlerUtil.sendErrorLogs('Exception in FIMCEmailDataCls',' Exception Message:'+ e.getMessage() +'\n' +'Stack Trace:'+e.getStackTraceString());        }
}

KeyTakeAway: The schedule constructor gets called only once, but the execute method gets called multiple times as per the schedule.

  • 1
    I can definitely remember being perplexed by this until I realized the schedulable's constructor is only called once, regardless of how many times it is "executed" - this means that runtime configuration that you might use needs to be fetched in the execute() method, otherwise it remains cached in a serialized copy of the schedulable's object – cropredy Oct 2 '18 at 2:11
  • @cropredy that’s exactly what I figured!!! – codeyinthecloud Oct 2 '18 at 3:20

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.