[SalesForce] How to determine how many future calls have been executed in 24 hours

I just ran into a huge gotcha in Salesforce: the 24 hr limit on # of future calls – cf. http://www.sundoginteractive.com/index.php?/sunblog/posts/governor-limit-gotchas-part-1-the-future-call/.

I have the following questions about how to handle this:

  1. How can I determine how many future calls have been made in a 24 hour period? I need to have a trigger check first so that it doesn't cause the overall upsert operation to fail on SF records if I'm over the limit. I saw a post on Developerforce boards saying it was stored in AsyncApexJobs object, but I didn't see how to query it in the API docs.

  2. What's the best way to have a custom script that I do to update a bunch of records in SF ensure that it is not making any future calls? Is there a way I could set some kind of global variable in my script that i could then reference in all the triggers so they wouldn't make future calls if I'm doing a mass update (it's an update of over 2000 records).

Currently I am in a really awkward situation where none of the leads from my external systems are getting added b/c of the future call failing in a trigger set to run whenever a Lead is upserted.

Here is the script that does the sync to Salesforce: https://github.com/techmission/mdl_custom_reports/blob/master/sf_student_sync.php. It is using an upsert function that leverages the PHP Toolkit, and the function is in the same Github repo, in sf_libs.inc

The code of the trigger is here:

trigger CityvisionUpsertLead on Lead (after insert, after update) {
   // score & qualify all leads
   // methods execute in future, so as to be non-blocking for save, and so tests will pass
   // confirm we are not in a future context or a batch context (i.e., when doing a bulk update)
   if(!System.isFuture() && !System.isBatch()) { 
     Set leadIds = new Set(); // leads to score and qualify - currently filtered in the methods, not here
     // iterate over all the leads
     for (Lead l : Trigger.new) {
        // add all to the pool for scoring and qualification
        leadIds.add(l.Id);
     }
     // invoke the methods for scoring and qualifying leads
     // if this is not being called from a future method context 
     if(!leadIds.isEmpty()) {
       CityvisionUtils.scoreLeads(leadIds);
       CityvisionUtils.qualifyLeads(leadIds);
    }
  }
}

Best Answer

(1) To determine the number of Future calls run in the past 24 hours, you can use this utility method:

public static Integer GetNumFutureCallsInLast24Hours() {
    return [select count() from AsyncApexJob 
            where CreatedDate >= :Datetime.now().addHours(-24) 
            and JobType = 'Future'];

(2) To dynamically run methods in a future context ONLY if you have future calls available, you could leverage a "dispatcher" method framework that would run your code in a future context only if you have not surpassed the 24-hour-calls Limit and Call Stack Limit yet:

// Can we run future calls right now?
public static boolean CanUseFutureContext() {
    boolean callStackLimitExceeded = Limits.getFutureCalls() >= Limits.getLimitFutureCalls());

    if (!callStackLimitExceeded) {
        // Check 24-hour rolling window limit
        // of 200 * # of Salesforce, Salesforce Platform, or Force.com One App Licenses
        // *NOTE 1*: These queries may be cacheable depending on your situation
        // *NOTE 2*: If you have not used up all of your allocated licenses,
        // you will have more available licenses than this, so you may need to adjust.
        // However, you can't use SOQL to determine 
        //how many AVAILABLE licenses you have, 
        // only how many have actually been assigned to users
        return GetNumFutureCallsInLast24Hours() < getMaxFutureCallsAllowed();
    } else return false;
}

public static Integer getMaxFutureCallsAllowed() {
    Integer usersCount = [
        SELECT
            COUNT()
        FROM
            User 
        WHERE
            Profile.UserLicense.LicenseDefinitionKey IN ('SFDC','AUL','PID_FDC_FREE')];
    return Math.max(250000, usersCount*200);
}

// Main entry point to your method: call this from a trigger, other class, etc.
public static void RunMyLogic(params) {
    if (CanUseFutureContext()) futureRunMyLogic(params);
    else RunMyLogicMain(params);
}

private static void RunMyLogicMain(params) {
   /* Your logic here */
}

@future
private static void futureRunMyLogic(params) {
   RunMyLogicMain(params);
}
Related Topic