Skip to content

Asset bundle commit is a non-atomic remove-then-copy #665

Description

@jkmassel

EditorAssetBundle.copy(to:) commits a downloaded asset bundle by deleting the destination and copying into it. Two editors on the same site can run that concurrently, so one can delete the directory the other is currently serving assets from.

Detail

ios/Sources/GutenbergKit/Sources/Model/EditorAssetBundle.swift:217-223 is directoryExistsremoveItemcreateDirectorycopyItem, against storageRoot/<manifest-checksum> (EditorAssetLibrary.swift:245).

Nothing serializes two writers. Each EditorViewController builds its own EditorService (EditorViewController.swift:223), which builds its own EditorAssetLibrary — actor isolation is per-instance, so two editors for the same site are two independent actors writing the same path.

How it bites

Cold asset cache (first open of a site, or after deleteAllData), slow connection. The user opens the editor, backs out, and immediately reopens.

Editor B downloads and commits, then starts serving assets from storageRoot/<checksum>. Editor A — still finishing its own download — reaches copy(to:), sees the directory exists, removes it, and re-copies. For the duration of that window every plugin/theme asset request misses the bundle.

The blast radius is smaller than it first looks, and worth stating so nobody over-fixes it:

  • The path is checksum-keyed, so identical checksum means identical content. This is a window of absence, not corruption.
  • EditorAssetBundleProvider.swift:78-85 already falls back to fetchFromRemote for any asset missing from the bundle, so the visible editor degrades to network refetches rather than erroring. Offline, those assets just don't load.
  • prepareAssetBundle() short-circuits on readAssetBundles().first (EditorService.swift:234-243), so a warm cache never reaches copy(to:) at all.

Suggested fix

Commit via an atomic rename — write to a sibling temp directory and replaceItemAt — or skip the copy entirely when hasBundle(forManifestChecksum:) is already true.

Found while reviewing #651. Pre-existing; the removal of the mid-load cancellation widens the window slightly, but the old cancellation was worse here (see #667).

Activity

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

    [Type] BugAn existing feature does not function as intendediOS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions