Fix a crash generating thumbnails for self-hosted videos - #25977
Draft
jkmassel wants to merge 1 commit into
Draft
Conversation
The video-thumbnail async bridge used withUnsafeThrowingContinuation, so a double-resume (AVFoundation firing its image-generation completion handler a second time on teardown) was undefined behavior — an intermittent SIGSEGV — instead of a deterministic trap. It also mutated the Progress object graph after the awaiting task had already resumed and could be racing forward on another thread. Switch the three async bridges (MediaExporter.export(), MediaThumbnailExporter.exportThumbnail(forFileURL:/forVideoURL:)) to withCheckedThrowingContinuation routed through a resume-once guard so a stray second callback degrades to a no-op, and finalize the Progress before the resuming callback fires in MediaVideoExporter.exportPreviewImageForVideo.
Collaborator
Generated by 🚫 Danger |
Contributor
|
| App Name | WordPress | |
| Configuration | Release-Alpha | |
| Build Number | 34194 | |
| Version | PR #25977 | |
| Bundle ID | org.wordpress.alpha | |
| Commit | b4d61a7 | |
| Installation URL | 1opihf56h1aog |
Contributor
|
| App Name | Jetpack | |
| Configuration | Release-Alpha | |
| Build Number | 34194 | |
| Version | PR #25977 | |
| Bundle ID | com.jetpack.alpha | |
| Commit | b4d61a7 | |
| Installation URL | 07kchdd5jo0c8 |
Contributor
🤖 Build Failure AnalysisThis build has failures. Claude has analyzed them - check the build annotations for details. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
SIGSEGVin the video-thumbnailasyncbridge. It surfaces as a hard crash ofMediaImageServiceTests.testSmallThumbnailForRemoteSelfHostedVideo, and this bridge runs in production every time the app generates a thumbnail for a self-hosted video — so it is a shipping bug, not just a test artifact.AVFoundationpath and is the only thing that caught this.Root Cause
Self-hosted videos have no
remoteThumbnailURL, so the app generates the thumbnail from the video itself via liveAVFoundation:MediaImageService.generateThumbnailForVideo→MediaThumbnailExporter.exportThumbnail(forVideoURL:)→MediaVideoExporter.exportPreviewImageForVideo.The callback-to-
asyncbridge was unsafe in two compounding ways:withUnsafeThrowingContinuation.AVAssetImageGenerator.generateCGImagesAsynchronouslycan invoke its completion handler a second time (a.cancelledcallback on teardown, or racing the cancellation handler), resuming the same continuation twice. A double-resume of an unsafe continuation is undefined behavior — a rawSIGSEGV— instead of the deterministic trap a checked continuation gives.Progressmutated after the resume. InexportPreviewImageForVideothe image export is synchronous, soexporter.export(onCompletion:onError:)resumes the continuation inline. The next line —progress.addChild(imageProgress, …)— then mutated theProgressobject graph onAVFoundation's queue after the awaiting task had resumed and could be racing forward on another thread.Fix
1. Make the async bridges resume-once and checked
MediaExporter.export(),MediaThumbnailExporter.exportThumbnail(forFileURL:)andexportThumbnail(forVideoURL:)now usewithCheckedThrowingContinuationrouted through a smallNSLock-backedResumeOnce<T>that nils the continuation on first resume. A stray second callback degrades to a no-op. The checked variant stays permanently — the unsafe one buys nothing here and hides exactly this class of bug.2. Stop mutating
progressafter the resumeexportPreviewImageForVideofinalizesprogress.completedUnitCount = .doneinside the inner export'sonCompletion/onError, before the resuming callback fires. The post-resumeaddChildis removed. Because the image export is synchronous and returns an already-completeProgress, this is equivalent for progress reporting while removing the cross-thread mutation.What We Explored
TSan-detectable data race — soTSanis clean on this path and adds nothing here. Part 1 makes the crash impossible regardless of the exact micro-mechanism.Test Plan
rake lintclean on all changed files.WordPressUnitTestsbuilds;testSmallThumbnailForRemoteSelfHostedVideogreen across 121 executions of the realAVFoundationpath (0 crashes / 0 failures).WordPressUnitTestssuite on CI (running the pipeline repeatedly to confirm the flake is gone).Related
testSmallThumbnailForRemoteVideo~3% flake is a separateMemoryCache.shared/ImageDownloader.sharedtest-isolation issue, out of scope here.