I use FOR UPDATE locking in a query but when the first execution completed, the second execution (started while records were locked) seems to use the old data set.
I played around for a while and figured out, that when I query them by Id I get the updated records, but when I query them by a text field (generic parent Id), I get the old set.
So for my solution I have to query it twice, first to wait until the first execution releases the records, and again to get the right data afterwards. I would really love to get rid of this second query, since I'm close to the limits already.
So why is this and is there a way to solve this with a single query?
All I have is parent Ids. They are both using the same selector. I never know, which executes first.
This is some code glued together from SE to reproduce this problem:
vf page:
<apex:page controller="Rowlock" >
<script>
addEventListener("load", function() {
insert();
setTimeout(function(){
update();
setTimeout(function(){
query();
}, 1000);
}, 1000);
}, true);
</script>
<apex:form id="form">
<apex:actionFunction name="insert" action="{!insertAccount}" reRender="form"/>
<apex:actionFunction name="update" action="{!updateAccount}" reRender="form"/>
<apex:actionFunction name="query" action="{!queryAccount}" reRender="form"/>
</apex:form>
</apex:page>
Controller:
public with sharing class Rowlock {
Id accountId;
String name = randomString(8);
String changedName = name + 'changed';
public void insertAccount() {
Account a = new Account(Name = name);
insert a;
accountId = a.Id;
}
public void updateAccount() {
Account a = [SELECT Name FROM Account WHERE Name = :name FOR UPDATE];
lockItForSomeSeconds(2);
a.Name = changedName;
update a;
}
public void queryAccount() {
List<Account> updatedRecords = [SELECT Name FROM Account WHERE Name = :changedName FOR UPDATE];
if(updatedRecords.isEmpty() || updatedRecords[0].Name == name) {
throw new MyException('Working on invalid Records');
}
else {
throw new MyException('Working on updated Records');
}
}
// Replace with Id SOQL to make it work:
// [SELECT Name FROM Account WHERE Id = :accountId FOR UPDATE]
// Unimportant helpers:
private void lockItForSomeSeconds(Integer x) {
DateTime start = DateTime.now();
for(integer i = 0; i < 3000000; i++) {
if(DateTime.now() > start.addSeconds(x)) {
break;
}
}
}
private static String randomString(Integer len) {
final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
String randStr = '';
while (randStr.length() < len) {
Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
randStr += chars.substring(idx, idx+1);
}
return randStr;
}
class MyException extends Exception{}
}