What's happening?
App crashes with EXC_CRASH (SIGABRT) on iPhone 11 / iOS 18.7.7 during normal camera usage. The crashed thread is always a CoreMedia FigCaptureSessionNotificationQueue worker processing _handleConfigurationCommittedNotificationWithPayload and asserting in [AVCaptureOutput attachToFigCaptureSession:]_block_invoke.cold.1 at AVCaptureOutput.m:364. A separate thread is in HybridCameraSession.start() → AVCaptureSession.startRunning() → _buildAndRunGraph at the moment of the assert.
This is the attach-side sibling of #3730. #3730 (closed as fixed in v5.0.0) was the detach-side of the same race; the attach side appears not to have been addressed and is reproducible in v5.0.4.
We have 6 byte-for-byte identical crash reports from a single user from real TestFlight usage. All triggered by ordinary actions: photo retake, zoom switching, video record start, and general camera usage with no specific repro action reported. Each triggered through a different user-visible action but produced the same stack frames in the crashed thread and the same parallel HybridCameraSession.start() thread.
Hypothesis
HybridCameraSession.start() calls session.startRunning() immediately after a previous configure()'s commitConfiguration() returned, with no barrier waiting for CoreMedia to finish propagating the asynchronous "configuration committed" notification on FigCaptureSessionNotificationQueue. On multi-physical-cam devices (iPhone 11 has wide + ultra-wide), the CoreMedia XPC commit takes longer to propagate than on single-cam devices, widening the race window. When the notification handler runs _makeConfigurationLive: and iterates outputs to call attachToFigCaptureSession:, an output's _outputInternal->figCaptureSession is non-NULL (already attached by the racing startRunning), tripping the internal assert.
The serial Self.queue in HybridCameraSession correctly serializes JS-driven calls (configure, start, stop), but does not synchronize with CoreMedia's async commit notification.
Reproduceable Code
Standard usage. The <Camera> is mounted with stable photo/video outputs; isActive is derived from focus + foreground + a pendingMedia flag (off during a per-shot review screen).
const photoOutput = usePhotoOutput(PHOTO_OUTPUT_OPTIONS); // stable options object
const videoOutput = useVideoOutput({
targetResolution: VIDEO_RESOLUTION,
targetBitRate: VIDEO_BIT_RATE,
enableAudio: !isMuted, // mute toggle recreates the output instance
});
const outputs = useMemo(() => [photoOutput, videoOutput], [photoOutput, videoOutput]);
const constraints = useMemo<Constraint[]>(() => {
const out: Constraint[] = [
{ fps: 60 },
{ videoStabilizationMode: 'cinematic-extended' },
];
if (enablePhotoHdr) {
out.unshift({ photoHDR: true });
if (deviceSupportsHdrVideo) {
out.push({ videoDynamicRange: { bitDepth: 'hdr-10-bit', colorSpace: 'hlg-bt2020', colorRange: 'full' } });
}
}
return out;
}, [enablePhotoHdr, device]);
// pendingMedia toggles isActive during photo/video review (free sensor for review, restart on retake)
const isActive = visible && isFocused && isForeground && hasPermission && pendingMedia == null;
<Camera
ref={cameraRef}
style={StyleSheet.absoluteFill}
isActive={isActive}
device={device}
outputs={outputs}
constraints={constraints}
mirrorMode={cameraPosition === 'front' ? 'on' : 'off'}
orientationSource="device"
enableLowLightBoost={enableLowLightBoost}
enableSmoothAutoFocus={enableSmoothAutoFocus}
enableDistortionCorrection={enableDistortionCorrection}
onStarted={onCameraStarted}
onError={onError}
/>
User-visible actions that trigger reconfigure → start():
- Photo capture → review → tap "Retake" (
isActive flips off → on).
- Toggle mute (
isMuted) — useVideoOutput's memo invalidates on enableAudio change, recreating the AVCaptureMovieFileOutput instance, which forces removeOutput/addOutputWithNoConnections in updateOutputs.
- Toggle HDR (
enablePhotoHdr) — new constraints reference triggers configure().
- Flip front/back — new
device.
- Zoom switching past the wide↔ultra-wide threshold (zoom itself is imperative via
controller.setZoom, but iOS internally posts a configuration notification when the active sub-device changes on a multi-physical-cam logical device).
Relevant log output
Exception Type: EXC_CRASH (SIGABRT)
Termination Reason: SIGNAL 6 Abort trap: 6
Crashed thread (CoreMedia FigCaptureSessionNotificationQueue):
0 libsystem_kernel.dylib __pthread_kill + 8
1 libsystem_pthread.dylib pthread_kill + 268
2 libsystem_c.dylib __abort + 132
3 libsystem_c.dylib abort + 136
4 libsystem_c.dylib __assert_rtn + 284
5 AVFCapture __45-[AVCaptureOutput attachToFigCaptureSession:]_block_invoke.cold.1 + 44 (AVCaptureOutput.m:364)
6 AVFCapture __45-[AVCaptureOutput attachToFigCaptureSession:]_block_invoke + 100 (AVCaptureOutput.m:364)
7 libdispatch.dylib _dispatch_client_callout + 16
8 libdispatch.dylib _dispatch_lane_barrier_sync_invoke_and_complete + 56
9 AVFCapture -[AVCaptureOutput attachToFigCaptureSession:] + 108 (AVCaptureOutput.m:363)
10 CoreFoundation -[NSSet makeObjectsPerformSelector:withObject:] + 188
11 AVFCapture -[AVCaptureSession _makeConfigurationLive:] + 344 (AVCaptureSession.m:4199)
12 AVFCapture -[AVCaptureSession _handleConfigurationCommittedNotificationWithPayload:] + 600 (AVCaptureSession.m:5445)
13 AVFCapture avcaptureFigCaptureSessionNotification + 176 (AVCaptureSession.m:6678)
14 CoreFoundation __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 128
15 CoreFoundation ___CFXRegistrationPost_block_invoke + 92
16 CoreFoundation _CFXRegistrationPost + 436
17 CoreFoundation _CFXNotificationPost + 736
18 CoreFoundation CFNotificationCenterPostNotificationWithOptions + 140
19 CoreMedia CMNotificationCenterPostNotification + 96
20 CoreMedia __figXPCConnection_HandleNotificationMessage_block_invoke + 132
Parallel vision-camera thread (com.margelo.camera.session):
8 AVFCapture -[AVCaptureSession _buildAndRunGraph:] + 1372 (AVCaptureSession.m:4709)
9 AVFCapture -[AVCaptureSession _setRunning:] + 224 (AVCaptureSession.m:2674)
10 AVFCapture -[AVCaptureSession startRunning] + 452 (AVCaptureSession.m:2536)
11 QutrekDev partial apply for closure #1 in HybridCameraSession.start() + 56
12 QutrekDev closure #1 in static Promise.parallel(_:_:) + 96 (Promise.swift:117)
Camera Device
{
"id": "com.apple.avfoundation.avcapturedevice.built-in_video:0",
"position": "back",
"physicalDevices": ["wide-angle-camera", "ultra-wide-angle-camera"]
}
(iPhone 11 dual back camera; both back-facing sub-devices in a single logical multi-physical-cam device.)
Device
iPhone 11 (iPhone12,1), iOS 18.7.7
VisionCamera Version
5.0.4
Can you reproduce this issue in the VisionCamera Example app?
Have not tested in the example app yet. Reporting because of (a) volume of identical real-world crashes, (b) the obvious structural similarity to #3730 which suggests a known race-condition class.
Additional information
What's happening?
App crashes with
EXC_CRASH (SIGABRT)on iPhone 11 / iOS 18.7.7 during normal camera usage. The crashed thread is always a CoreMediaFigCaptureSessionNotificationQueueworker processing_handleConfigurationCommittedNotificationWithPayloadand asserting in[AVCaptureOutput attachToFigCaptureSession:]_block_invoke.cold.1atAVCaptureOutput.m:364. A separate thread is inHybridCameraSession.start()→AVCaptureSession.startRunning()→_buildAndRunGraphat the moment of the assert.This is the attach-side sibling of #3730. #3730 (closed as fixed in v5.0.0) was the detach-side of the same race; the attach side appears not to have been addressed and is reproducible in v5.0.4.
We have 6 byte-for-byte identical crash reports from a single user from real TestFlight usage. All triggered by ordinary actions: photo retake, zoom switching, video record start, and general camera usage with no specific repro action reported. Each triggered through a different user-visible action but produced the same stack frames in the crashed thread and the same parallel
HybridCameraSession.start()thread.Hypothesis
HybridCameraSession.start()callssession.startRunning()immediately after a previousconfigure()'scommitConfiguration()returned, with no barrier waiting for CoreMedia to finish propagating the asynchronous "configuration committed" notification onFigCaptureSessionNotificationQueue. On multi-physical-cam devices (iPhone 11 has wide + ultra-wide), the CoreMedia XPC commit takes longer to propagate than on single-cam devices, widening the race window. When the notification handler runs_makeConfigurationLive:and iterates outputs to callattachToFigCaptureSession:, an output's_outputInternal->figCaptureSessionis non-NULL (already attached by the racingstartRunning), tripping the internal assert.The serial
Self.queueinHybridCameraSessioncorrectly serializes JS-driven calls (configure,start,stop), but does not synchronize with CoreMedia's async commit notification.Reproduceable Code
Standard usage. The
<Camera>is mounted with stable photo/video outputs;isActiveis derived from focus + foreground + apendingMediaflag (off during a per-shot review screen).User-visible actions that trigger reconfigure →
start():isActiveflips off → on).isMuted) —useVideoOutput's memo invalidates onenableAudiochange, recreating the AVCaptureMovieFileOutput instance, which forcesremoveOutput/addOutputWithNoConnectionsinupdateOutputs.enablePhotoHdr) — newconstraintsreference triggersconfigure().device.controller.setZoom, but iOS internally posts a configuration notification when the active sub-device changes on a multi-physical-cam logical device).Relevant log output
Camera Device
{ "id": "com.apple.avfoundation.avcapturedevice.built-in_video:0", "position": "back", "physicalDevices": ["wide-angle-camera", "ultra-wide-angle-camera"] }(iPhone 11 dual back camera; both back-facing sub-devices in a single logical multi-physical-cam device.)
Device
iPhone 11 (
iPhone12,1), iOS 18.7.7VisionCamera Version
5.0.4
Can you reproduce this issue in the VisionCamera Example app?
Have not tested in the example app yet. Reporting because of (a) volume of identical real-world crashes, (b) the obvious structural similarity to #3730 which suggests a known race-condition class.
Additional information