Summary
On iOS, the app crashes with SIGTRAP / _os_unfair_lock_corruption_abort inside Apple's Vision framework, called from BarcodeAnalyzer.DidOutputSampleBuffer. The root cause is that the AVCaptureVideoDataOutput sample-buffer delegate is set on a concurrent dispatch queue, while BarcodeAnalyzer uses a single shared, non-thread-safe VNSequenceRequestHandler.
Environment
- BarcodeScanning.Native.Maui 3.0.3 (also present on
master)
- .NET MAUI on net10.0-ios
- Device: iPhone 15 (iPhone15,4), iOS 17.6.1 — reproducible on fast devices that deliver overlapping frames
Symbolicated crash (crashing thread)
0 libsystem_platform _os_unfair_lock_corruption_abort
1 libsystem_platform _os_unfair_lock_lock_slow
2 Vision -[VNRequestPerformer _performOrderedRequests:inContext:error:]
3 Vision -[VNRequestPerformer _performRequests:onBehalfOfRequest:inContext:error:]
4 Vision -[VNSequenceRequestHandler _performRequests:onImageBuffer:gatheredForensics:error:]
5 Vision -[VNSequenceRequestHandler performRequests:onCMSampleBuffer:orientation:...]
6 (managed -> native) VNSequenceRequestHandler.Perform
...
12 App -[BarcodeScanning_BarcodeAnalyzer captureOutput:didOutputSampleBuffer:fromConnection:]
13 AVFCapture -[AVCaptureVideoDataOutput _processSampleBuffer:]
A second thread was simultaneously blocked in CMCapture on _pthread_mutex_firstfit_lock_slow — i.e. a second frame was being delivered at the same instant the crashing frame was running Vision.
Root cause
In Platform/MaciOS/CameraManager.cs (Start()), the delegate is attached to the concurrent global queue:
_videoDataOutput?.SetSampleBufferDelegate(_barcodeAnalyzer, DispatchQueue.DefaultGlobalQueue);
Apple's contract for setSampleBufferDelegate:queue: requires a serial queue:
The queue you specify must be a serial dispatch queue to ensure that frames are delivered in order.
Because the queue is concurrent, DidOutputSampleBuffer can run on multiple threads simultaneously. BarcodeAnalyzer holds one shared VNSequenceRequestHandler:
private readonly VNSequenceRequestHandler _sequenceRequestHandler;
...
_sequenceRequestHandler.Perform([_detectBarcodesRequest], sampleBuffer, out _);
VNSequenceRequestHandler is not thread-safe, so concurrent Perform calls corrupt Vision's internal os_unfair_lock, aborting the process. AlwaysDiscardsLateVideoFrames = true does not prevent this, since the "drop frame while busy" behavior depends on the delegate queue being serial.
Note: a serial queue (com.barcodescanning.maui.sessionQueue) is already created in the class but is only used for session configuration, not for the delegate.
Proposed fix
Attach the sample-buffer delegate to a dedicated serial queue instead of DispatchQueue.DefaultGlobalQueue, e.g.:
// field
private readonly DispatchQueue? _cameraQueue = new("com.barcodescanning.maui.cameraQueue");
// in Start()
_videoDataOutput?.SetSampleBufferDelegate(null, null);
_videoDataOutput?.SetSampleBufferDelegate(_barcodeAnalyzer, _cameraQueue);
(Don't reuse _dispatchQueue for the delegate, since it's used with DispatchBarrierAsync for start/stop and would let frame processing block session reconfiguration.)
Summary
On iOS, the app crashes with
SIGTRAP/_os_unfair_lock_corruption_abortinside Apple's Vision framework, called fromBarcodeAnalyzer.DidOutputSampleBuffer. The root cause is that theAVCaptureVideoDataOutputsample-buffer delegate is set on a concurrent dispatch queue, whileBarcodeAnalyzeruses a single shared, non-thread-safeVNSequenceRequestHandler.Environment
master)Symbolicated crash (crashing thread)
A second thread was simultaneously blocked in
CMCaptureon_pthread_mutex_firstfit_lock_slow— i.e. a second frame was being delivered at the same instant the crashing frame was running Vision.Root cause
In
Platform/MaciOS/CameraManager.cs(Start()), the delegate is attached to the concurrent global queue:Apple's contract for
setSampleBufferDelegate:queue:requires a serial queue:Because the queue is concurrent,
DidOutputSampleBuffercan run on multiple threads simultaneously.BarcodeAnalyzerholds one sharedVNSequenceRequestHandler:VNSequenceRequestHandleris not thread-safe, so concurrentPerformcalls corrupt Vision's internalos_unfair_lock, aborting the process.AlwaysDiscardsLateVideoFrames = truedoes not prevent this, since the "drop frame while busy" behavior depends on the delegate queue being serial.Note: a serial queue (
com.barcodescanning.maui.sessionQueue) is already created in the class but is only used for session configuration, not for the delegate.Proposed fix
Attach the sample-buffer delegate to a dedicated serial queue instead of
DispatchQueue.DefaultGlobalQueue, e.g.:(Don't reuse
_dispatchQueuefor the delegate, since it's used withDispatchBarrierAsyncfor start/stop and would let frame processing block session reconfiguration.)