You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: START_CRASH_FINDINGS.md
+19-14Lines changed: 19 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,9 +6,9 @@ Branch: `codex/start-crash-3773`
6
6
7
7
## Current status
8
8
9
-
This branch contains an event-driven reproducer in the simple-camera app and an iOS Harness stress test. I have not produced the exact AVFoundation assertion locally or in CI yet. The local multi-physical-camera iPhone 15 Pro became locked and SpringBoard denied further launches, while the available iPhone SE 3rd gen ran the repro loop without crashing. The first CI iOS Harness run also ran the new 120-cycle repro on a multi-camera iOS Device Farm device without crashing.
9
+
This branch contains an event-driven reproducer in the simple-camera app and an iOS Harness stress test. I have not produced the exact AVFoundation assertion locally or in CI yet. The local multi-physical-camera iPhone 15 Pro became locked and SpringBoard denied further launches, while the available iPhone SE 3rd gen ran the earlier repro loop without crashing. The first CI iOS Harness run also ran the earlier 120-cycle repro on a multi-camera iOS Device Farm device without crashing.
10
10
11
-
The strongest confirmed root-cause evidence is therefore the original issue stack plus the VisionCamera call ordering below. The stack proves `AVCaptureSession.startRunning()` overlapped with AVFoundation's private configuration-commit notification path. The exact assertion condition inside `-[AVCaptureOutput attachToFigCaptureSession:]`is private Apple code, so the remaining internal invariant is inferred from the stack, not decompiled source.
11
+
The strongest confirmed root-cause evidence is therefore the original issue stack plus the VisionCamera call ordering below. The original stack proves `AVCaptureSession.startRunning()` overlapped with AVFoundation's private configuration-commit notification path. A separate client report mentions the sibling private assertion `AVCaptureOutput detachFromFigCaptureSession` during rapid `photo -> video -> photo` changes, especially with a filter-preview path involved. That client report is not a stack trace, but it is directionally important because it points at the same output lifetime boundary from the detach side rather than the attach side. The exact assertion conditions inside `-[AVCaptureOutput attachToFigCaptureSession:]`and `detachFromFigCaptureSession` are private Apple code, so the remaining internal invariant is inferred from stacks/logs, not decompiled source.
12
12
13
13
## What the crash stack proves
14
14
@@ -29,6 +29,8 @@ Because VisionCamera serializes `configure()` and `start()` on `HybridCameraSess
29
29
6. AVFoundation is still attaching outputs from the committed configuration on `FigCaptureSessionNotificationQueue`.
30
30
7. AVFoundation hits its private assertion in `attachToFigCaptureSession`.
31
31
32
+
The newer client report describes `detachFromFigCaptureSession`, which is the same class of invariant violation during output teardown instead of output attachment. Taken together, the evidence points at AVFoundation output attachment/detachment lifetime crossing a session start or a subsequent output topology change. It does not point at JavaScript timing alone, and it does not prove that a single specific output type is always responsible.
33
+
32
34
## VisionCamera code path responsible
33
35
34
36
Native serialization is in `packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraSession.swift`:
@@ -61,30 +63,31 @@ That is a valid VisionCamera queue order, but it is the exact dangerous order if
61
63
The example app starts the camera automatically, then loops from native callbacks:
62
64
63
65
1.`onStarted` sets `isActive=false`.
64
-
2.`onStopped`swaps video output settings and optional HDR constraints.
66
+
2.`onStopped`alternates the active output topology between photo-only and video-only.
65
67
3.`onStopped` immediately sets `isActive=true`.
66
68
4. The next React commit reconfigures outputs/constraints and restarts without sleeps.
67
69
68
-
The loop attaches both photo and video outputs, alternates `HD_16_9`/`FHD_16_9` video output settings, requests 60 FPS when supported, requests cinematic-extended stabilization when supported, and toggles `photoHDR` when supported.
70
+
The current loop uses `<SkiaCamera>` so the preview path is a frame output rendered through Skia rather than a native preview output. It alternates `photo -> video -> photo`, alternates `HD_16_9`/`FHD_16_9` video output settings, requests 60 FPS when supported, requests cinematic-extended stabilization during video cycles when supported, and toggles `photoHDR` during photo cycles when supported.
69
71
70
72
This is intentionally event-driven. It does not use artificial sleeps or timeouts to create the race window.
71
73
72
74
## Harness reproducer added
73
75
74
-
`apps/simple-camera/__tests__/visioncamera.camera-view.harness.tsx` now contains:
76
+
`apps/simple-camera/__tests__/visioncamera.start-crash.harness.tsx` now contains two iOS-only stress tests:
75
77
76
78
```text
77
-
reconfigures outputs and immediately restarts from Camera lifecycle callbacks
79
+
cycles photo -> video -> photo outputs through native preview start/stop
80
+
cycles photo -> video -> photo outputs with Skia frame-preview attached
78
81
```
79
82
80
-
It renders a high-level `<Camera>`and runs 120 cycles of:
83
+
They render high-level camera components and run 60 cycles of:
81
84
82
85
```text
83
86
onStarted -> inactive
84
-
onStopped -> replace video output + toggle constraints + active
This is the closest Harness shape to the reported crash because it exercises the React hook lifecycle that can enqueue `configure()` followed by `start()`in the same commit. A crash on CI should appear as an iOS Harness process failure rather than a normal assertion failure.
90
+
The first stress test uses the normal `<Camera>` native preview path. The second uses `<SkiaCamera>`, which always adds a frame output for preview rendering and therefore exercises the filter-preview shape mentioned in the newer client report. A crash on CI should appear as an iOS Harness process failure rather than a normal assertion failure.
88
91
89
92
## Device observations
90
93
@@ -100,16 +103,18 @@ Observed locally:
100
103
- Subsequent iPhone 15 Pro launches were denied because the device was locked: `Unable to launch com.margelo.nitro.camera.example.simple because the device was not, or could not be, unlocked`.
101
104
- The attached Teams video shows an app crash alert after an active video recording session, but it does not expose a native stack. It supports the general "active camera plus output/session state changes" trigger shape, not the internal AVFoundation diagnosis by itself.
102
105
103
-
CI observations from PR #4001 / Harness AWS Device run `27018903509`:
106
+
CI observations from PR #4001 / Harness AWS Device run `27018903509` for the earlier, now-replaced Harness repro:
104
107
105
108
- Build iOS passed.
106
109
- Test iOS executed on a multi-camera iOS device exposing wide, ultra-wide, telephoto, dual, dual-wide, triple, lidar-depth, and true-depth devices.
107
110
- The new Harness test completed 120 cycles on `Back Camera` in 25.766s: `reconfigures outputs and immediately restarts from Camera lifecycle callbacks`.
108
111
- iOS Harness summary was green: 12 test suites passed, 125 tests passed, 15 skipped, 0 failed.
109
112
- The GitHub `Test iOS` job still reported failure because the Device Farm step itself returned failed, but the Harness output did not contain an AVFoundation crash, app crash, failed test, or assertion.
110
-
- Android also ran the new test and completed 120 cycles in 27.414s. Android failed in unrelated existing suites (`photo`, `coordinates`, `controller`, `video`), not in the new Camera-view repro.
113
+
- Android also ran the earlier test and completed 120 cycles in 27.414s. Android failed in unrelated existing suites (`photo`, `coordinates`, `controller`, `video`), not in the Camera-view repro.
114
+
115
+
Why CI passed: that earlier Harness test was not equivalent to either report. It kept photo and video outputs attached together, then mostly recreated the video output and restarted. It did not remove the photo output before adding video, did not remove video before re-adding photo, did not use the Skia/filter-preview path, did not record video, and did not run on the original iPhone 11 / iOS 18.7.7 matrix. Its green result only proves that "replace video while photo remains attached, then restart" did not hit the assertion on that AWS device in 120 cycles.
111
116
112
-
Negative evidence: the iPhone SE result and the first iOS Device Farm result suggest the current loop is not sufficient to deterministically reproduce the crash. Hardware/OS version, camera topology, recording state, recorder preparation, lens switching, mount/unmount timing, or a narrower output transition likely matters.
117
+
Negative evidence: the iPhone SE result and the first iOS Device Farm result suggest the earlier loop was not sufficient to deterministically reproduce the crash. Hardware/OS version, camera topology, recording state, recorder preparation, lens switching, mount/unmount timing, or the narrower photo-only/video-only output transition likely matters.
`bunx biome check apps/simple-camera/__tests__/visioncamera.camera-view.harness.tsx apps/simple-camera/src/screens/CameraScreen.tsx` reported only pre-existing unused-variable warnings in `CameraScreen.tsx`. The new Harness file had no Biome diagnostics.
130
+
`bunx biome check apps/simple-camera/__tests__/visioncamera.start-crash.harness.tsx apps/simple-camera/__tests__/visioncamera.camera-view.harness.tsx apps/simple-camera/src/screens/CameraScreen.tsx START_CRASH_FINDINGS.md apps/simple-camera/__tests__/README.md` reported only pre-existing unused-variable warnings in `CameraScreen.tsx`. The new stress Harness file had no Biome diagnostics.
126
131
127
132
Local `react-native-harness --harnessRunner ios` tried to boot an iOS simulator, so I stopped it. The simulator is not useful for this camera pipeline race; the PR CI iOS Harness run on real hardware is the relevant signal.
128
133
129
134
## Current root-cause statement
130
135
131
136
The best current issue description is:
132
137
133
-
> VisionCamera can enqueue `AVCaptureSession.startRunning()` immediately after a configuration commit that replaces outputs or changes output-affecting constraints. VisionCamera's own serial queue ensures `startRunning()` happens after `commitConfiguration()` returns, but AVFoundation/CoreMedia may still be processing the committed configuration asynchronously on `FigCaptureSessionNotificationQueue`. On affected hardware/OS combinations, `startRunning()` can overlap that private output-attachment work and trip AVFoundation's internal assertion in `-[AVCaptureOutput attachToFigCaptureSession:]`.
138
+
> VisionCamera can enqueue `AVCaptureSession.startRunning()` immediately after a configuration commit that replaces outputs or changes output-affecting constraints. VisionCamera's own serial queue ensures `startRunning()` happens after `commitConfiguration()` returns, but AVFoundation/CoreMedia may still be processing the committed configuration asynchronously on `FigCaptureSessionNotificationQueue`. On affected hardware/OS combinations, `startRunning()`or a subsequent topology change can overlap that private outputattachment/detachment work and trip AVFoundation's internal assertions in `-[AVCaptureOutput attachToFigCaptureSession:]` or `detachFromFigCaptureSession`.
134
139
135
140
This is proven at the call-order/concurrency level by the crash stack and VisionCamera source. It is not yet proven at the "Apple internal field X had value Y" level because AVFoundation source is private and the exact assertion condition is not visible.
|[visioncamera.start-crash.harness.tsx](visioncamera.start-crash.harness.tsx)| iOS AVFoundation output attach/detach stress repro for issue #3773, including photo -> video -> photo output topology and the Skia frame-preview path |
33
34
34
35
Pick the file that best matches what you're testing. If you're reproducing a bug that spans multiple outputs, put it in the file most central to the failure. If nothing fits, open a new `visioncamera.<domain>.harness.ts` — Jest picks up anything matching `__tests__/**/*.harness.{ts,tsx}`.
0 commit comments