27

I have code that must be called as @future as it requires all the resources one can get. The only problem is that in 1% of the cases this code is called from a Batch and therefore fails with

System.AsyncException: Future method cannot be called from a future or batch method

Is the new Queueable interface a solution to this? Meaning can I convert this @future into an Apex class that implements Queueable and fix this problem?

Or would I need to convert my calling Batch into a series of Queueable calls?

What I already tried:

I tried to call multiple Queueable's (one per record) from the Batch and ran into

Too many queueable jobs added to the queue: 2

When I instead passed in a List<Id> and let the Queueable to iterate through this list be restarting itsself for remaining records in its execute() method I got

Maximum stack depth has been reached.

4 Answers 4

34

Yes, Queueable can be called from Database.Batchable classes. You must go through enqueueJob for this to work:

public class BatchClass implements Database.Batchable<SObject> {
    public Database.QueryLocator start(Database.BatchableContext context) {
        return Database.getQueryLocator([SELECT Id FROM Account]);
    }
    public void execute(Database.BatchableContext context, Account[] records) {
        System.enqueueJob(new QueueClass(new Map<Id, Account>(records).keySet()));
    }
    public void finish(Database.BatchableContext context) {
    }
}

public class QueueClass implements Queueable {
    Set<Id> recordids;
    public QueueClass(Set<Id> recordIds) {
        this.recordIds = recordIds;
    }
    public void execute(QueueableContext context) {
         // Do something here
    }
}

Note that you're limited to just one enqueueJob call per execute in the Database.Batchable class. This is to prevent explosive execution (a so-called "Rabbit Virus" effect).

4
  • Is that one call limit in a batch context documented? I don't see it here.
    – Adrian Larson
    May 7, 2015 at 20:40
  • 5
    @AdrianLarson 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. It's not obvious, but the key here is jobs. Scheduled classes, batchable classes, and queueable classes are all jobs (AsyncApexJob). While in any such context, you can only add one job. There's also a ... bug, as far as I can tell, where you can't callout and chain in the same transaction.
    – sfdcfox
    May 7, 2015 at 20:57
  • I gathered as much about the callout bug from the comment thread. Nice inference on the batch limit, I wouldn't have picked up on that implication.
    – Adrian Larson
    May 7, 2015 at 21:05
  • This limit applies to the finish method, not just the execute method. Which it shouldn't IMO. >>>>>> Note that you're limited to just one enqueueJob call per execute in the Database.Batchable class. This is to prevent explosive execution (a so-called "Rabbit Virus" effect).
    – Todd
    Feb 15, 2019 at 0:32
10

Wasted about 2 months to get an answer from salesforce support why I get

Too many queueable jobs added to the queue: 2

when documentation (https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm) says that the limit is 50 for entry point?

The root cause was in the undocumented limit for invocation of System.enqueueJob from batch context mentioned by @sfdcfox

In my case, to prevent hitting of this limit I checked the context in which my method executes, see code below:

if (FALSE == System.isBatch() || FALSE == System.isFuture()) {
    System.enqueueJob(worker);
}
4
  • How, then, do you run your Queueable job in the batch context? Just skip it?
    – KendallB
    Mar 17, 2016 at 3:23
  • @KendallB, In my case I simply avoid queueable job when trigger runs from batch context. But yo can enqueue job SINGLE time from batch execute() method - not more than one time. Mar 17, 2016 at 10:21
  • 1
    Thanks the reply. It appears I'm chaining (using a Batch + Queueable) and calling out at the same time, which is leading to the same error you reported above.
    – KendallB
    Mar 17, 2016 at 16:24
  • @KendallB, Chaining also a problem - true thing Mar 18, 2016 at 11:41
6

As a workaround you can use Platform Events to publish it from the batch. In that case it would be a 'legal' way to process some work in separate transaction. And from Platform Event trigger you can run future or Queueble

3

You can prevent this by checking the limit before enqueuing:

if(Limits.getQueueableJobs() < Limits.getLimitQueueableJobs()){
   System.enqueueJob(worker);
}
1
  • just wanted to add that won't help you to avoid System.AsyncException if you chain Queueable jobs from test context. i just hit that bug.
    – hlopetz
    Feb 7, 2019 at 19:38

You must log in to answer this question.

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