Reproducing concurrency problems in Apex

Let's try to understand and reproduce a concurrency issue in Salesforce. In this chapter, we will take an example of a nonprofit firm, which is funded by many corporates. The fund information is saved in a custom object named fund account. This nonprofit company is in the process of going online and currently does not support a fund transfer using online payment gateways. Interested volunteers of different companies need to call the organization's contact number to provide information about their contributions. The custom object fund account stores the aggregated contributions of all the employees of a company. Now, the foundation has been set up; let's look at a simple case of a concurrency issue.

The following figure shows the schema of the custom object fund account:

Reproducing concurrency problems in Apex

Two employees of the company ABC call the customer service of this nonprofit organization at the same time. The already available fund of the company ABC is $10,000 and both the employees want to contribute $100 more.

 The Edit button of the fund account is overridden using the Visualforce page, and the save operation is done in a custom controller.

Two customer service representatives open the edit page of the fund account record for the company ABC at the same time. They do not know that the same record is opened by them for editing. Both of them can see the available fund as $10,000 and try to add $100 more. Both the representatives are able to save the record without any error or warning, and the final value is $10,100, which is wrong. The correct and expected value is supposed to be $10,200.

The following figure shows the concurrency problem that we just discussed:

Reproducing concurrency problems in Apex

As we can see in the preceding figure, there is no error or warning and data is already corrupt.

Note

This situation is also known as the race condition. No one is ever going to notice it and there is no easy way to correct it as well. The reason for this corrupt data is the Apex controller, which we used to override the standard edit page. Apex is not concurrency safe until we design it.

However, the standard Salesforce interface is intelligent and can easily handle the preceding scenario. Standard Salesforce will throw an error to the other representatives trying to save the record saying that some other user already modified it. The following figure shows an error thrown by the standard Salesforce edit page:

Reproducing concurrency problems in Apex

Note

Salesforce will throw a concurrency error only if the same record is being edited by different users; however, it will not throw an error if the same record is being edited by the same user from multiple processes, such as the standard Salesforce UI and from the Apex scheduler.

Let's generate a concurrency issue using the Apex code with the help of the following example.

In order to replicate this situation, we need to pause the execution for some time. As Salesforce does not have a thread-related class, so there is no native way to put the execution to sleep. After multiple trial and error methods, we came up with the following Apex class to pause the execution. Basically, we are using the Date class to add and subtract the days and year; as per our previous experience, date operations are a little bit slower:

/** 
*  This class is used to pause execution 
* 
*/ 
public class Timer {    
  
    //method to pause execution by n sec 
    public static void pause(Integer seconds){ 
 
        Date todaysDate = Date.today(); 
 
        for(integer i = 0; i<8900*seconds; i++) 
        { 
 
            todaysDate.addYears(1).addDays(1).addMonths(1).addYears(-1); 
 
        } 
 
    } 
 
} 

The preceding  Timer class consists of the  pause static method that takes the number of seconds as the input parameter and performs a time-consuming operation to give the effect of a pause. We identified that if we repeatedly add and subtract the date 8,900 times, it could result in a 1 sec delay; however, there is no guarantee of precision, although we are pretty sure that it will work in most cases.

To create a concurrency situation, we need to start multiple processes at the same time, working on the same record. We observed that future methods are executed as soon as they get queued for execution. So, let's create a future method to update the same fund account:

/** 
* class to reproduce concurrency issue 
*/ 
public class FundOperations { 
 
    @future 
    public static void addFund(String accountName, Decimal amount, Integer pause_second){ 
 
        List<Fund_Account__c> lstfundAcc = [SELECT Available_Fund__c FROM Fund_Account__c WHERE NAME =: accountName LIMIT 1]; 
 
        if(!lstfundAcc.isEmpty()) 
 
        { 
            //pause execution 
            Timer.pause(pause_second); 
 
            lstfundAcc[0].Available_Fund__c = lstfundAcc[0].Available_Fund__c + amount; 
 
            update lstfundAcc; 
 
        } 
 
    } 
 
} 

The addFund future method takes the following three arguments:

  • An account name that needs to be updated
  • The Amount that needs to be added to the existing fund
  • The Pause duration (in seconds)

Let's execute the following anonymous code:

FundOperations.addFund('Company ABC',100,4); 
 
FundOperations.addFund('Company ABC',100,4); 

The preceding anonymous Apex code adds two future methods to update the fund account of the company ABC with $100 each. Also, we have specified a pause of 4 seconds.

After reading the record from the database, the execution is put to sleep for 4 seconds. In the meantime, the other future method will also read the same record and sleep for 4 seconds. At this point, both threads have retrieved the same information (that is, the available fund is equal to $10,000 ) from the database, and after 4 secs sleep, they will update and commit data. Both future methods will update the value as the available fund is equal to the sum of 10,000 and 100, that is, 10,100.

As we can see, the second future method will overwrite the amount added by the first future method with no error and warning. The expected output is $10,200. However, after the execution of the preceding code, the output would be $10,100.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset