Skip to content

fix(nimbus): prevent kinto preview sync from clobbering concurrent user state transitions#15619

Merged
jaredlockhart merged 1 commit into
mainfrom
15618
May 13, 2026
Merged

fix(nimbus): prevent kinto preview sync from clobbering concurrent user state transitions#15619
jaredlockhart merged 1 commit into
mainfrom
15618

Conversation

@jaredlockhart
Copy link
Copy Markdown
Collaborator

@jaredlockhart jaredlockhart commented May 13, 2026

Because

  • The kinto preview sync task hydrated experiments in memory across slow Kinto network calls, then called experiment.save(), clobbering any concurrent write — most visibly a Request Launch on a Preview experiment, which appeared to succeed in the HTMX response but reverted on refresh
  • All three sections (push, expire, unpublish) had the same lost-update shape

This commit

  • Replaces experiment.save() with conditional QuerySet.update() in all three sections, gating each write on the status the task observed and writing only the columns it owns
  • Adds regression tests for the push and expiry races

Fixes #15618

…ites

Because

* nimbus_synchronize_preview_experiments_in_kinto hydrated experiments
  in memory, made slow Kinto network calls, then called experiment.save()
  inside transaction.atomic, writing all fields back from the stale
  in-memory instance
* Any concurrent write to a Preview experiment during the kinto push
  window — most visibly a user clicking Request Launch
  (PreviewToReviewForm committing status=DRAFT, status_next=LIVE,
  publish_status=REVIEW) — was silently overwritten by the task's
  full-object save; the HTMX response rendered as if the click
  succeeded but a hard refresh showed the experiment back in Preview
* All three sections (push to preview, expire from preview, unpublish
  from preview) had the same lost-update shape, differing only in the
  width of the window

This commit

* Replaces experiment.save() with conditional QuerySet.update() in
  all three sections, gating each write on the status the task
  observed (status=PREVIEW for push and expire, status=DRAFT for the
  unpublish field-clear); if the row has moved on, the WHERE clause
  matches zero rows and the task takes no action
* Each section now writes only the columns it owns rather than every
  field on the model
* For push and expire, skips the changelog when the conditional UPDATE
  affects no rows; for unpublish, preserves the existing
  REMOVED_FROM_PREVIEW changelog whether or not the field-clear
  applied (the kinto record was already deleted)
* Adds regression tests covering a concurrent Request Launch during
  the push window and a concurrent transition out of Preview during
  the expiry window

Fixes #15618
Copy link
Copy Markdown
Contributor

@yashikakhurana yashikakhurana left a comment

Choose a reason for hiding this comment

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

oh nice catch, thanks @jaredlockhart

@jaredlockhart jaredlockhart added this pull request to the merge queue May 13, 2026
Merged via the queue into main with commit bac7ef4 May 13, 2026
26 checks passed
@jaredlockhart jaredlockhart deleted the 15618 branch May 13, 2026 19:03
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.

Preview experiment can lose user state transition due to race with kinto preview sync task

2 participants