Skip to content

Fix deadlock in AnimationBackendChoreographer#56305

Open
bartlomiejbloniarz wants to merge 1 commit intomainfrom
export-D99099455
Open

Fix deadlock in AnimationBackendChoreographer#56305
bartlomiejbloniarz wants to merge 1 commit intomainfrom
export-D99099455

Conversation

@bartlomiejbloniarz
Copy link
Copy Markdown
Contributor

Summary:
scheduleCallback() and pause() held synchronized(paused) while calling
ReactChoreographer.postFrameCallback/removeFrameCallback, which acquire
synchronized(callbackQueues). ReactChoreographer dispatches frame
callbacks inside synchronized(callbackQueues), invoking
executeFrameCallback -> scheduleCallback -> synchronized(paused).

This is a lock ordering inversion: background thread acquires paused
then callbackQueues, main thread acquires callbackQueues then paused.

Fix: move postFrameCallback/removeFrameCallback calls outside the
synchronized(paused) block. The atomic state check (paused +
callbackPosted) stays inside the lock, only the ReactChoreographer
interaction moves outside.

Why this is safe:

  • callbackPosted.getAndSet(true) inside the lock guarantees at most one
    thread proceeds to post. A concurrent scheduleCallback will see
    callbackPosted=true and bail out.
  • If pause() runs between the lock release and postFrameCallback:
    pause sets paused=true and callbackPosted=false, then calls
    removeFrameCallback. Two outcomes depending on ordering inside
    callbackQueues (which serializes both calls):
    (a) post runs first, then remove cancels it — clean.
    (b) remove runs first (nothing to remove), then post adds a callback.
    That callback fires once, executeFrameCallback sees paused=true
    via scheduleCallback and does not re-post. Net effect: one extra
    no-op frame, then the loop stops.
  • resume() already operated without synchronized(paused) before this
    change, so no new races are introduced on that path.

Differential Revision: D99099455

Summary:
scheduleCallback() and pause() held synchronized(paused) while calling
ReactChoreographer.postFrameCallback/removeFrameCallback, which acquire
synchronized(callbackQueues). ReactChoreographer dispatches frame
callbacks inside synchronized(callbackQueues), invoking
executeFrameCallback -> scheduleCallback -> synchronized(paused).

This is a lock ordering inversion: background thread acquires paused
then callbackQueues, main thread acquires callbackQueues then paused.

Fix: move postFrameCallback/removeFrameCallback calls outside the
synchronized(paused) block. The atomic state check (paused +
callbackPosted) stays inside the lock, only the ReactChoreographer
interaction moves outside.

Why this is safe:
- callbackPosted.getAndSet(true) inside the lock guarantees at most one
  thread proceeds to post. A concurrent scheduleCallback will see
  callbackPosted=true and bail out.
- If pause() runs between the lock release and postFrameCallback:
  pause sets paused=true and callbackPosted=false, then calls
  removeFrameCallback. Two outcomes depending on ordering inside
  callbackQueues (which serializes both calls):
  (a) post runs first, then remove cancels it — clean.
  (b) remove runs first (nothing to remove), then post adds a callback.
      That callback fires once, executeFrameCallback sees paused=true
      via scheduleCallback and does not re-post. Net effect: one extra
      no-op frame, then the loop stops.
- resume() already operated without synchronized(paused) before this
  change, so no new races are introduced on that path.

Differential Revision: D99099455
@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 1, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync bot commented Apr 1, 2026

@bartlomiejbloniarz has exported this pull request. If you are a Meta employee, you can view the originating Diff in D99099455.

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported meta-exported p: Facebook Partner: Facebook p: Software Mansion Partner: Software Mansion Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant