I'm taking a look at running 300+ test classes in parallel again today. The main error - in about 5% of the test classes - was:
UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record
which makes sense given the parallel execution. But the question is how to fix these. There seem to be 2 root causes...
Contact locks
The tests have Contact record inserts that had no Account specified and there is some underlying Account defaulting trigger logic. I am assuming that the lock is on the parent Account so that specifying a separate Account every time will fix. Does that seem right? It appears to work.
Custom setting locks (and User locks)
Some tests modify hierarchical custom settings to exercise some of our configuration options and so update the org-level custom settings at the same time, hence the locking problem. (The current User is also modified in a smll number off cases.) No easy solution comes to mind for these: is there a good pattern for this case? There is this known issue on the subject Document known issue with UNABLE TO LOCK ROW when testing Custom Settings.
Presently trying this ugly code for the custom setting upserts in the tests:
/**
* When tests are run in parallel, UNABLE_TO_LOCK_ROW errors occur where tests update the same custom setting.
* This class aims to get around that by retrying over a longish time.
*/
public class Retry {
public static void upsertOnUnableToLockRow(SObject sob) {
// Only ever seen one retry needed this should be lots
Integer n = 20;
Integer sleepMs = 100;
Exception lastException;
for (Integer i = 0; i < n; i++) {
try {
upsert sob;
return;
} catch (DmlException e) {
if (!e.getMessage().contains('UNABLE_TO_LOCK_ROW')) {
throw e;
}
lastException = e;
sleep(sleepMs);
}
}
throw lastException;
}
private static void sleep(Integer ms) {
Long start = System.currentTimeMillis();
while (System.currentTimeMillis() < start + ms) {
// Throw away CPU cycles
}
}
}
SELECT ... FROM CustomSetting__c FOR UPDATE
)?SeeAllData=true
. Also seems as if the custom setting rows already have an implicitfor update
;