[SalesForce] Governor Limits on System.enqueueJob in Asynchronous Apex – Documentation Wrong

Is there any execution context which uses asynchronous governor limits and allows up to 50 jobs to be queued with System.enqueueJob in a single transaction, or is Salesforce’s documentation just wrong?

Since API v32.0, the primary documentation for governor limits has stated that the:

"maximum number of Apex jobs added to the queue with System.enqueueJob" is 50 in both synchronous and asynchronous contexts.

I have tested this in every asynchronous context that I’m aware of, and it appears that the true limit on asynchronous use of System.enqueueJob is actually 1.

Test Queueable Used:

public class QueueableTest implements Queueable{    
    public void execute(QueueableContext context){ 
        System.debug('do stuff');        
    }   
}

Test future method:

@future
public static void futureTest(){
    Id jobId1 = System.enqueueJob(new QueueableTest());
    Id jobId2 = System.enqueueJob(new QueueableTest());
}

Test batch job (tested start, execute, and finish independently):

public class BatchTest implements Database.Batchable<sObject> {    

    public Database.QueryLocator start(Database.BatchableContext bc){
        Id jobId1 = System.enqueueJob(new QueueableTest());
        Id jobId2 = System.enqueueJob(new QueueableTest());
        String q = 'SELECT Id FROM Contact LIMIT 1';
        return Database.getQueryLocator(q);
    }

    public void execute(Database.BatchableContext bc, List<Contact> scope){     
        Id jobId1 = System.enqueueJob(new QueueableTest());
        Id jobId2 = System.enqueueJob(new QueueableTest());        
    }

    public void finish(Database.BatchableContext bc){
        Id jobId1 = System.enqueueJob(new QueueableTest());
        Id jobId2 = System.enqueueJob(new QueueableTest());
    }  
}

Test from another queueable:

public class QueueableTest2 implements Queueable{    
    public void execute(QueueableContext context){ 
        Id jobId1 = System.enqueueJob(new QueueableTest());
        Id jobId2 = System.enqueueJob(new QueueableTest());   
    }
}

In all of the above, I get the following error:

FATAL_ERROR System.LimitException: Too many queueable jobs added to the queue: 2

It does appear to be possible to enqueue multiple jobs from a schedulable, like this:

public class SchedulableTest implements Schedulable{    
    public void execute(SchedulableContext sc){
        Id jobId1 = System.enqueueJob(new QueueableTest());
        Id jobId2 = System.enqueueJob(new QueueableTest());
    }
}

However, it is already established that scheduled apex uses synchronous governor limits.

So what am I missing here? I am not asking why the limit would be 1. I just want to know whether I've misunderstood something or if the documentation is inaccurate.

Best Answer

Yes, the documentation in question is incorrect. You can prove that the Limits class does not conform to what is written therein fairly easily:

public class Demo implements Queueable
{
    public void execute(QueueableContext context)
    {
        system.assertEquals(1, Limits.getLimitQueueableJobs());
    }
    @future
    public static void doStuff()
    {
        system.assertEquals(1, Limits.getLimitQueueableJobs());
    }
}

Proving it for batches is a fairly straightforward alteration of the above.


Some pieces of the documentation, however, do touch on this point in a way that properly explains the governor limits involved. They are somewhat lacking in clarity, but you can only execute one job from within a job. Here's one such example:

Chaining Jobs

If you need to run a job after some other processing is done first by another job, you can chain queueable jobs. To chain a job to another job, submit the second job from the execute() method of your queueable class. You can add only one job from an executing job, which means that only one child job can exist for each parent job. For example, if you have a second class called SecondJob that implements the Queueable interface, you can add this class to the queue in the execute() method as follows:

public class AsyncExecutionExample implements Queueable {
    public void execute(QueueableContext context) {
        // Your processing logic here       

        // Chain this job to next job by submitting the next job
        System.enqueueJob(new SecondJob());
    }
}

Also from the same document:

When chaining jobs, you can add only one job from an executing job with System.enqueueJob, which means that only one child job can exist for each parent queueable job. Starting multiple child jobs from the same queueable job isn’t supported.

Related Topic