Skip to content

Concurrent platform-link creation can assign duplicate display orders and corrupt link ordering #485

Open

Description

@Ridanshi

Summary

Platform link creation determines the next display order using a read-before-write workflow that is not transactionally protected.

Concurrent requests can assign the same display order to multiple links.

Affected Files

  • profileService.ts
  • schema.prisma

Root Cause

The creation flow calculates the current maximum display order and then inserts a new record separately.

Concurrent requests can observe the same maximum value and both create links using identical display orders.

No transactional enforcement or uniqueness constraint prevents this condition.

Reproduction

  1. Create multiple platform-link creation requests simultaneously for the same user.
  2. Allow both requests to execute concurrently.
  3. Inspect the resulting displayOrder values.
  4. Observe duplicate ordering assignments.

Expected Behavior

Each newly created platform link should receive a unique display order.

Actual Behavior

Concurrent requests can generate duplicate display orders.

Why This Is Difficult To Detect

Sequential testing behaves correctly.

The issue only appears under concurrent request execution.

Production Impact

  • Unstable link ordering
  • Ambiguous reorder behavior
  • Inconsistent profile rendering
  • Persistent ordering corruption

Suggested Fix

Protect display-order assignment with transactional guarantees or enforce uniqueness and retry behavior.

Severity

High

Activity

  1. Ridanshi commented on Jun 6, 2026

    @Ridanshi
    ContributorAuthor

    I investigated this issue and would like to work on it.

    The current platform-link creation flow appears vulnerable to concurrent display-order assignment races.

    I plan to:

    • review the order allocation workflow
    • enforce ordering consistency under concurrency
    • preserve existing link creation behavior
    • add regression tests covering concurrent requests

    Please assign this issue to me.

  2. added
    criticalIncludes schema, architecture, or other critical core functionality changes.
    on Jun 12, 2026
  3. Harxhit commented on Jun 12, 2026

    @Harxhit
    Collaborator

    @Ridanshi I have assigned this issue to you.

  4. moved this from Todo to In progress in DevCardon Jun 12, 2026
  5. added a commit that references this issue on Jun 12, 2026
    b9591d0
  6. ShantKhatri commented on Jun 13, 2026

    @ShantKhatri
    Collaborator

    @Harxhit @Ridanshi I'm not able to understand this issue, can you be specific?

  7. Ridanshi commented on Jun 13, 2026

    @Ridanshi
    ContributorAuthor

    The Problem (Issue #485)
    When two users tried to create a platform link at the same time, both requests would read the same max(displayOrder) value and then both try to insert with that same order number causing duplicate displayOrder values and corrupting the display order.

    What Changed & Why

    1. schema.prisma: DB Constraint (@@unique([userId, displayOrder]))
      Added a unique constraint so the database itself rejects any duplicate (userId, displayOrder) pair. This is the ground-truth enforcement layer.

    2. profileService.createPlatformLink: Retry Loop
      Since two concurrent requests can still race to insert before either gets rejected, we now catch P2002 (Prisma's unique constraint violation code), re-read the current max(displayOrder), and retry up to 5 times. This handles the race gracefully instead of crashing.

    3. profileService.reorderLinks: Two-Phase Transaction
      When reordering (e.g., swapping positions 2 and 3), a naive update would temporarily set both to position 2, violating the unique constraint mid-transaction. The fix: first shift all affected rows to a temporary high offset, then set the final values — so the constraint is never violated during the swap.

    4. Tests

    27 new tests in platform-link-ordering.test.ts covering all the new paths (retry logic, concurrent simulation, two-phase reorder)

    Updated $transaction mock in profile-cache.test.ts to handle both array and callback forms (Prisma supports both)

    Happy to walk through any specific file in the diff if anything is still unclear!

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendcriticalIncludes schema, architecture, or other critical core functionality changes.

    Type

    No type

    Projects

    • Status
      In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions