Skip to content

[BUG]: MidiSrv deadlocks WinMM apps during device enumeration (circular lock in ActivateEndpointInternal) #939

@wellsst

Description

@wellsst

Windows Version

Windows 11 Pro 25H2, Build 26200.8037

Service Installation Method

In-box (retail Windows 11 25H2, not Insider, not GitHub install)

  • MidiSrv version: 10.0.26100.7705
  • MidiSrv MIDI 2.0 driver: 10.0.26100.1

SDK version, if appropriate

N/A

Location

Application using WinMM MIDI 1.0 (Classic API)

Type of bug

Application Crash, Application not working as expected

Steps to reproduce

Device: Akai Professional MPK Mini IV
USB HW ID: USB\VID_09E8&PID_005D&REV_0141&MI_01
Akai USB driver version: 7.0.0.3500
Affected DAWs: Mixcraft 10, Ableton Live Lite 12 (both hang identically)

Scenario A: DAW launch with MPK connected (most reliable repro)

  1. Clean boot Windows
  2. Connect Akai MPK Mini IV via USB (MidiSrv starts automatically on Manual startup type)
  3. Launch Mixcraft 10 (or Ableton Live Lite 12)
  4. Result: DAW window appears but immediately becomes unresponsive (Responding = False in Task Manager). Main thread stuck on LpcReply wait reason (cross-process call to MidiSrv).
  5. DAW remains hung indefinitely — must be force-killed.

Scenario B: Hot-plug MPK while DAW is running

  1. Clean boot Windows, MidiSrv set to Manual (stays Stopped)
  2. Launch Mixcraft 10 without MPK connected — loads normally
  3. Plug in MPK Mini IV — MidiSrv starts and begins MIDI 2.0 enumeration
  4. Result: Mixcraft becomes unresponsive. Same permanent hang.

Scenario C: Registry fix (discovery disabled) — still deadlocks

  1. Set HKLM\Software\Microsoft\Windows MIDI Services\Midi2DiscoveryEnabled = 0
  2. Reboot, connect MPK Mini IV
  3. Launch Mixcraft
  4. Result: MidiSrv starts, creates all 10 MMDEVAPI bridge ports successfully (all show OK status), but Mixcraft still hangs. Main thread blocked on LpcReply. MidiSrv has a Suspended thread.
  5. Conclusion: Discovery disable is insufficient — the deadlock is in core enumeration.

Scenario D: MidiSrv disabled entirely — works

  1. sc.exe config MidiSrv start= disabled, reboot
  2. Connect MPK Mini IV
  3. Launch Mixcraft — starts normally, no hang
  4. Conclusion: The deadlock is entirely within MidiSrv's code paths.

Runtime diagnostics when deadlock is active

  • Mixcraft: Responding = False, 35 threads, main thread in LpcReply wait state
  • MidiSrv: Running, 9 threads (5 UserRequest, 3 EventPairLow, 1 Suspended), 508 handles
  • Stop-Service MidiSrv loops forever on "Waiting for service to stop"
  • taskkill /F /PID <pid> is required; service enters StopPending and ghost devices remain until reboot

Expected behavior

  1. MidiSrv's device enumeration should not hold locks across synchronous waits on async operations (FindAllAsync().get(), SwDeviceCreate().wait())
  2. GetMidiDeviceCount() in the WinMM bridge should not deadlock with MidiSrv's endpoint activation
  3. If MIDI 2.0 discovery fails or is disabled, endpoint activation should complete without blocking WinMM consumers
  4. Stop-Service should complete within a reasonable timeout even when devices are in a bad state

Additional notes

Root cause analysis (from source code review)

We identified a two-layer deadlock in the MidiSrv source:

Layer 1: MIDI 2.0 discovery worker blocks indefinitely

Files: MidiEndpointProtocolWorker.cpp, MidiEndpointProtocolManager.cpp

  1. KS transport OnDeviceAdded() fires when MPK is plugged in
  2. ActivateEndpoint() creates a MIDI SWD (software device)
  3. DiscoverAndNegotiate() spawns a CMidiEndpointProtocolWorker thread (detached)
  4. Worker sends UMP discovery request via RequestAllEndpointDiscoveryInformation()
  5. MPK does NOT respond to MIDI-CI messages
  6. m_allInitialDiscoveryAndNegotiationMessagesReceived.wait(5000ms) times out
  7. Worker then blocks forever on m_endProcessing.wait() — no timeout
  8. Service shutdown calls EndProcessing() → signals event, but m_midiBidiDevice->Shutdown() blocks on stuck KS driver communication

