11

I am attempting to implement a piece of scheduled apex which can work in an org with remarkably long computation time, meaning only a few hundred records can be operated on in a single context before the CPU time limit is hit. The basic idea is to enqueue queueable apex from a scheduled class and have that queued apex class call itself again if it needs to.

I currently have in place a class which is scheduled to run nightly, the execute() of which looks like (some names changed to protect the innocent)

global void execute(SchedulableContext SC) {
    System.enqueueJob(new QueueableApexClassName());
    //do stuff
}

Then the execute() of QueueableApexClassName() is

Boolean empty;

global void execute(QueueableContext SC) {
    empty = false;
    DoStuff();
    if (empty) System.enqueueJob(new QueueableApexClassName());
}

Empty is a boolean which is set in the DoStuff() method, to indicate there are no further need to enqueue jobs as the queries have returned empty (thus there are no more records to operate upon).

I cannot exactly deploy this willy-nilly to this remarkably large org, and test methods don't cut it in the dev org. Will this work, or will I get an error once I try to run it?

1
  • You can chain indefinitely, but be aware that any governor limit hit during processing will result in a sudden, terminal end to your Queueable. If you used Batchable, in contrast, you could at least run through all your records once in an attempt to process as many as you can. – sfdcfox Jan 26 '16 at 4:28
7

You can do essentially what you're asking, but there's a couple of precautions you'll want to take.

First, you'll want to create an "on/off" switch that allows you to shut down your queueables should they happen run amuck. Should that occur, you'll need some way of preventing them from continually spawning new jobs. You can easily do this by creating a class that checks the value of a custom setting. Your schedulable and queuables then checks a boolean that's been set on a utility class before it queues up another instance of the queueable.

You'll also want to check limits so you don't try to fill up your flex queue with too many pending jobs. Your method would look something like this:

@future
private static void tryToQueue()
{
    if(!MyAppConfigSupport.appEnabled) return; 
       // This is your On/off switch
    try
    {
        if((Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0) && (!empty)) 
         // I suspect your criteria is !empty

            System.enqueueJob(new QueueableApexClassName();
    }
    catch(Exception ex)
    {
        // The exception would be because of hitting limits, so... 
        // you could wait for next request...
        // or maybe try using another instance of scheduled Apex for 2 min later?
    }
}
5
  • The flex queue only applies to batches, not queueables, futures, etc. – sfdcfox Jan 26 '16 at 4:16
  • 3
    Also, no try-catch needed here (there's nothing you could catch here), and you can just Limits.getLimitQueueableJobs() > Limits.getQueueableJobs() instead. And no need to make it a @future method. – sfdcfox Jan 26 '16 at 4:25
  • That code essentially came straight from Appleman's 3rd Edition and may be somewhat out of context. The code was written in the context of @future methods and is a slight modification of something else I'm working on. – crmprogdev Jan 26 '16 at 5:35
  • 4
    Appleman's code always seems to be a bit interesting. I keep finding that people quote Appleman every time I notice weird, illogical code. – sfdcfox Jan 26 '16 at 5:44
  • Again, the above is out of context. It makes sense when in context of the rest of the code. My bad for not adjusting it further. The main point I had wanted to make was that he needed to have a switch to turn off his queuables. – crmprogdev Jan 26 '16 at 5:53
7

I can confirm that this works - I have just created these classes and executed them and they successfully ran.

For reference, here is the exact code:

Schedulable:

global class X_DeleteMeSchedulable implements Schedulable {
    global void execute(SchedulableContext sc) {
        System.enqueueJob(new X_DeleteMeQueuable());
    }
}

Queueable:

global without sharing class X_DeleteMeQueuable implements Queueable {
  Boolean empty;
  global void execute(QueueableContext SC) {
    empty = false;
    System.debug(SC);
    if (empty){
      System.enqueueJob(new X_DeleteMeQueuable());
    }
  }
}

Output:

DEBUG|QueueableContextImpl:[jobId=7074B000005GroGQAS]
1
  • thanks I needed to verify that you can enqueue the same class from within itself. – Bahman.A Sep 1 '20 at 2:53

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.