Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible race condition in CameraManager causing crashes in versions 1.5.9 and above #123

Open
rafageist opened this issue Jan 4, 2025 · 5 comments

Comments

@rafageist
Copy link

With version 1.5.7 in production we do not have the following issue. However, after upgrading to later versions we started experiencing intermittent crashes, typical of race conditions. This may already be known and being fixed (perhaps it has already been fixed in development), but I am reporting it here anyway.

Affected Versions:

  • Working: 1.5.7
  • Problematic: 1.5.9 and above (including the latest 1.7.3)

Platform

  • iOS (specifically within the CameraManager class)

Steps to Reproduce:

Important

Race conditions are difficult to reproduce

  • Upgrade the BarcodeScanning library from version 1.5.7 to 1.5.9 or later
  • Run the app on an iOS device (slow devices preferably).
  • Initiate barcode scanning operations that start and stop the camera session.
  • Observe that the app crashes intermittently during these operations.

Temporary Workaround:

  • Downgrading to version 1.5.7 eliminates the crashes.

Logs

ObjCRuntime.ObjCException: Objective-C exception thrown.  Name: NSGenericException Reason: *** -[AVCaptureSession stopRunning] stopRunning may not be called between calls to beginConfiguration and commitConfiguration
Native stack trace:
 0   CoreFoundation                      0x00000001804b70ec __exceptionPreprocess + 172
 1   libobjc.A.dylib                     0x000000018008ede8 objc_exception_throw + 72
 2   AVFCapture                          0x00000001d5b8fb7c -[AVCaptureSession stopRunning] + 520
 3   libxamarin-dotnet-debug.dylib       0x0000000105c2ca14 xamarin_dyn_objc_msgSend + 160
 4   libmonosgen-2.0.dylib               0x0000000106e209b8 do_icall + 160
 5   libmonosgen-2.0.dylib               0x0000000106e1f2f4 do_icall_wrapper + 356
 6   libmonosgen-2.0.dylib               0x0000000106e13d44 mono_interp_exec_method + 2836
 7   libmonosgen-2.0.dylib               0x0000000106e10fc4 interp_entry_from_trampoline + 548
 8   WMS.App                             0x00000001043f8d68 native_to_interp_trampoline + 104
 9   libdispatch.dylib                   0x0000000180178de0 _dispatch_client_callout + 16
 10  libdispatch.dylib                   0x0000000180180f60 _dispatch_lane_serial_drain + 956
 11  libdispatch.dylib                   0x0000000180181a98 _dispatch_lane_invoke + 388
 12  libdispatch.dylib                   0x000000018018cf44 _dispatch_root_queue_drain_deferred_wlh + 276
 13  libdispatch.dylib                   0x000000018018c5a0 _dispatch_workloop_worker_thread + 440
 14  libsystem_pthread.dylib             0x000000010568fb74 _pthread_wqthread + 284
 15  libsystem_pthread.dylib             0x000000010568e934 start_wqthread + 8

   at AVFoundation.AVCaptureSession.StopRunning() in .../xamarin-macios/src/build/dotnet/ios/generated-sources/AVFoundation/AVCaptureSession.g.cs:line 500
   at CoreFoundation.DispatchQueue.static_dispatcher_to_managed(IntPtr context) in .../xamarin-macios/src/CoreFoundation/Dispatch.cs:line 379
Native stack trace:
 0   CoreFoundation                      0x00000001804b70ec __exceptionPreprocess + 172
 1   libobjc.A.dylib                     0x000000018008ede8 objc_exception_throw + 72
 2   AVFCapture                          0x00000001d5b8fb7c -[AVCaptureSession stopRunning] + 520
 3   libxamarin-dotnet-debug.dylib       0x0000000105c2ca14 xamarin_dyn_objc_msgSend + 160
 4   libmonosgen-2.0.dylib               0x0000000106e209b8 do_icall + 160
 5   libmonosgen-2.0.dylib               0x0000000106e1f2f4 do_icall_wrapper + 356
 6   libmonosgen-2.0.dylib               0x0000000106e13d44 mono_interp_exec_method + 2836
 7   libmonosgen-2.0.dylib               0x0000000106e10fc4 interp_entry_from_trampoline + 548
 8   WMS.App                             0x00000001043f8d68 native_to_interp_trampoline + 104
 9   libdispatch.dylib                   0x0000000180178de0 _dispatch_client_callout + 16
 10  libdispatch.dylib                   0x0000000180180f60 _dispatch_lane_serial_drain + 956
 11  libdispatch.dylib                   0x0000000180181a98 _dispatch_lane_invoke + 388
 12  libdispatch.dylib                   0x000000018018cf44 _dispatch_root_queue_drain_deferred_wlh + 276
 13  libdispatch.dylib                   0x000000018018c5a0 _dispatch_workloop_worker_thread + 440
 14  libsystem_pthread.dylib             0x000000010568fb74 _pthread_wqthread + 284
 15  libsystem_pthread.dylib             0x000000010568e934 start_wqthread + 8

Suspicions

