Skip to content

bug: Crash on iOS when you access the storedCalls on threads other than the "bridge"-DispatchQueue #5889

@ph1ps

Description

@ph1ps

Bug Report

Capacitor Version

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 4.1.0
  @capacitor/core: 4.1.0
  @capacitor/android: 4.1.0
  @capacitor/ios: 4.1.0

Installed Dependencies:

  @capacitor/android: not installed
  @capacitor/cli: 4.1.0
  @capacitor/core: 4.1.0
  @capacitor/ios: 4.1.0

[success] iOS looking great! 👌

Platform(s)

iOS

Current Behavior

Calling saveCall or releaseCall from another queue other than the "bridge"-DispatchQueue can cause crashes since Swift's dictionaries are not thread-safe.

Expected Behavior

The expected behaviour would be no crashes. I understand that it may be required that the calls are saved or released on the "bridge"-DispatchQueue. But there is no possibility to do this from other threads which should be possible in my opinion since you may want to release a call after some asynchronous portion of code is done. getDispatchQueue() is internal, so there is no way to schedule work on it other than reflection.

Code Reproduction

In the JS portion you literally just do this:

for (let i = 0; i < 10000; i++) { 
    Capacitor.Plugins.BridgeCrash.echo({value: 'test'});
}

And in the native part:

@objc func echo(_ call: CAPPluginCall) {
    call.keepAlive = true

    DispatchQueue.main.async { [self] in
        bridge?.saveCall(call)
        call.resolve([:])
        bridge?.releaseCall(call)
    }
}

Other Technical Details

npm --version output:
8.5.2
node --version output:
v16.13.1
pod --version output (iOS issues only):
1.11.3

Additional Context

The crash (most of the time) is here:

@objc public func releaseCall(withID: String) {
    storedCalls.removeValue(forKey: withID)
}

This makes sense since Swift's dictionaries are not thread-safe and the 10.000 calls was causing race conditions most of the times I tried. I am suggesting to either put a lock around the storedCalls dictionary access or grant access to the internal DispatchQueue so you can safely switch to the correct thread.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions