Change Request: Customer Record Lock Contention Fix
| Field | Value |
|---|---|
| Change ID | CHANGE-001 |
| Date | 2026-02-26 |
| Extension | BC Dialing Application (Cambay Solutions) v2.1.0.0 |
| Severity | Critical — blocking end-user workflow |
| Status | Implemented (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:
- Looks up the associated Customer record
- Makes multiple HTTP API calls to Nextiva (to retrieve call summaries, recordings, and transcripts)
- Uploads files to Azure Blob Storage
- Creates Record Link entries attached to the Customer record
- 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:
-
TryFunctionwrappers — 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), theTryFunctioncatches 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), aCommit()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 Logstable 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
TryFunctioncatches an error, BC emits a telemetry trace with the error details, giving visibility into per-record failures without relying solely on theNextiva Error Logstable - 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
| File | Object | Change |
|---|---|---|
src/Codeunit/CU60003.NextivaIntegration.al | Codeunit 60003 | Restructured OnRun loops with TryFunction + Commit(); added TryProcessPhoneLog and TryProcessEmailLog procedures |
src/Page/Pag-80000.ReceivePhoneNumber.al | Page 80000 | Added LockTable() before Customer existence check |
src/Page/Pag-80003.ReceiveEmail.al | Page 80003 | Added LockTable() before Customer existence check |
app.json | Extension manifest | Added 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.jsonversion has been incremented from 2.0.0.12 to 2.1.0.0 for this release
Risk Assessment
| Risk | Likelihood | Mitigation |
|---|---|---|
Commit() inside a loop means partial batches are persisted if the Job Queue is interrupted mid-run | Low | Already 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 Low | BC'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 record | By Design | This 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 serialization | Very Low | The 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
- Open the extension in VS Code
- Run AL: Download Symbols to pull dependencies
- Run AL: Publish (F5) to deploy to the sandbox environment
Step 3: Functional Verification
| Test | Expected Result |
|---|---|
| Enable the Job Queue entry for CU60003 in the sandbox | Job Queue processes phone and email logs as before |
| While the Job Queue is running, edit and save a Customer record | Save completes without lock timeout errors |
| Introduce a test record with an invalid Nextiva Session ID | Error is logged to Nextiva Error Logs (table 80004); subsequent records continue processing |
Check CustomerPhoneLog / CustomerEmailLog records after processing | SummaryUpdated, TranscriptUpdated, RecordingUpdated flags are set correctly; Attempts counter increments |
| Check Customer cards for processed records | Record Links (Summary, Recording, Transcript, Email) appear correctly |
| Verify a record that fails 3 times stops retrying | Attempts reaches 3; record is skipped on subsequent Job Queue runs |
| Send two simultaneous API calls to Page 80000 with the same phone number | Only one Customer record is created (no duplicates) |
Step 4: Verify Telemetry
| Test | Expected Result |
|---|---|
| After a sync cycle completes, open Azure Log Analytics for the Bestway Application Insights resource | Telemetry traces appear from the BC Dialing Application extension |
Query traces for Job Queue execution events related to Codeunit 60003 | Traces show batch start/end, record count, and duration |
| Introduce a test record that triggers a TryFunction failure | A telemetry trace is emitted with the error details (in addition to the Nextiva Error Logs entry) |
| Send an API request to Page 80000 or 80003 | The request appears in Application Insights telemetry |
Step 5: Production Deployment
After successful sandbox verification:
- Version has been set to
2.1.0.0inapp.json - Build the
.apppackage via AL: Package - Deploy to production via the Extension Management page or admin center
- Monitor the
Nextiva Error Logstable and Azure Application Insights telemetry for the first few Job Queue cycles to confirm normal operation
Rollback Plan
If the fix introduces unexpected behavior:
- Uninstall the updated extension version from the environment
- Reinstall the previous version (2.0.0.12) from the existing
.apppackage atCambay Solutions_BC Dialing Application_2.0.0.12.app - 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