Hi all!
The code below contains a try/catch that I use a lot when developing batch jobs, especially multithreaded ones.
It deals with frequently occurring exceptions that, in some cases, can be easily solved by retrying:
- Deadlocks
- Update conflicts
- Duplicate key conflicts
Duplicate key conflicts are more rare than update conflicts, but I’ve seen them pop up under heavy load on InventDim records.
#OCCRetryCount
try
{
ttsbegin;
// do stuff here
ttsCommit;
}
catch (Exception::Deadlock)
{
// retry on deadlock
retry;
}
catch (Exception::UpdateConflict)
{
// try to resolve update conflict
if (appl.ttsLevel() == 0)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::UpdateConflictNotRecovered;
}
else
{
retry;
}
}
else
{
throw Exception::UpdateConflict;
}
}
catch(Exception::DuplicateKeyException)
{
// retry in case of an duplicate key conflict
if (appl.ttsLevel() == 0)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::DuplicateKeyExceptionNotRecovered;
}
else
{
retry;
}
}
else
{
throw Exception::DuplicateKeyException;
}
}
Related
Navy
on August 10, 2011 at 08:48
Thanks for sharing, it’s very useful.
Tommy Skaue
on August 10, 2011 at 08:55
Hi
Thanks for sharing. Please explain why you don’t have to test retrycount when you catch the deadlock. You simply retry. Wouldn’t that cause an “endless loop”?
Klaas Deforche
on August 10, 2011 at 13:06
Hi Tommy,
That a good question :) I’ve been thinking about that too, and I think I know why.
When you look at standard X++ code in AX, you can see that it is used a lot in this way.
However, sometimes this is used:
{
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::Deadlock;
}
else
{
retry;
}
}
Perhaps the code above is a safeguard against “excessive retrying” when it is not really needed.
I don’t believe retrying in case of a deadlock can cause an endless loop. If you catch a deadlock exception, it would mean that you are in the “unlucky” process that was kicked out by SQL because it detected a deadlock. When in the catch, your transaction is aborted and rolled back, which means that the deadlock was resolved. The other process will have finished by the time your process locks that resource again.
I believe the reason why you can’t simply retry in case of UpdateConflict and DuplicateKeyException is because these errors can also be catched inside a transaction, and that there is no implicit abort when you catch those exceptions. That’s the reason why my example throws that same error when the ttslevel is not 0, so it would be caught by a catch statement that is not in a transaction, causing a ttsabort. That would be this case if this code was called from within an other transaction. If you were to simply retry, you would get unexpected results because nothing was rolled back.
Sometimes, you do extra stuff in the catch, like logging. I think the retrycount check comes in handy when the problem is situated in the catch itself, when the developer wrote code that didn’t clean everyting up correctly (while handling a dupplicatekey or updateconfict exception), causing a dupplicatekey or updateconfict exception, and as a result, endless retrying.
In case of deadlock you get a “fresh start” in the beginning of each retry, so you shouldn’t experience this endless looping.
I wrote this short article for you to demonstrate try/catch in combination with transactions:
http://www.artofcreation.be/2011/08/10/try-catch-and-transactions/
Hope this clears things up – and that I’m not telling lies, correct me if I’m wrong :-).
Tommy Skaue
on August 10, 2011 at 14:24
Ok. That makes sense. The clue here is that a DeadLock will implicitly cause a ttsabort and you are likely to get through on the next attempt. You might be right about the unnecessary “safeguard”. Good to know!
d1l1yd
on October 12, 2011 at 05:50
new dax dev here…
it really helps me understanding dax :D
Ian Schofield
on July 9, 2013 at 14:22
#OCCRetryCount //Macro for the #RetryNum var
Klaas Deforche
on July 9, 2013 at 14:54
Correct, added it to the code, thanks.
shivam
on November 19, 2014 at 11:36
why we use the #occretrycount marcos
Klaas Deforche
on December 3, 2014 at 17:34
Hi Shivam,
Because it contains the #RetryNum macro that is set to 5, so we retry 5 times everywhere in AX. Should we want to change that, we only have to do it in one place.
RP
on January 8, 2016 at 01:36
You might want to add a comment in the catch block for DuplicateKeyException, just noting that it doesn’t make sense to retry unless you add code that tries to change the value for one of the fields in the table’s primary key. If you don’t change any value, there is no sense retrying because your values will still be a duplicate of a existing key.
Trackbacks
on October 4, 2023 at 09:49