sequenceDiagram
    participant AppThread as App Thread
    participant SDK as SDK/AVCaptureSession
    AppThread->>SDK: BeginConfiguration
    AppThread->>SDK: StopRunning (explicit)
    Note over AppThread: Explicit Stop called between Begin and Commit
    AppThread->>SDK: CommitConfiguration
    AppThread->>SDK: BeginConfiguration
    SDK->>SDK: StopRunning (implicit)
    Note over SDK: Implicit Stop called by SDK during configuration
    AppThread->>SDK: CommitConfiguration
Loading

There are two potential causes for this issue:

  1. Design or Implementation Issue:

    • Explicit calls to Stop within the CameraManager class, such as from the UpdateCameraEnabled method or other internal logic, might interfere with ongoing beginConfiguration and commitConfiguration blocks. This could result from a design oversight where Stop is enqueued at a point where it disrupts the configuration flow.
  2. Implicit stopRunning Invocations by the SDK:

    • Certain operations in the CameraManager class may trigger the SDK to implicitly call stopRunning on the AVCaptureSession. Potential points include:
      • RemoveInput and AddInput in the UpdateCamera method.
      • Modifications to the SessionPreset property in UpdateResolution.
      • Interactions with the _previewLayer, such as removing it from its superlayer.
    • These actions might internally invoke stopRunning, which could interfere with a beginConfiguration block.

Both possibilities should be investigated to determine whether the issue lies in the design or the behavior of the SDK.

Recommendation

It is highly recommended to review the changes made to the CameraManager class, specifically the file BarcodeScanning.Native.Maui/Platform/MaciOS/CameraManager.cs, between versions 1.5.7 and the latest affected version. This review may shed light on the root cause of this issue, whether it stems from changes in the design or implementation of the class, or from interactions with the SDK's internal behavior.

@afriscic
Copy link
Owner

afriscic commented Jan 5, 2025

Hello.
Thank you for extensive report. You pointed out everything correctly, it'll be very hard to reproduce and I don't have iOS device on hand all the time and also I have access only to very limited number of devices.
I went over all code changes but other than changing this bit of code:

if (_captureSession.Inputs.Length == 0)
   UpdateCamera();
if (_captureSession.SessionPreset is null)
   UpdateResolution();

which could be the issue, I can't find any relevant changes.

I purposely used custom serial DispatchQueue with DispatchAsync to execute conflicting code serially and aslo get good UI responsivnes, but it looks like either I missed something or something gets executed out of order. Maybe DispatchBarrierAsync would be a solution?
Can you see any pattern in this crashes that would be helpful with debugging?

@rafageist
Copy link
Author

Hello @afriscic

Thanks for your comments.

I haven't been able to find a pattern. But it happens without even scanning. Usually when closing the scanner.

Now, I'm more inclined to think that the root of the problem is a call to the stopRunning method within the SDK.

To debug this, I suggest you make a class that inherits from AVCaptureSession and overload the stopRunning method to output a log or better yet, throw an Exception. Maybe get the caller info in some way it can be useful. With this we could know if the stop is called within the SDK without us knowing it.

I'm not sure if the class is sealed, the method allows it with override or new, and this can be done.

public class CustomAVCaptureSession : AVCaptureSession
{
    public override void StopRunning()
    {
        Console.WriteLine("Custom StopRunning called");
        Console.WriteLine(Environment.StackTrace); // Log the stack trace for debugging

        base.StopRunning(); // Call the original implementation
    }
}

If that is the case, then we will know what we should avoid within the begin/commit.

@afriscic
Copy link
Owner

afriscic commented Jan 9, 2025

My wild guess is that it happens on disposing. I'll try to push an update to change the code to DispatchBarrierAsync and to rearrange the conflicting code a bit that would potentially fix this issue.
If it doesn't fix it than I'll try with method override.

@rafageist
Copy link
Author

My wild guess is that it happens on disposing. I'll try to push an update to change the code to DispatchBarrierAsync and to rearrange the conflicting code a bit that would potentially fix this issue. If it doesn't fix it than I'll try with method override.

Perfect, that's a good strategy! The barrier can help ensure the stop doesn't occur in the middle. I'll keep an eye on it.

@yannickberk
Copy link

yannickberk commented Jan 31, 2025

Hey All,

just found the following error occured for one of our users on an iPhone XR (BarcodeScanning.Native.Maui 1.7.3):

System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException ()
System.Collections.Generic.Dictionary`2[[Microsoft.Maui.Controls.BindableProperty, Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.Controls.BindableObject+BindablePropertyContext, Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].TryInsert ()
System.Collections.Generic.Dictionary`2[[Microsoft.Maui.Controls.BindableProperty, Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Microsoft.Maui.Controls.BindableObject+BindablePropertyContext, Microsoft.Maui.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].Add ()
Microsoft.Maui.Controls.BindableObject.SetValueCore ()
Microsoft.Maui.Controls.BindableObject.SetValue ()
BarcodeScanning.CameraView.set_DeviceSwitchZoomFactor ()
BarcodeScanning.CameraManager.UpdateZoomFactor ()
BarcodeScanning.CameraManager.<UpdateCamera>b__24_0 ()
CoreFoundation.DispatchQueue.static_dispatcher_to_managed ()

i suspect this might be caused by the same underlying issue.
We will update to 1.7.6 and see if the issue occurs again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants