Repository navigation
refactor(model): drop the redundant abort rollback from the transactional advisory-lock body - #4338
Conversation
…ory-lock body $advisoryLockTransactionBody() rolled the transaction back in its outer finally when the callback neither committed nor threw, i.e. when the request ended with abort. Since #4335, invokeWithTransaction() itself rolls back on abort ($invokeTransactionMethod()), and this method only runs through invokeWithTransaction(), so that rollback is redundant. Removing it also drops the two flags only it read and the outer catch that only set one of them, which leaves the outer try catch-free (cross-engine invariant 22). The inner catch-free release on abort or throw and the release after a failed commit are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
… guard $advisoryLockTransactionBody() no longer has a try with both a catch and a finally, so the guard needs no exceptions. With develop's locking.cfm and an empty allowlist the guard fails at the old outer try; with this branch it passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
Review @ c102d09: source looks good. Final verdict after matrix 37316988407 and PR CIRemoval is behaviour-neutral on every path
The guard. Docblock. The updated comments now describe the abort rollback as coming from No findings. On the matrix I'll read |
Wheels Test Results222 580 tests 217 459 ✅ 1h 25m 54s ⏱️ Results for commit c102d09. |
Final: APPROVE @ c102d09 (matrix 37316988407, all 32 legs read; PR CI read)Matrix
PR CI: 18 pass, 3 skipping, 0 fail, 0 pending. Behaviour is identical on the normal, throw, commit-failure, abort and migration-wrapper paths, as set out in my source review. No open findings. |
Summary
A follow-up to #4330 and #4335.
$advisoryLockTransactionBody()(the body ofwithAdvisoryLock(transaction = true)) rolled the transaction back in its outerfinallywhen the callback had neither committed nor thrown, which means the request ended withabort. Since #4335,invokeWithTransaction()rolls back on abort itself ($invokeTransactionMethod()), so that rollback is redundant. This PR removes it, along with what only existed for it:committedandthrewflags (only that rollback read them);catch (any) { threw = true; rethrow; }.The outer
tryis now catch-free, soTryCatchFinallyGuardSpec(#4337) no longer needs its one exception, and this PR removes it.Unchanged:
try/finallythat releases the lock on abort or throw;Why removing the rollback can't change behaviour
The removed block ran only when
!committed && !threw:committedis set.threw.On abort:
Normal route.
$runWithAdvisoryLockTransactional()reaches this method only throughinvokeWithTransaction(), whosetransactiondefaults tocommit. When a Wheels transaction is already open, it takes a separate path ($runAdvisoryLockInOpenTransaction()). So this method runs inside$runInTransaction()→$transactionBody()→$invokeTransactionMethod(), whose catch-freefinallyrolls back on abort on every engine.Lucee and Adobe ColdFusion. The removed rollback ran first, and the one in
$invokeTransactionMethod()right after it. Both are on the same open transaction, and the matrix in fix(model): roll back a Wheels transaction when the request ends with abort inside it #4335 showed the second as a no-op. Now only the latter runs, with the same end state.BoxLang. The removed rollback never ran on abort: its
tryhad a catch clause, and BoxLang skips such afinallyon abort (invariant 22). So nothing changes there.The migration wrapper. When a migration wraps the call (
request.$wheelsTransactionWrapper),invokeWithTransaction()takes thealreadyopenroute, and the body runs inside the migration's own transaction rather than through$invokeTransactionMethod(). On an abort there:cftransactionwhen the request is aborted, which matches every abort case in the fix(model): roll back a Wheels transaction when the request ends with abort inside it #4335 matrix;finallyanyway.So that route ends the same way too.
With the outer
catchgone, the outerfinallynow also runs on BoxLang abort. It only holds the release-after-failed-commit check. On abort the innerfinallyhas already released the lock (release.done), so it's a no-op.Tests
No new spec. #4330's and #4335's specs are the proof:
advisoryLockAbortReleaseSpec: abort and redirect with and withouttransaction = true, lock freed;transactionAbortRollbackSpec: writes rolled back on abort;advisoryLockTransactionalSpec,advisoryLockExclusionSpec,advisoryLockTenantSpecandlockingSpec.Guard. With an empty allowlist,
TryCatchFinallyGuardSpecpasses on this branch. With develop'slocking.cfmit fails at the old outertry(/model/locking.cfm:244), so the exception was real and is now gone.modeldirectory before merging develop (pass / fail / error; the six lock and transaction specs: 0 fail, skips identical to develop's database-specific ones):After merging develop (with #4337):
modeldirectory on BoxLang, Lucee 7 and Adobe ColdFusion 2025 with MySQL (1438 / 1444 / 1435) and SQL Server (1450 / 1450 / 1447), 0 fail / 0 error. The lock and transaction specs pass 37 with 4 database-specific skips on each. Thesecuritydirectory, including the guard with its empty allowlist: 400 / 0 / 0.internal113.modeldirectory: 1388 / 0 / 0.🤖 Generated with Claude Code