Skip to main content

Change Request: Customer Record Lock Contention Fix

FieldValue
Change IDCHANGE-001
Date2026-02-26
ExtensionBC Dialing Application (Cambay Solutions) v2.1.0.0
SeverityCritical — blocking end-user workflow
StatusImplemented (pending deployment & verification)

Problem Summary

Users are unable to save changes to Customer records in Business Central. When a user edits a Customer card and clicks Save, they receive a lock timeout error. This occurs intermittently but frequently, and correlates with the Nextiva Integration Job Queue entry actively processing records.

Who Is Affected

  • All users who edit Customer records (Customer cards, Customer list)
  • The issue is most noticeable during business hours when the Job Queue is running and users are concurrently working in BC

Root Cause

The Nextiva Integration background job (Codeunit 60003) processes all unsynced phone call and email log records in a single, long-running database transaction. For each record, it:

  1. Looks up the associated Customer record
  2. Makes multiple HTTP API calls to Nextiva (to retrieve call summaries, recordings, and transcripts)
  3. Uploads files to Azure Blob Storage
  4. Creates Record Link entries attached to the Customer record
  5. Updates status flags on the phone/email log records

Each of these steps can take several seconds due to network latency. Because there is no transaction boundary between records, the entire batch — which may include dozens of records — runs as one atomic operation. Database locks acquired early in the batch (particularly on the Customer and Record Link tables) are held for the full duration, which can be minutes. Any user trying to save a Customer record during this window is blocked until the Job Queue transaction completes.

A secondary issue exists in the API pages that receive incoming phone numbers and emails from the Nextiva dialer. These pages check whether a Customer already exists and create one if not, but do not acquire a table lock before the check. Two simultaneous API calls for the same phone number could both pass the existence check and attempt to create duplicate Customer records.


Solution

Overview

Break the single long-running transaction into many short transactions — one per record — so that database locks are held only for the few seconds needed to process each individual record, then released before moving to the next.

Additionally, wrap each record's processing in error isolation so that one failed record does not abort the entire batch.

What Changes

1. Codeunit 60003 "Nextiva Integration" — Transaction Isolation (Critical)

Before: The Job Queue's OnRun trigger processed all unsynced phone logs and email logs in two repeat...until loops with no Commit() calls. The entire run was one transaction.

After: Each record is now processed in its own isolated micro-transaction:

  • TryFunction wrappers — Two new procedures (TryProcessPhoneLog, TryProcessEmailLog) encapsulate per-record processing. If any step within a record's processing throws an error (HTTP failure, blob upload failure, database error), the TryFunction catches it and rolls back only that record's partial writes. Processing continues with the next record.

  • Commit() per iteration — After each record is processed (whether successfully or not), a Commit() is called. This persists the results, releases all database locks, and starts a fresh transaction for the next record.

  • Error logging preserved — Failed records are logged to the Nextiva Error Logs table with the error message. The attempt counter still increments, so records that fail 3 times stop retrying (existing behavior).

Bonus fix: The original email log loop contained a copy-paste bug where it referenced phone log variables (ProcessCalls."No." and ProcessCalls.PhoneNumber) instead of email log variables when logging errors. This is corrected in the new implementation.

2. Codeunit 60004 "Nextiva Integration Manual" — No Changes Needed

This codeunit provides the same Nextiva integration functions but is invoked manually from a sync page, not from the Job Queue. It does not process records in a batch loop, so each manual invocation is already its own short transaction. No changes are required.

3. Extension-Level Telemetry — app.json

The applicationInsightsConnectionString property is configured in app.json to send extension-specific telemetry to Bestway's shared Azure Application Insights resource:

InstrumentationKey=766938e4-62ce-4e78-8241-a6979a5caf9f

This enables:

  • Job Queue execution traces — runtime telemetry for Codeunit 60003 (batch duration, record counts, errors) is emitted automatically by the BC platform for any extension with telemetry configured
  • TryFunction failure signals — when a TryFunction catches an error, BC emits a telemetry trace with the error details, giving visibility into per-record failures without relying solely on the Nextiva Error Logs table
  • API page call tracking — HTTP requests to Pages 80000 and 80003 are logged, which supports diagnosing the duplicate-Customer concurrency scenario
  • Correlated diagnostics — because all Bestway extensions share the same Application Insights resource, telemetry from this extension can be correlated with environment-level signals (login events, long-running queries, lock timeouts) for root-cause analysis

Telemetry data can be queried via Azure Log Analytics using the traces and customEvents tables, filtered by the extension's app ID.

4. API Pages 80000 and 80003 — Concurrency Guard (Lower Priority)

Pages affected:

  • Page 80000 ReceivePhoneNumber (receives incoming calls from the dialer)
  • Page 80003 ReceiveEmail (receives incoming emails from the dialer)

