Skip to content

Bug: [Memory leak] retain cycle in GutenbergCoverUploadProcessor #25441

@amanjeetsingh150

Description

@amanjeetsingh150

Summary

GutenbergCoverUploadProcessor has a retain cycle that causes it to never be deallocated after a cover block upload.

Root Cause

The lazy var coverBlockProcessor creates a GutenbergBlockProcessor with a replacer closure that strongly captures self:

// GutenbergCoverUploadProcessor.swift
lazy var coverBlockProcessor = GutenbergBlockProcessor(for: CoverBlockKeys.name, replacer: { coverBlock in
    guard let mediaID = coverBlock.attributes[CoverBlockKeys.id] as? Int,
        mediaID == self.mediaUploadID else {  // strong capture of self
            return nil
    }
    ...
    let innerProcessor = self.isVideo(attributes) ? self.videoUploadProcessor() : self.imgUploadProcessor()
    ...
})

This creates a cycle:

GutenbergCoverUploadProcessor
  → lazy var coverBlockProcessor (strong)
    → GutenbergBlockProcessor.replacer closure (strong)
      → self (GutenbergCoverUploadProcessor) ← cycle

Detection

Detected using XCTestLeaks — a tool that runs leaks(1) after each XCTest case via a dylib shim.

Results from GutenbergCoverUploadProcessorTests before fix:

leaks=1  testCoverBlockProcessor
leaks=2  testCoverBlockProcessorWithOtherAttributes
leaks=3  testDeepNestedCoverBlockProcessor
leaks=4  testImageCoverInVideoCoverBlockProcessor
leaks=5  testMultipleCoverBlocksProcessor
leaks=5  testNestedCoverBlockProcessor
leaks=7  testUpdateOuterCoverBlockProcessor
leaks=7  testVideoCoverBlockProcessor
leaks=7  testVideoCoverInImageCoverBlockProcessor

After fix: all tests show leaks=0.

Fix

Use [weak self] in the replacer closure:

lazy var coverBlockProcessor = GutenbergBlockProcessor(for: CoverBlockKeys.name, replacer: { [weak self] coverBlock in
    guard let self,
          let mediaID = coverBlock.attributes[CoverBlockKeys.id] as? Int,
          mediaID == self.mediaUploadID else {
            return nil
    }
    ...
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions