Skip to content

fix: take the level check's unique lock after the commit is saved - #1075

Open
Akiyo974 wants to merge 1 commit into
dev-null-sh:productionfrom
Akiyo974:fix/level-check-lock
Open

Akiyo974 wants to merge 1 commit into
dev-null-sh:productionfrom
Akiyo974:fix/level-check-lock

Conversation

@Akiyo974

Copy link
Copy Markdown
Contributor

Closes #1066.

CheckUserLevel is a unique job, and Laravel takes its lock when the job is dispatched, even with ->afterCommit(). CreateCommit and UpdateCommitStatuses dispatched it inside their transaction. With the database cache store, a lock still held by an earlier check makes DatabaseLock fail its insert and fall back to an update. On Postgres the failed insert has already aborted the transaction, so the update fails with 25P02 and everything rolls back:

  • posting: a second approved commit within the lock window of the first returned a 500 and was lost;
  • moderation: approving a commit whose author still had a check queued failed the same way, whether a moderator or the AI moderation job approved it.

Both now dispatch the job from DB::afterCommit(), as CreateCommitStar already does, so the lock is taken outside the transaction, where a failed insert can't abort anything.

Tests: the suite runs every test inside a RefreshDatabase transaction, so it can't replay "a lock already exists" without aborting the test's own transaction. The new tests check the property that matters instead: with the database cache store, every query on cache_locks runs outside the transaction that saves the commit or the approval. They're in CommitControllerTest and CommitApprovalControllerTest, and both fail without the change.

I also replayed the issue on a local Postgres with the database cache and no worker, outside any test transaction. Before the change, the second approved commit failed with SQLSTATE[25P02]: In failed sql transaction. After it, both commits were saved. The full suite passes (4,053 tests, 100% coverage), along with type coverage, PHPStan, Pint and Rector.

Copilot AI balanced review requested due to automatic review settings October 23, 2026 20:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Both affected dispatch paths defer unique-lock acquisition until after commit, with focused regression coverage and no unresolved findings.

No open issues 🎉

What changed in this PR

This PR addresses #1066 by deferring CheckUserLevel dispatch until commit creation or approval has completed, preventing database-backed unique-lock conflicts from aborting those PostgreSQL transactions.

Changes:

  • Move both level-check dispatch paths into DB::afterCommit() callbacks.
  • Add regression tests verifying that lock queries execute outside the transaction saving the commit or approval.
File Description
tests/​Feature/​Controllers/​CommitControllerTest.php Verify lock acquisition occurs after the commit-saving transaction.
tests/​Feature/​Controllers/​CommitApprovalControllerTest.php Verify lock acquisition occurs after the approval-saving transaction.
app/​Actions/​UpdateCommitStatuses.php Dispatch authors’ level checks after status changes are committed.
app/​Actions/​CreateCommit.php Dispatch the level check after commit creation is committed.

Review effort: Balanced

@arifhossen-dev

Copy link
Copy Markdown

I tested this locally (macOS, PHP 8.5, Postgres 18) by merging it onto the current production (704f8c8). The fix still applies cleanly, but both new tests fail after the merge:

  • #1157 dropped users.username in favour of handle. CommitApprovalControllerTest now errors with column "username" of relation "users" does not exist.
  • In CommitControllerTest, config(['app.admins' => [$user->username]]) now sets a null admin. The commit stays pending, no lock is taken, and the test fails with Expecting [] not to be empty.

After changing those two lines to handle (['handle' => 'admin'] and $user->handle), both tests pass. With CreateCommit and UpdateCommitStatuses reverted to production, both fail. So the tests catch the bug for posting and moderation 👍

A rebase onto production with those two renames should make this ready. FYI, #1252 also targets #1066 but only covers the posting path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Second approved commit 500s while CheckUserLevel still holds its unique-job lock

3 participants