Skip to content

Retain cycles in AnnouncementsDataStoreTests and GutenbergVideoUploadProcessor (flagged by leak audit) #25547

Description

@amanjeetsingh150

I'm doing a weekly audit with OSS and I ran tool I'm working on, XCTestLeaks with wordpress and I found couple of leaks.

Cycle 1 — AnnouncementsDataStoreTests (test code)

Reported root types: CachedAnnouncementsStore, WordPressFlux.Dispatcher<()>.

Cycle shape (from the audit's rawLines):

ROOT CYCLE: <CachedAnnouncementsStore>
   __strong changeDispatcher --> ROOT CYCLE: <WordPressFlux.Dispatcher<()>>
      __strong observers._variant --> <Swift._DictionaryStorage<DispatchToken, (()) -> ()>>

All four tests in the file follow the same pattern:

subscription = store.onChange {
    
    store.announcements   // closure captures local `store` strongly
}

The closure is retained by Dispatcher.observers, which is owned by store.changeDispatcher. Result:

store → changeDispatcher → observers[token] → closure → store

The originating audit attributed this leak to AnnouncementsDataStoreTests.testLocalAnnouncementsRetrieved and (separately) to AppIconListViewModelTests.testAtLeastOneCustomIcon — the second attribution is a side-effect of leaks snapshotting whichever test was running when the cycle persisted in the heap. Bisecting the audit's test order shows the cycle is created in AnnouncementsDataStoreTests itself.

Cycle 2 — GutenbergVideoUploadProcessor (production code)

Reported root types: HTMLProcessor, GutenbergBlockProcessor, GutenbergVideoUploadProcessor.

Cycle shape:

ROOT CYCLE: <HTMLProcessor>
   __strong replacer.context --> ROOT CYCLE: <GutenbergVideoUploadProcessor>
      __strong $__lazy_storage_$_videoHtmlProcessor --> CYCLE BACK TO <HTMLProcessor>
      __strong $__lazy_storage_$_mediaTextVideoBlockProcessor --> ROOT CYCLE: <GutenbergBlockProcessor>
      __strong $__lazy_storage_$_videoBlockProcessor --> ROOT CYCLE: <GutenbergBlockProcessor>

GutenbergVideoUploadProcessor has three lazy var stored properties (videoHtmlProcessor, videoBlockProcessor, mediaTextVideoBlockProcessor) whose replacer: closures reference self.remoteURLString / self.mediaUploadID etc. without a capture list. Each lazy property is owned strongly by self, and the child processor it builds retains a closure that captures self strongly — producing three independent two-node cycles per processor instance.

Reproduces in isolation:

xctestleaks agent run \
  --workspace WordPress.xcworkspace --scheme WordPress \
  --only-testing "WordPressTest/GutenbergVideoUploadProcessorTests/testMediaTextBlockProcessor"

Reproduction commands

Cycle Command
1 --only-testing WordPressTest/AnnouncementsDataStoreTests (full class — single tests don't reproduce)
2 --only-testing WordPressTest/GutenbergVideoUploadProcessorTests/testMediaTextBlockProcessor

Both cycles are addressed in #25548.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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