Before: Both pages check if a Customer exists, and create one if not, without acquiring a table lock first. Two simultaneous API calls with the same identifier could both pass the check and create duplicates.

After: A LockTable() call is added before the existence check. This ensures that if two API calls arrive simultaneously, the second one waits for the first to complete its insert before checking, preventing duplicate Customer creation.


Files Modified

FileObjectChange
src/Codeunit/CU60003.NextivaIntegration.alCodeunit 60003Restructured OnRun loops with TryFunction + Commit(); added TryProcessPhoneLog and TryProcessEmailLog procedures
src/Page/Pag-80000.ReceivePhoneNumber.alPage 80000Added LockTable() before Customer existence check
src/Page/Pag-80003.ReceiveEmail.alPage 80003Added LockTable() before Customer existence check
app.jsonExtension manifestAdded applicationInsightsConnectionString for extension-level telemetry to Bestway's Azure Application Insights

No changes to:

  • src/Codeunit/CU-60004.NextivaIntegrationManual.al (no batch loops; not affected)
  • Any tables, enums, permissions, or other objects
  • The app.json version has been incremented from 2.0.0.12 to 2.1.0.0 for this release

Risk Assessment

RiskLikelihoodMitigation
Commit() inside a loop means partial batches are persisted if the Job Queue is interrupted mid-runLowAlready handled — unprocessed records remain flagged as unsynced and will be picked up on the next Job Queue run. The attempt counter ensures retries.
FindSet() cursor invalidation after Commit()Very LowBC's record set cursors survive Commit() calls. This is a well-established BC pattern used by standard Microsoft codeunits (e.g., posting routines).
TryFunction rollback on failure loses partial work for that recordBy DesignThis is the intended behavior. A partially-processed record (e.g., summary uploaded but recording failed) would leave the Customer card in an inconsistent state. Rolling back and retrying on the next Job Queue run is safer.
LockTable() on API pages adds brief serializationVery LowThe lock is held only for the duration of the API page's OnInsertRecord trigger — typically milliseconds. This is negligible compared to the minutes-long locks from the Job Queue.

Verification Plan

Step 1: Confirm Diagnosis (Pre-Deployment)

Temporarily disable the Job Queue entry for Codeunit 60003 and confirm that users can save Customer records without lock errors. If this resolves the issue, it confirms the Job Queue transaction is the source of contention.

Step 2: Deploy to Sandbox

  1. Open the extension in VS Code
  2. Run AL: Download Symbols to pull dependencies
  3. Run AL: Publish (F5) to deploy to the sandbox environment

Step 3: Functional Verification

TestExpected Result
Enable the Job Queue entry for CU60003 in the sandboxJob Queue processes phone and email logs as before
While the Job Queue is running, edit and save a Customer recordSave completes without lock timeout errors
Introduce a test record with an invalid Nextiva Session IDError is logged to Nextiva Error Logs (table 80004); subsequent records continue processing
Check CustomerPhoneLog / CustomerEmailLog records after processingSummaryUpdated, TranscriptUpdated, RecordingUpdated flags are set correctly; Attempts counter increments
Check Customer cards for processed recordsRecord Links (Summary, Recording, Transcript, Email) appear correctly
Verify a record that fails 3 times stops retryingAttempts reaches 3; record is skipped on subsequent Job Queue runs
Send two simultaneous API calls to Page 80000 with the same phone numberOnly one Customer record is created (no duplicates)

Step 4: Verify Telemetry

TestExpected Result
After a sync cycle completes, open Azure Log Analytics for the Bestway Application Insights resourceTelemetry traces appear from the BC Dialing Application extension
Query traces for Job Queue execution events related to Codeunit 60003Traces show batch start/end, record count, and duration
Introduce a test record that triggers a TryFunction failureA telemetry trace is emitted with the error details (in addition to the Nextiva Error Logs entry)
Send an API request to Page 80000 or 80003The request appears in Application Insights telemetry

Step 5: Production Deployment

After successful sandbox verification:

  1. Version has been set to 2.1.0.0 in app.json
  2. Build the .app package via AL: Package
  3. Deploy to production via the Extension Management page or admin center
  4. Monitor the Nextiva Error Logs table and Azure Application Insights telemetry for the first few Job Queue cycles to confirm normal operation

Rollback Plan

If the fix introduces unexpected behavior:

  1. Uninstall the updated extension version from the environment
  2. Reinstall the previous version (2.0.0.12) from the existing .app package at Cambay Solutions_BC Dialing Application_2.0.0.12.app
  3. The original lock contention behavior will return, but users can work around it by having an admin temporarily disable the Job Queue entry during peak hours until a revised fix is prepared