Fix crashes when recording on an external display (e.g. 5K Studio Display)#2
Open
junyetong wants to merge 4 commits into
Open
Fix crashes when recording on an external display (e.g. 5K Studio Display)#2junyetong wants to merge 4 commits into
junyetong wants to merge 4 commits into
Conversation
Connecting a Studio Display (or other display/audio route change) could
crash the app with EXC_BAD_ACCESS while SwiftUI dispatches a Button
gesture:
libobjc lookUpImpOrForward
libswiftCore swift_getObjectType
Concurrency swift_task_isCurrentExecutorWithFlagsImpl
SwiftUI MainActor.assumeIsolated
SwiftUI _ButtonGesture.internalBody
This is the known Swift 6 / macOS 26 concurrency runtime issue where the
"is current executor" check (enabled for apps built against the Fall 2024+
SDK) is handed a corrupted SerialExecutorRef and crashes instead of
returning, rather than an app logic bug. A display/audio route change
(such as plugging in a Studio Display) destabilizes the executor state and
makes the next button tap fault.
Restore the pre-Swift-6, non-crashing executor check via LSEnvironment so
the runtime no longer aborts on this path.
See: swiftlang/swift runtime flag
SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE (Bincompat.cpp).
Add a setenv in CueRecordApp.init() (overwrite=0) so the SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE flag is also applied for launch paths that bypass LaunchServices and would not pick up the Info.plist LSEnvironment value. Belt-and-suspenders for the same crash.
Connecting a Studio Display (or any display/audio route change) could leave the Swift 6 runtime with a corrupted MainActor SerialExecutorRef. The next SwiftUI button dispatch then hit swift_task_isCurrentExecutorImpl / MainActor.assumeIsolated and crashed (EXC_BAD_ACCESS, later EXC_BREAKPOINT) because start/stop recording suspended the main actor across awaits (SCShareableContent enumeration, stream.startCapture, camera startup, etc). Move the whole start/stop/preview orchestration into nonisolated contexts (Task.detached) and only touch MainActor state through synchronous MainActor.run blocks, so the main actor never suspends across those awaits. CameraManager, AVAudioEngineRecorder, RecordingMetricsRecorder and the camera frame helpers are made nonisolated / Sendable accordingly, with an NSLock guarding capture-session state. Also pick HEVC instead of H.264 when either dimension exceeds 4096px: a 5K Studio Display full-screen capture (5120x2880) is above the H.264 hardware encoder limit and aborted AVAssetWriterInput initialization. Co-authored-by: Cursor <cursoragent@cursor.com>
The Paraformer .onnx weights are stored via Git LFS. On a checkout without `git lfs pull` they exist as ~130-byte pointer files, so hasRequiredFiles() passed and ONNX Runtime then threw an uncaught C++ exception while building the session (Ort::Session::Session -> std::terminate -> abort) the moment a recorded teleprompter session started. Require a realistic minimum size for tokens.txt and the encoder/decoder so a missing or pointer-only model surfaces as a recoverable ASRError (handled by start()) with the existing "run scripts/setup_sherpa_onnx.sh" guidance, instead of crashing the whole app. Co-authored-by: Cursor <cursoragent@cursor.com>
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
On macOS 26 with Apple Silicon, recording would crash in several ways once a Studio Display was attached. This PR makes full-screen recording on a 5K Studio Display work end-to-end. It addresses three distinct crashes found while chasing the original report.
1. MainActor executor corruption (the original crash)
Connecting a Studio Display (or any display/audio route change) could leave the Swift 6 runtime with a corrupted
MainActorSerialExecutorRef. The next SwiftUI button dispatch then hitswift_task_isCurrentExecutorImpl/MainActor.assumeIsolatedand crashed — first asEXC_BAD_ACCESS, and asEXC_BREAKPOINT (SIGTRAP)once the legacy executor flag was set.Root cause:
startRecording/stopRecording/ preview setup suspended the main actor across awaits (SCShareableContent enumeration,stream.startCapture, camera startup, etc.).Fix: move the whole start/stop/preview orchestration into nonisolated contexts (
Task.detached) and only touch MainActor state through synchronousMainActor.runblocks, so the main actor never suspends across those awaits.CameraManager,AVAudioEngineRecorder,RecordingMetricsRecorderand the camera frame helpers are madenonisolated/Sendableaccordingly, with anNSLockguarding capture-session state. The existingLSEnvironment/setenvlegacy-executor flag is kept as belt-and-suspenders.2. H.264 limit at 5K
A 5K Studio Display full-screen capture is 5120x2880, which is above the H.264 hardware encoder limit and aborted
AVAssetWriterInputinitialization. Now HEVC is selected automatically when either dimension exceeds 4096px; H.264 is still used otherwise.3. ASR model load aborts the app when Git LFS isn't pulled
The Paraformer
.onnxweights are stored via Git LFS. On a checkout withoutgit lfs pullthey are ~130-byte pointer files, sohasRequiredFiles()passed and ONNX Runtime threw an uncaught C++ exception while building the session (Ort::Session::Session->std::terminate->abort) the moment a recorded teleprompter session started.hasRequiredFiles()now also checks a realistic minimum file size, so a missing/pointer-only model surfaces as a recoverableASRError(with the existing setup-script guidance) instead of crashing.Environment
Test plan
xcodebuild -scheme CueRecord -configuration Release).