6

For out project we need to put users into correct public groups. This happens in a trigger when a user's contact is updated, depending on some of the fields there we decided where to put a user.

The problem is that sometimes we get this row locking error:

Failed to invoke future method 'public static void futurePutUserIntoCorrectPublicGroup(Set)' on class 'BP_User' for job id '707w000003xIXBI'
caused by: System.DmlException: Insert failed. First exception on row 0; first error: UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record: []

This method(futurePutUserIntoCorrectPublicGroup()) is just @future annotated method that calls another method(putUserIntoCorrectPublicGroup()) which does all the logic. Future here is needed to avoid MIXED_DML_OPERATION error.

After some googling and cheatsheeting, we found out that insert of a GroupMember record will lock Group table and the way to "fix" it is to query the locked records with FOR UPDATE keywords.

However, this was not possible due to the compilation error:

Result: [OPERATION FAILED]: classes/BP_User.cls: SObject type does not allow locking rows (Line: 397, Column: 36)

So the question is: how can we handle UNABLE_TO_LOCK_ROW errors on insert of a GroupMember record?

Stripped code of this method if needed:

public static void putUserIntoCorrectPublicGroup(Set<Id> userIds) {
    // Querying users, accountcontactroles and account
    Map<Id, User> userMap; // = query
    List<AccountContactRole> acrList; // = query
    Map<Id, Account> dealerAccs; // = query
    // Creating a list of public group names that we need
    List<String> consShares = new List<String>();
    // Querying all public groups with these names
    // Can't use FOR UPDATE here, compilation error:
    // Result: [OPERATION FAILED]: classes/BP_User.cls: SObject type does not allow locking rows (Line: 397, Column: 36)
    List<Group> publicGroups = [SELECT Id, DeveloperName
                                FROM Group
                                WHERE DeveloperName IN :consShares];
    List<GroupMember> gmList = new List<GroupMember>();
    // Looping through the AccountContactRoles
    for (AccountContactRole acr : acrList) {
        // Getting the account and the user of this ACR from the maps
        Account a;
        User u;
        // Finding needed public group
        Group g;
        // Create a new GroupMember object/record with GroupId being equal to the group we just found
        // And UserOrGroupId being equal to the user which is linked via Contact to this ACR
        GroupMember gm = new GroupMember(GroupId = g.Id, UserOrGroupId = u.Id);
        gmList.add(gm);
    }
    // Deleting all old GroupMember records so that we only have up-to-date data
    delete [SELECT Id FROM GroupMember WHERE UserOrGroupId IN :userIds];
    // Inserting new GroupMember records
    insert gmList;
}

I omitted most queries and loops to strip down the code and make it more readable. If we get this error, then it is on the very last line, where we insert that list.

  • I had the same issue, Fixed by creating groups within the unit test, and that fixed the issue for me. – Pasan Eeriyagama Oct 21 at 23:21
3

Pretty tricky but you could perform this logic in a Batch job, specifically in the start() method of that job. The start() methods of Batch jobs are guaranteed to never run in parallel with another start() method, i.e. you will/should never have locking problems there.

Documentation on this: Execution Governors and Limits

Maximum number of batch Apex job start method concurrent executions: 1

Note that you can only queue up to 100 Batch jobs using the Apex Flex Queue, or even just 5 if you don't have Flex Queue enabled.

Update:

We have also gone so far at a customer to create a Mutex that is used to guarantee that pieces of code never run at the same moment. We found a way to store in the DB that a piece of code is executing, without actually using DML. If you want to know more about this, give me a PM.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.