0

We have external Systems using an rest-endpoint in our Org. In the called function a certain record is called (some kind of tracker) which gets updated everytime the function runs with an success.

Most of the time everything works fine, but when the load increases (in a certain time of the year, due to more customers) we often get the QueryException UNABLE_TO_LOCK_ROW, because the endpoint is called multiple times and the record is always queried and updated. The Query already uses the "FOR UPDATE" keyword.

I already have some approaches to solve this, but my main problem is how to reproduce the error to verify the solution works.

I guess it is not possible via unittests, but also I tried it via a anonymous script calling the endpoint and also via postman, but it didn't work. There is either no exception or a different one.

I know that there is this question Reproduce UNABLE_TO_LOCK_ROW error but it is 8 years old and the linked youtube video is not accessible anymore.

Can somebody provide some hints or example code to reproduce this?

Endpoint which is called

global static Response post() {  
    Data data = Request //some logic returns data to create an account 
    Account a = insertAccount(data);
}

When the Account is inserted the accountTriggerHandler runs, and the function where the records are locked:

NumberHelper__c numberHelper = [SELECT Id, currentNumber__c FROM NumberHelper__c in :accounts FOR UPDATE LIMIT 1] // there is only 1 record

for (Account a : accounts){
    a.accNumber = numberHelper.currentNumber__c;
    numberHelper.currentNumber__c ++;
}

update autoNumber;  //UNABLE_TO_LOCK_ROW error

In my scripts I just tried make fast calls to the endpoint, but this probably can't work, because of the user context.

6
  • when the load increases (in a certain time of the year, due to more customers) we often get the QueryException UNABLE_TO_LOCK_ROW, because the endpoint is called multiple times and the record is always queried and updated : please edit your question and show your code that locks the record as well as your test script.
    – identigral
    Commented Jun 8, 2023 at 18:06
  • I added some examples
    – N4go
    Commented Jun 12, 2023 at 13:34
  • It's hard to diagnose these issues without seeing all of the code, workflow rules, etc that can run on a transaction where you are getting the lock. Take a look at this record locking cheatsheet salesforce.stackexchange.com/questions/404717/… , go through your code/flows and see if you're doing any DML in the cheatsheet that could cause locking issues. if so, you might need to put them in a @future or queueable/async/after? trigger.
    – Zerkz
    Commented Jun 12, 2023 at 14:00
  • if you wrap your SOQL in a class (selector class), then at unit test time, you can dependency inject a mock selector class that throws the QueryException allowing you to test your recovery strategy
    – cropredy
    Commented Jun 12, 2023 at 15:39
  • @Zerkz There are no flows etc. no other classes or whatever which use this object (custom setting). It's whole purpose is to set some id on account creation. And it's used only in this function of our accountTriggerHandler (beforeInsert), so it is used every time an account is inserted (if the account fulfills the requirements).
    – N4go
    Commented Jun 13, 2023 at 13:32

1 Answer 1

0

I grabbed and edited a testmethod in my code base to show how to mock a selector class to throw an exception. Admittedly, this is based on ApexMocks and relies on fflib Selector classes but you should get the idea

Code Under Test

 class MyClass {
   
    public void doStuff() {
       try {
          Number_Helper__c[] numberHelpers = NumberHelpersSelector.newInstance().selectByAccountIds(accountIds);
       }
       catch (QueryException qe) {
          if (qe.getMessage().contains('UNABLE_TO_LOCK_ROW')) { .. do retry }
       }
     }
  }

Unit Test Class

@IsTest
private static void givenUnableToLockRowVerifyRetry () {
    fflib_ApexMocks mocks = new fflib_ApexMocks();

    
    //  Given mock UNABLE_TO_LOCK_ROW Exception
    Exception mockUnableToLockRowException =
            new System.QueryException('... UNABLE_TO_LOCK_ROW ...');

    // Given mock selector(s)
    NumberHelpersSelector mockNumberHelpersSelector =
           (NumberHelpersSelector) mocks.mock(NumberHelpersSelector.class);
    mocks.startStubbing();
    mocks.when(mockNumberHelpersSelector.SObjectType()).thenReturn(Number_Helper__c.SObjectType);

    // mock throwing exception on call (regardless of arg values)
    ((INumberHelpersSelector)mocks.doThrowWhen(mockUnableToLockRowException,mockNumberHelpersSelector))
            .selectByAccountIds((Set<Id>fflib_Match.anyObject());
    mocks.stopStubbing();
    
    // Given mocks injected
    Application.Selector.setMock(mockNumberHelpersSelector);

    // when code-under-test invoked
    new MyClass().doStuff();

    // verify the retry occurred

}

You must log in to answer this question.

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