MYOB Invoice Sync Serialisation (v3.9.1)
DB-level named lock + claim-before-POST pattern that stops concurrent sync runs from double-posting invoices to MYOB
Overview
A Gio-flagged incident on Trello iDdBEqFx showed MYOB receiving duplicate invoices for several projects that had only been invoiced once on the platform side. Investigation traced the cause to the end-of-batch flush pattern in InvoiceProcessor combined with two concurrent sync entry points: the 20:00 cron and the admin "Sync Now" button. Under concurrent invocation, both paths could read the same unflushed batch of processable invoices and both would POST to MYOB before either marked them processed.
v3.9.1 ships on the same day as v3.9.0 with a two-layer serialisation fix.
Layer 1: DB-level named lock
The invoice processor on both platform and ordering sides now acquires a MySQL named lock at the top of processInvoices() and reprocessInvoice(), and releases it in a finally block:
GET_LOCK('fts_myob_invoice_sync', 0)Timeout 0 means a second caller sees "lock busy" immediately and returns false rather than blocking. Both sides share the same lock name so a platform-side sync and an ordering-side sync cannot run concurrently against the same DB rows.
Return type on both methods changed from void to bool: true = ran, false = skipped due to lock.
Layer 2: Claim-before-POST pattern
The per-invoice processor now sets isProcessed = true plus requestPayload and flushes before calling the MYOB client. If the worker dies between the flush and the POST, the row is durably marked "attempted" and the bulk sync cannot pick it up again. An operator can resolve it explicitly via the existing reprocess path.
Per-invoice flush replaces the end-of-batch flush, so a crash mid-batch no longer rolls back successfully-posted invoices.
Layer 3: Per-send audit log
The per-invoice processor writes a TYPE_MYOB_SYNC note with "MYOB sync claim: posting invoice" plus the request payload before the POST. Pairs with the existing success/error entries so every MYOB-bound request has a pre/post pair in the audit trail. Duplicate-post investigation is now trivial: count pairs, not just post-event notes.
Caller behaviour
- The
POST /myob/syncendpoints (both platform and ordering sides) now return HTTP 409 with a "another sync run is currently in progress" flash when the lock is held. - The admin Reprocess action on the invoice surfaces the same message instead of silently retrying.
- The currently-dormant MyobSyncMessage handler is hardened for future use.
Why not increase PHP session timeout instead
A shorter-term fix would have been to bump the 20:00 cron's PHP timeout so it finishes flushing before the admin clicks Sync Now. That doesn't address the root cause (unsafe concurrent reads of unflushed state) and it still breaks under any worker crash scenario. The lock + claim pattern is durable.
Related
- Trello
iDdBEqFxtracks the original duplicate-invoice report. - Almario's parallel Sale Check form-save fix ships in the same release - see Sale Check Form-Save.
Sale Check Form-Save Row Preservation
Empty rows submissions from stale tabs no longer wipe saved Sale Check rows
Clone RFI Template - Per-Category Checklist Status
Cloning an RFI template now stamps a category-specific default status on each new checklist item so the checklist reflects the RFI request