ioscoremidi

Any Core MIDI call causes app to become unresponsive


I've come across an issue with Core MIDI across a couple of iOS apps I work on, and another app I downloaded from the App Store.

What appears to be happening is the Core MIDI server crashes. After this happens, any interaction with Core MIDI functions or properties makes the calling app unresponsive. The following line is in AppDelegate's didFinishLaunchingWithOptions::

[MIDINetworkSession defaultSession].enabled = YES;

Execution stops at this point, causing a forever hang when debugging or a #8badf00d crash otherwise.

This happens even if the Core MIDI function should return an OSStatus - the return value never comes back:

OSStatus s = MIDIObjectGetStringProperty(ref, kMIDIPropertyDisplayName, (CFStringRef*)&string);

This call (which is in the PGMidi library) also hangs, with s not being returned. The only way to fix the issue is to restart the device.

The Core MIDI server becoming unresponsive seems related to running multiple Network Sessions from a connected Mac, but I haven't been able to pinpoint a way to replicate reliably. As well as my own apps (which use the MIKMIDI library), I have experienced unresponsive behaviour with midimittr, downloaded from the App Store.

Of course, I have no control over whether the Core MIDI Server becomes unresponsive. But is there a way to check the status of the MIDI server before making calls that may make my app unresponsive?


Update: 2019-05-10

I've found that I can get the iOS Core MIDI server into this unresponsive state by following these steps:

  1. Set up a Network MIDI session on a Mac in Audio/MIDI Setup.
  2. In iOS app, connect to that Network MIDI session.
  3. Let Mac go to sleep naturally (invoking sleep from the Apple Menu doesn't appear to trigger the issue).
  4. Once the Mac has gone to sleep do something in iOS app that will call Core MIDI.

So it's like the iOS Core MIDI server has lost connection to the Mac's MIDI network, but continues to try and execute.


Update: 2019-05-17

Apple responded to my bug report (#50657978) saying it is a duplicate of another report (#49583498) and will be closed. At the very least they are aware of the bug.


Update: 2019-07-17

This Core MIDI hang state can also be entered more quickly (for debugging purposes) by connecting the Core MIDI network via Audio Music Setup, then backgrounding your app and switching on Airplane mode on the iOS device.


Solution

  • SEE BELOW FOR LATEST UPDATE AND FINAL SOLUTION


    I've managed to come up with a partial solution to this problem. (Alas, as the root cause seems embedded in Apple's code a complete solution is not possible.)

    In my AppDelegate I now run a routine which can detect whether calls to Core MIDI will time out:

    func enableMIDINetworkSession() {
        let midiNetworkGroup = DispatchGroup()
        
        midiNetworkGroup.enter()
        DispatchQueue.global(qos: .background).async {
            MIDINetworkSession.default().isEnabled = true
            midiNetworkGroup.leave()
        }
        
        // If `midiNetworkGroup.leave()` is not reached in the closure above, then the result below will be `.timeOut`
        let midiNetworkTimeoutResult = midiNetworkGroup.wait(timeout: DispatchTime.now() + 10.0)
        
        switch midiNetworkTimeoutResult {
        case .timedOut:
            { ... }  // Calls to Core MIDI will cause app to hang. Handle as required. Note: app will crash anyway at some point in the future.
            
        case .success:
            break    // All good, continue as before
        }
    }
    

    A thing to note here: if you intend to display an alert in the case that midiNetworkTimeoutResult == .timeOut then keep in mind that any calls to Core MIDI that occur straight after this may cause the alert to not be presented (as the main queue will be blocked).


    Update: 2019-07-17

    It's important to mention with the solution above that if you end up in the .timedOut case above, the app will crash at some point in the future anyway, as it catches up with the MIDINetworkSession.default().isEnabled call.

    The gap before the crash is around 5 minutes, so there's enough time to display an alert and advise the user to restart their device.


    Update: 2019-08-14

    Recommended solution

    It appears that this issue has been resolved by Apple in iOS 12.4. The solution then would be to advise your users to update to iOS 12.4 or later.

    (Also, this issue seems only to affect iOS 12.2 and 12.3 versions.)