AUv3 hosting: loading back multiple instances

Hello,


I was able to isolate an issue affecting AUv3 hosts, where multiple intanciation of AUv3 plug-ins would randomly lead to unexpected behavior / inconsistency in the host. I tried both on iOS 9.3.5 and iOS 10.3.2, both showing the same results.


This happens when loading back a project containing multiple instances of the same plugin. In my case, this is not done on the main thread but on a worker thread which take care of various "heavy" tasks. I use [AUAudioUnit instantiateWithComponentDescription: options: completionHandler:] to instanciate clients. When the completion handler gets called, I will setup my structure, and call allocateRenderResourcesAndReturnError, restore state, and cache the various blocks for later use.


Some AUv3 plugins will reload just fine, but some will randomly fail. I could observe multiple reasons for failing:

- allocateRenderResourcesAndReturnError returning an error (see below).

- AudioUnitRender returning 49 during audio pulling (with error: "The operation couldn’t be completed. (OSStatus error 49.)").

- Complete freeze of the main thread, blocking the UI.


In allocateRenderResourcesAndReturnError, the NSError passed in the completionHandler block shows the following:

Cannot async instantiate instrument/generator: Couldn’t communicate with a helper application. (au=0x0)


Console logs:

Error: Session <__NSConcreteUUID 0x1334d1d50> D6068E40-5B4C-4BE5-8993-972796FCD56F was unable to communicate with the remote service: Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named xxx.xxxxxxx.viewservice was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid." UserInfo={NSDebugDescription=The connection to service named xxx.xxxxxx.xxxx.viewservice was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid.}


More debug follows:

[739:88058] plugin xxx.xxxxx.xxxx interrupted

[739:88029] 11:46:06.091 WARNING: [0x16e087000] 316: Extension request interrupted! (AU likely crashed. 0x12f9cc760 0x13390cd90)

[739:88029] Received kAudioComponentInstanceInvalidationNotification: auAudioUnit <AUAudioUnit_XH: 0x13390cd90>, audioUnit 0x0


I am wondering the following:


- Is it safe to instanciate AUAudioUnit instances via [AUAudioUnit instantiateWithComponentDescription: options: completionHandler:] on a thread that is not the main one? [Update: I think it's fine, tried both on the main thread and my worker thread with similar results].

- Is it safe to call allocateRenderResourcesAndReturnError on a thread that is not the main one? [Update: I think it's fine, tried both on the main thread and my worker thread with similar results].

- Is it possible that my host, by quickly instanciating AUv3 instances, somewhat triggers race conditions in the AUv3 plugin code? Again, some plugins will load just fine, every time.

Is there any other threading considerations that should be absolutely respected in order to avoid this random plugin client crashes, main thread freezing and instanciation issues?

Thank you for your help.

Mathieu.

Update: I tried the exact same testsuite with multiple AUv3 hosts available on the App Store, and all of them will also randomly fail to load multiple AUv3 instances, or would also crash or freeze, exactly like what I could observe in my host.


The test is simple: I create a new "project" with ~8 instances of the same AUv3, then proceed to reload the project. AU's gets re-instanciated quickly in a row. I'm not sure if iOS will use multiple threads to instanciate various AUv3 at once or if it is done sequentially, but it seems to be the latest.


It seems to be a general issue, that comes from "within" the problematic AUv3 plugins. Some will load just fine -- I was able to load up to 16 instances of a rock stable AUv3, and reload the project back with no issue at all. Granted, this is pretty "empiric" testing. My conclusion is that problematic plugins suffer from various race conditions or other concurrent code delights, crashing in the process of "batch instanciating" them. I will reach out to the two AUv3 developers I based my test-suite on, and see if they can isolate the issue on their side too.


I could also observe that the instanciating code, within iOS AUAudiounit internals, is subject to a deadlock, rendering the main thread unresponsive. This is random too. From the WWDC15 slides, it is suggested to never wait for the completion of an instanciation. My implementation doesn't wait or lock, but is rather notified on success or errors, BUT, randomly, the UI will become unresponsive. I can observe this stack trace while stopping the main thread:


(lldb) bt
* thread #1, queue = 'AUSyncCaller', stop reason = signal SIGSTOP
  * frame #0: 0x00000001809dd014 libsystem_kernel.dylib`semaphore_wait_trap + 8
    frame #1: 0x000000010488fa20 libdispatch.dylib`_dispatch_semaphore_wait_slow + 244
    frame #2: 0x0000000183683bf8 AudioToolbox`___ZN12AUSyncCaller4callERP7NSErrorU13block_pointerFvU13block_pointerFvvEE_block_invoke + 92
    frame #3: 0x000000010487da3c libdispatch.dylib`_dispatch_client_callout + 16
    frame #4: 0x00000001048895f0 libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 164
    frame #5: 0x000000018367eda0 AudioToolbox`-[AUAudioUnit_XH doOpen:completion:] + 540
    frame #6: 0x0000000180e64a60 CoreFoundation`__invoking___ + 144
    frame #7: 0x0000000180d5c488 CoreFoundation`-[NSInvocation invoke] + 284
    frame #8: 0x00000001827ff7ac FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
    frame #9: 0x00000001827ff618 FrontBoardServices`-[FBSSerialQueue _performNext] + 168
    frame #10: 0x00000001827ff9c8 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 56
    frame #11: 0x0000000180e1509c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #12: 0x0000000180e14b30 CoreFoundation`__CFRunLoopDoSources0 + 540
    frame #13: 0x0000000180e12830 CoreFoundation`__CFRunLoopRun + 724
    frame #14: 0x0000000180d3cc50 CoreFoundation`CFRunLoopRunSpecific + 384
    frame #15: 0x0000000182624088 GraphicsServices`GSEventRunModal + 180
    frame #16: 0x0000000186022088 UIKit`UIApplicationMain + 204
    frame #17: 0x000000010175bb8c XXXXXX`nglApplication::Main(this=0x000000013703cc00, argc=1, argv=0x000000016fd6fa60) at nglApplication_UIKit.mm:322
    frame #18: 0x00000001001f4a14 XXXXXX`main(argc=1, argv=0x000000016fd6fa60) at XXXXX.cpp:44
    frame #19: 0x00000001808da8b8 libdyld.dylib`start + 4


I will try to update this thread as more information comes. This was tested on an iPad Pro 12.9" running 10.3.2 (14F5089a) and an an iPad Mini 2 (Retina) running iOS 9.3.5 -- both giving same results.


Thank you,

Mathieu.

Another update: I was able to freeze Garageband following the same procedure. It did happen less often though.

This is indeed an issue that has been around for a while. Discussing it with various AU Host developers it seems to be a symptom of the way iOS handles instantiation of AUv3 extensions. It would be great to know if this is something we can alleviate (either on the side of the host, or of the extension) or whether something needs to be updated in iOS...


Right now it's an unfortunate state of affairs hampering the adoption of the AUv3 standard on iOS, since it leads to unpredictable situations for the end user, which we, developers, can not explain.

AUv3 hosting: loading back multiple instances
 
 
Q