What is a good pattern for recursion blocking in generic trigger handlers?

Here I’m talking about a handler that covers all the TriggerOperations and that accepts SObjects of arbitrary type. And the recursion problem is where a change caused by a trigger results in that trigger getting called again and so on creating an infinite loop.

So an example trigger would be:

trigger ExampleTrigger on Example__c (before insert, before update, before delete,
        after insert, after update, after delete, after undelete) {

    SObjectType t = Example__c.SObjectType;
    MyHandler.handle(t, Trigger.operationType, Trigger.oldMap, Trigger.newMap);
}

with the handler:

public inherited sharing MyHandler {

    public void handle(SObjectType t, TriggerOperation op,
            Map<Id, SObject> oldMap, Map<Id, SObject> newMap) {

        // Logic conditional on t and op that does updates that cause recursion
        ...
    }
}

Often single static Boolean flags are talked about to block trigger recursion. (Though an approach based on change checking that is designed to settle to the correct state can be needed instead to produce the correct business results.)

But for the simple case of complete blocking, (and now I’m writing this I think the answer is “yes”), is this pattern sufficient?

public inherited sharing MyHandler {

    // Implements equals and hashCode
    private class Kind {
        ...
    }

    private static final Set<Kind> BLOCKED = new Set<Kind>();

    public void handle(SObjectType t, TriggerOperation op,
            Map<Id, SObject> oldMap, Map<Id, SObject> newMap) {

        if (BLOCKED.contains(new Kind(t, op))) return;
        ...
        BLOCKED.add(new Kind(t, TriggerOperation.BEFORE_INSERT));
        BLOCKED.add(new Kind(t, TriggerOperation.AFTER_INSERT));
        insert sobs;
        ...
    }
}

Answer

Remember that triggers will be called in chunks if you create/update/delete (generally in chunks of 200 items). Perhaps you could adjust this to track operations against ID, something like:

static Map<TriggerOperation, Set<Id>> blockedObjectIdsByOperation;

That way if the object has already been through the trigger for the given operation type the execution can be suppressed but this won’t interfere with chunked trigger calls.

I don’t think there’s an issue with before insert since such a trigger shouldn’t get invoked iteratively on an object (though there could be some edge cases).

Attribution
Source : Link , Question Author : Keith C , Answer Author : Phil W

Leave a Comment