Apex Flow Control – Preventing Infinite Loops
When you build your first few triggers everything is so peaceful. The grass is green, the birds are chirping, and everything is working wonderfully. You continue to develop more triggers, classes, batch jobs, @future classes, and then one day your realize, “Great Scott!, I have created a monster”. If you connected all the classes and triggers that were related it would look like a giant spaghetti dinner.
When this happens you will undoubtedly run into one very specific problem. You have a piece of code A that causes code B to execute which then causes code A to execute which then causes code B to execute. OH NO! Infinite loop, governor limits AHHHHH!
Fear not! For there is a very simple solution to this problem. The extremely clear and concise diagram below shows a common scenario.
Let’s say we have an update trigger that sends a group of objects to be processed by an @future asynchronous method. There is a good chance this method will need to update the records. This would normally cause the trigger to execute and the @future method to be called again, hence our unstoppable loop. So how do we stop this loop? Before we perform the update on the records we need to say, “Hey, this is a update from a @future methods, don’t do anything”. We can do this by creating a simple Boolean value in a utility class and can be accessed by anycode in context of the update operation.
Below is the code of how something like this would work. First let’s look at the very simple solution. Yup, this is it. Some minor changes to our @future and trigger code and this is it.
public class utility{ public static boolean isFutureUpdate; }
Here is the trigger, we only want the code in this trigger to execute if the isFutureUpdate variable is not true.
trigger updateSomething on Account (after insert, after update) { /*We only want this trigger to perform it's logic when the update is not from an @future method */ if(utility.isFutureUpdate == null || utility.isFutureUpdate == false){ Set<Id> idsToProcess = new Se<Id>(); for(Account acct : trigger.new){ if(acct.NumberOfEmployees > 500){ idsToProcess.add(acct.Id); } } //Send Ids to @future method for processing futureMethods.processLargeAccounts(idsToProcess); } }
And here is our @future method. The only thing we need to add is one line of code to set the isFutureUpdate variable to true before we perform the update.
public class futureMethods{ @future public static void processLargeAccounts(Set<Id> acctIDs){ List<Account> acctsToUpdate = new List<Account>(); /* Do awesome stuff with apex code */ /*Before we perform this update we want to set the isFutureUpdate boolean in our utility class to true */ utility.isFutureUpdate = true; /*Now we can perform the update. The trigger will still fire but none of the code inside of it will execute and this method will not be called again*/ update acctsToUpdate; } }
Now when the trigger is fired the @future method will not be called again.
This example shows the interaction between asynchronous operations but you can use this same solution if you have triggers that update triggers and cause a loop.
Thanks. I always wondered how to use static variables to prevent Infinite loops. Now it is clear.
A great post. Can you clarify, however, how (or when) the static variable utility.isFutureUpdate becomes null or false again? I see where it’s set to true in the futureMethods class… does it stay true globally (so the trigger sees it as true) only as long as that futureMethods class is still running? In other words, when futureMethods finishes, is that when isFutureUpdate becomes null again?
K1rk, you are correct. The utility.isFutureUpdate boolean will only stay true for the context of the current apex transaction. So if the @future method updates accounts which then causes additional triggers to execute they will all see this as true. As soon as all code has executed the value will return to null.
As chance may have it Jeff Douglas posted about this exact issue on the same exact day as me. The concept is the same but the code is a little different.
Check it out here: http://blog.jeffdouglas.com/2009/10/02/preventing-recursive-future-method-calls-in-salesforce/
Great advice – thanks for sharing this! Your touch of humor helps the medicine go down