Layer 2: Core enumeration deadlock (the real blocker)

Even with discovery disabled, MidiSrv deadlocks during device enumeration due to circular lock contention between the WinMM bridge and the service:

Thread 1 — WinMM app (e.g., Mixcraft calling midiInGetNumDevs()):

midiInGetNumDevs()
  → WinMM driver midMessage(MIDM_GETNUMDEVS)
    → CMidiPorts::GetMidiDeviceCount() [MidiSrvPorts.cpp]
      → Acquires m_Lock (critical_section)
        → SetupDiGetClassDevs() + SetupDiEnumDeviceInterfaces()
          → Queries device properties managed by MidiSrv
            → BLOCKS waiting for property update to complete

Thread 2 — MidiSrv service (device watcher callback):

OnDeviceAdded()
  → ActivateEndpointInternal() [MidiDeviceManager.cpp]
    → Acquires m_midiPortsLock
      → SwDeviceCreate() with 10-second synchronous wait
        → Needs to update device properties
          → But properties are being read by Thread 1's SetupDi call
            → DEADLOCK — circular wait

Additional synchronous blocking calls under m_midiPortsLock:

  • CompactPortNumbers() (line ~2083): holds m_midiPortsLock while calling DeviceInformation::FindAllAsync().get() — synchronous wait on async WinRT operation
  • AssignPortNumber() (line ~2200): same pattern, FindAllAsync().get() under lock
  • SwDeviceCreate() (line ~1381): 10-second CreationCompleted.wait(SWD_CREATION_TIMEOUT) per device

Additional bugs found

  • Worker threads have no cancellation mechanism — only m_endProcessing event, but bidi device shutdown blocks
  • RemoveWorkerIfPresent() tries Thread->join() but threads are detached (joinable() returns false)
  • Stop-Service MidiSrv never completes because the SCM stop handler hits the same deadlocked paths

MIDI 2.0 endpoints registered

When MidiSrv runs with the MPK Mini IV connected, 9 MIDI 2.0 sub-ports are created under SWD\MMDEVAPI:

Direction Port Name
In (0_0) MPK mini IV MIDI Port
In (0_1) MPK mini IV DAW Port
In (0_2) MPK mini IV Plugin Port
In (0_3) MPK mini IV Software Control
Out (1_0) MPK mini IV MIDI Port
Out (1_1) MPK mini IV DAW Port
Out (1_2) MPK mini IV Plugin Port
Out (1_3) MPK mini IV Software Control
Out (1_4) MPK mini IV Din Port

Suggested fixes

  1. Break the circular lock: Don't hold m_midiPortsLock across FindAllAsync().get() and SwDeviceCreate() — copy needed data, release the lock, perform the async operation, then re-acquire
  2. Add timeouts to all waits: m_endProcessing.wait() in the protocol worker needs a bounded timeout
  3. Make CompactPortNumbers() async: Replace FindAllAsync().get() with proper async/await or move to a background thread that doesn't hold the port lock
  4. Add per-device disable: Allow users to block MIDI 2.0 enumeration for specific devices (by VID/PID) via registry, not just globally

Workaround

Disable MidiSrv: sc.exe config MidiSrv start= disabled

This forces MIDI apps to use the legacy WinMM → USB Audio Class driver path, which works correctly. To restore: sc.exe config MidiSrv start= demand

Questions for the MIDI team

  1. Is there a way to disable MIDI 2.0 enumeration for a specific device (by VID/PID) while keeping MidiSrv running for other devices?
  2. Has this circular lock pattern been identified in internal testing? It appears to affect any USB MIDI device that triggers MidiSrv endpoint activation while a WinMM app is enumerating.
  3. Are there plans to make CompactPortNumbers() and AssignPortNumber() non-blocking?
  4. Is there an ETW provider for MidiSrv that would show the exact lock contention in real-time?

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-investigation 🔍Needs to be investigated before considering or solving.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions