✏️ Explanatory Question

How does Dynamics 365 Finance & Operations handle update conflicts and deadlocks? Explain optimistic concurrency control (OCC), the role of RecVersion and forUpdate, and how you write a proper retry pattern.

👁 9 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

D365 F&O • X++ INTERVIEW

Update Conflict & Deadlock Handling in Dynamics 365 Finance & Operations

Question 11 — Optimistic concurrency, RecVersion, forUpdate, and the retry pattern.

Interview Question

How does Dynamics 365 Finance & Operations handle update conflicts and deadlocks? Explain optimistic concurrency control (OCC), the role of RecVersion and forUpdate, and how you write a proper retry pattern.

Model Answer (Short)

D365 F&O uses Optimistic Concurrency Control (OCC) by default. Instead of locking a record when it is read, it tracks a version number called RecVersion. When you later update() or delete(), the kernel compares the RecVersion held in your buffer with the one currently in the database. If they differ — meaning another session changed the row in between — it throws an UpdateConflict exception. A deadlock occurs when two sessions each hold a lock the other needs. Both situations are recoverable: you wrap the transaction in try/catch and use the retry statement (with a retry-count guard) to re-run the logic on fresh data.

Detailed Explanation

Optimistic Concurrency Control (OCC)

  • The default concurrency model — no update lock is taken when the record is read.
  • Each record carries a RecVersion value that changes on every update.
  • At update()/delete() time, the buffer's RecVersion is compared with the database's.
  • If they match → the update succeeds and RecVersion is set to a new value.
  • If they differ → an UpdateConflict exception is thrown.

forUpdate and pessimistic locking

Adding forUpdate to a select marks the record for a coming update. Under OCC this does not place a database update lock (UPDLock) — the conflict is still detected via RecVersion at write time. To force a genuine pessimistic lock you would use pessimisticLock() or the optimisticLock hint appropriately.

Deadlock

A deadlock happens when session A holds a lock that session B wants, while B holds a lock A wants — neither can proceed. SQL Server picks a victim and D365 F&O surfaces it as an Exception::Deadlock, which you handle by simply issuing retry.

RECOVERY PATTERN
try/catchcatch Deadlock / UpdateConflictretry on fresh data

Do not use ttsAbort

In D365 F&O you generally do not call ttsAbort: when an exception is caught outside the transaction, the framework automatically rolls back the whole transaction. Explicit ttsAbort is considered an anti-pattern.

Prerequisites (Rule 5)

  • Visual Studio with the Dynamics 365 developer tools.
  • A custom model / package for your objects.
  • The #OCCRetryCount macro available (declare a retry-count macro if using a custom limit).
  • A transaction scope (ttsbegin/ttscommit) around the update/delete.

Code Example — Full retry pattern

Handling both deadlock and update conflict with a bounded retry:

#OCCRetryCount

try
{
    ttsbegin;

    // ... update or delete process ...

    ttscommit;
}
catch (Exception::Deadlock)
{
    // Simply retry on a deadlock
    retry;
}
catch (Exception::UpdateConflict)
{
    if (appl.ttsLevel() == 0)
    {
        if (xSession::currentRetryCount() >= #RetryNum)
        {
            // Give up after too many attempts
            throw Exception::UpdateConflictNotRecovered;
        }
        else
        {
            retry;
        }
    }
    else
    {
        throw Exception::UpdateConflict;
    }
}

Exclusive control with forUpdate

protected void execute()
{
    SalesLine  salesLine;
    LineAmount lineAmount;

    ttsbegin;
    while select forUpdate salesLine
    {
        lineAmount = this.calcLineAmount(salesLine);
        salesLine.LineAmount = lineAmount;
        salesLine.update();          // RecVersion checked here
    }
    ttscommit;
}

Set-based update to reduce conflicts

protected void execute()
{
    SalesLine salesLine, salesLineForUpdate;

    ttsbegin;
    while select salesLine
    {
        LineAmount lineAmount = this.calcLineAmount(salesLine);

        update_recordset salesLineForUpdate
            setting LineAmount = lineAmount
            where salesLineForUpdate.RecId == salesLine.RecId;
    }
    ttscommit;
}

Update Conflict vs. Deadlock

Aspect Update Conflict Deadlock
Cause Another session changed the row (RecVersion mismatch) Two sessions each waiting on the other's lock
Detected by OCC RecVersion comparison at write SQL Server lock manager
Exception Exception::UpdateConflict Exception::Deadlock
Recovery retry with count guard retry
Prevention Shorter transactions, re-read before update Consistent lock order, proper indexes

Points the interviewer wants to hear

  • D365 F&O uses Optimistic Concurrency Control by default.
  • RecVersion mismatch at write time triggers an UpdateConflict.
  • Under OCC, forUpdate alone does not take a DB update lock.
  • Handle deadlock/conflict with try/catch + retry, guarded by a retry count.
  • Avoid ttsAbort — rollback is automatic on a caught exception.

Likely Follow-up Questions

  • What is the difference between optimistic and pessimistic locking in D365 F&O?
  • Why must you guard retry with a retry-count check?
  • Why is ttsAbort discouraged in D365 F&O?
  • How can proper indexing reduce deadlocks in bulk updates?

Key Takeaway

D365 F&O relies on optimistic concurrency and RecVersion to detect update conflicts, and on SQL Server to detect deadlocks. Both are recoverable with a disciplined try/catch + retry pattern (with a retry-count guard) — and you should never resort to ttsAbort.