Skip to content

iOS/macOS crash: os_unfair_lock_corruption_abort in Vision — sample-buffer delegate runs on a concurrent queue #201

Description

@JoacimWall

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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions