2

As we have used Async operations (Future and Queueable) very heavily in our codebase. Thus for doing callouts from trigger, I started using Platform Events + Flow.

So from Trigger a PE will be raised :

Integration_With_External_System__e externalSystem = new Integration_With_External_System__e();
externalSystem.External_System__c = 'POMS';
externalSystem.JSON_Request__c = JSON.serialize(inboundTaskMap.keySet());
Database.SaveResult saveResult = EventBus.publish(externalSystem);
ExceptionHandler.logInfo('sertfhrtsh', 'sergergh', '', '', 'High', null, JSON.serialize(inboundTaskMap.keySet()) + '----------' + String.valueOf(saveResult.getId()));

// More code from trigger

After which the flow will listen to it and then process it (Callout) Something like below:

public without sharing class SendExternalAPIRequestPE {

    @InvocableMethod(Label='test callout' Callout=true)
    public static void fireAPIRequest(List<String> strings){
        List<ARecord__c> aRecords = new List<ARecord__c>();
        System.debug('strings -> '+strings.size());
        try{
            for (Integer i = 0; i < strings.size(); i++) {
                Http h = new Http();
                HttpRequest req = new HttpRequest();
                req.setEndpoint('https://jsonplaceholder.typicode.com/users');
                req.setMethod('GET');
                HttpResponse res = h.send(req); // Throws exception here randomly.
                ARecord__c aRecord = new ARecord__c();
                aRecord.testMe__c = String.valueOf(res.getStatusCode());
                aRecord.Name = 'Proper Response '+i;
                aRecords.add(aRecord);
            }
        } catch(Exception objException){
            ARecord__c aRecord = new ARecord__c();
            aRecord.testMe__c = objException.getMessage();
            aRecord.Name = 'Exception';
            insert aRecord;
        } finally {
            insert aRecords;
        }
    }
}

These all work fine. But sometimes, I see exception being logged as You have uncommitted work pending. Please commit or rollback before calling out. Which is strange because I have configured PE to be published only after commit.

So not sure which commit is still pending. As SF identified a commit is pending, is there a way to log which commit is pending here. Or whats is the issue here?

NOTE: This exception happens randomly, I can say 2 out of every 10 request shows this exception.

I debugged it more.

I added some debuglogs just after fireAPIRequest.

    debugLog = debugLog + 'Governor Limits -> '+ LimitUtils.getRemainingDMLStatement() +' -> '+ LimitUtils.getRemainingCallouts() + '\n';

And strangely for most of the executions its printing Governor Limits -> 150 -> 100

But few are getting printed as Governor Limits -> 149 -> 99

The strange part is this is an Invocable method with Callout=true, so technically this should always be a separate transaction. And the debug statement is the first statement in fireAPIRequest not sure what DML and callout already happened?

Flow diagram : Just to show no other operation is done before the apex call.

enter image description here

12
  • Hi Nagendra, you should perform callout and dml in separate transaction to prevent this error. salesforce.stackexchange.com/questions/115434/…
    – Kevan
    May 21, 2022 at 7:39
  • Not exactly true, we can do a callout before dml too. And it does not fail always. It fails randomly. May 21, 2022 at 8:00
  • The separate transaction behavior on callout=true applies to screen flows: developer.salesforce.com/docs/atlas.en-us.apexcode.meta/…
    – cropredy
    May 22, 2022 at 4:05
  • Then it should fail consistently, right. Why only fails randonly 1 or 2 out of 10 transactions. Also the PE is raised only after successfully commit, so it should never show 149 for LimitUtils.getRemainingDMLStatement() as its the first debug statement after fireAPIRequest. This is a bug in Salesforce. I will attach some evidences in my answer below after some time. May 22, 2022 at 4:49
  • Also @cropredy Even though apex is not a separate transaction. At least each platform event triggered flow runs asynchronously in a separate transaction as per doc. Which does not seem to be true if LimitUtils.getRemainingDMLStatement() returns less than 150 at the start of the transaction. developer.salesforce.com/docs/atlas.en-us.platform_events.meta/…. May 22, 2022 at 6:41

1 Answer 1

1

I forgot to post my findings.

This is working as designed. How Platform event(PE) works is as follows:

If there are 3 PEs which gets triggered within same time (few milliseconds), then PE based flow will try to consume all 3 PEs and start its execution.

In my case, my PE based flow was having three different classes making the callouts. So in real time, there were scenarios where few PEs were getting combined and all three classes were getting called in the same time parallelly. Out of which one of the classes was doing a DML.

So the issue was , when these classes gets called parallelly, they start running in same transactions, i.e under same governor limits, hence if one class does a DML few milliseconds before and if another does a callout after the DML from first class it will throw the error You have uncommitted work pending. Please commit or rollback before calling out

Solution:

We removed all DMLs and moved it to PE (Publish Immediately) based DML operation, as Publish Immediately PE does not get counted as DML operation as we can do a callout after Publish Immediately PE.

You must log in to answer this question.

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