Skip to content

perf(mobile): canvas mount causes severe main-thread hang when opening a .canvas file #55

@LeslieOA

Description

@LeslieOA

Context

Profiled on iPhone 16 Pro (A18 Pro), iOS 26.4.1. Release build, Xcode Instruments "Animation Hitches" template in Deferred mode. Trace covers cold launch → file picker → canvas open → pinch gestures (~35s total).

Profiling caveat: Deferred-mode Instruments adds non-trivial overhead. The hang duration reported below is an upper bound. Without Instruments attached, the canvas "feels a little jumpy on first appear" — there's a perceptible hitch but it's less dramatic than the trace suggests. The trace's value is in identifying the blocking work, not the exact duration.

Observation

Instruments flagged a Severe Hang (>500ms main-thread block) when a .canvas file is selected from the picker and the canvas view mounts. The hang manifests as both a Hang event and multiple single-frame hitches in the trace. This corresponds to the user-perceptible "jumpy" feeling when the canvas first appears — a burst of dropped frames followed by smooth rendering once the canvas settles.

Hypotheses

These are starting points for investigation, not conclusions:

  1. Synchronous image decoding via useImage. If image nodes call useImage eagerly on mount, every image node — including those outside the viewport — may trigger synchronous decode before viewport culling has had a chance to exclude them.

  2. JSON Canvas parsing on the JS thread. File read + JSON parse + node tree construction may all be happening synchronously within a single JS frame, producing a long task that blocks the main thread bridge.

  3. Full layout pass before culling. If all 20–50 nodes go through an initial layout pass before the viewport culling logic runs, that's a lot of unnecessary measurement work on the first frame.

  4. Reanimated shared value initialisation. Camera transform setup (pan offset, zoom scale, decay animations) initialises multiple shared values; if these trigger synchronous UI-thread work during mount, they contribute to the hang.

Suggested investigation

  • Add os_signpost markers around: file read, JSON parse, node tree construction, and first render pass to identify which phase dominates.
  • Check whether useImage calls block the JS thread or fire asynchronously — if they're awaited or synchronous, they're part of the hang.
  • Verify that viewport culling runs before the first render, not as a post-render effect that prunes on the second frame.
  • Consider deferring non-visible node mounting behind InteractionManager.runAfterInteractions or a requestAnimationFrame boundary so the canvas shell appears immediately and nodes populate progressively.

Acceptance criteria

Opening a canvas with 50 nodes produces no Hang event (>100ms main-thread block) in a clean Instruments trace, and feels visually smooth (no jumpy first frames) in normal use on iPhone 16 Pro.

Related

  • workspace-sh/workspace#76 — Instruments profiling for canvas rendering during gestures
  • workspace-sh/workspace#75 — rasterise-during-gesture optimisation
  • workspace-sh/workspace#79 — picker presentation hitch (filed alongside this issue)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcanvasJSON Canvas

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions