fix(events): handle P2002 slug conflicts from concurrent event creation#406
Open
Ridanshi wants to merge 1 commit into
Open
fix(events): handle P2002 slug conflicts from concurrent event creation#406Ridanshi wants to merge 1 commit into
Ridanshi wants to merge 1 commit into
Conversation
Root cause
----------
generateUniqueSlug() checked slug uniqueness with a findUnique() read
before calling event.create(). Two concurrent requests for an event with
the same name could both observe the slug as available, pass the pre-check,
and race to insert. The loser received Prisma error P2002 (unique constraint
on the slug column). The catch block in the POST handler treated every error
as a generic 500, so callers had no way to distinguish a transient slug
collision from a real database failure, and the request was not retried.
A secondary issue: generateUniqueSlug() used while(true) with no exit
bound, making an infinite loop possible if the slugExists callback always
returned true.
Fix
---
slug.ts
- Replace while(true) with a for loop bounded by MAX_SLUG_RETRIES (10).
- Add a final fallback attempt using a longer random suffix before
throwing, so the bound is always honored.
- Export MAX_SLUG_RETRIES so callers and tests can reference it.
event.ts
- Wrap slug generation + event.create in a for loop bounded by
MAX_CREATE_ATTEMPTS (5).
- Catch P2002 specifically: regenerate the slug and retry. Any other
error exits immediately as a 500 — no unnecessary retries.
- Replace the inline ad-hoc authentication preHandler on POST, join,
and leave with the standard preHandler: [app.authenticate] pattern
used by every other route plugin in the codebase. The previous
workaround left request.user unpopulated after a successful jwtVerify()
call, causing all authenticated routes to throw on (request.user as
any).id and return 500.
event.test.ts
- Fix buildApp() to decorate app.authenticate (sets request.user),
matching the preHandler contract used by the fixed routes.
- Add _count to all attendees-endpoint mocks (previously missing, causing
event._count.attendees to throw and return 500).
- Add 6 new tests covering: P2002 retry success on second attempt,
multiple consecutive P2002 retries, retry budget exhaustion → 500,
no retry on non-P2002 errors, concurrent same-name requests, and
the end-to-end regression scenario where a TOCTOU collision is
resolved by a suffixed slug on the retry attempt.
- Fix all pre-existing test failures (29 → 0 failures; 42 tests pass).
Closes Dev-Card#311
|
Hi, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #311
Problem
Event creation currently performs slug uniqueness checks using a read-before-write pattern.
A slug is first checked for availability and then later used during event creation. Under concurrent requests creating events with the same name, multiple requests can observe the slug as available and attempt insertion simultaneously.
This creates a race condition where one request succeeds and the other fails with a Prisma
P2002unique constraint violation. The failure is currently surfaced as a generic 500 Internal Server Error.Root Cause
The issue stems from a classic time-of-check vs time-of-use (TOCTOU) race condition.
Current flow:
Under concurrent execution:
P2002unique constraint errors.Additionally:
P2002errors.Solution
Implemented deterministic handling for concurrent slug creation conflicts.
Event Creation
P2002unique constraint violations.Slug Generation
Route Reliability Improvements
Changes
apps/backend/src/routes/event.ts
P2002handling.Slug generation flow
Test Suite
Tests
Event test suite results:
New coverage includes:
P2002handlingImpact
Low risk.
The fix preserves existing slug behavior while making event creation resilient under concurrent requests.
Users creating events normally will see no behavioral changes.
Under concurrent creation scenarios, slug conflicts are now handled deterministically rather than returning unexpected 500 responses.
Verification