¶MIDI startup delays under Windows XP
Anyone who has tried running old Windows programs that use MIDI music under Windows XP knows what a frustrating experience it is: every time the program switches to a MIDI track, it locks up solid for a couple of seconds. This behavior wasn't present in Windows 95/98, and although it is avoided by playing MIDI files through DirectShow, that doesn't help old programs that use Media Control Interface (MCI) to play their music. Interestingly, enough, the pause doesn't get any shorter on a faster system.
This behavior annoyed me enough that I attached a debugger to figure out what was causing the pauses. The cause turned out to be rather surprising.
The way I track down pauses of a few seconds or more is simple: attach a debugger and then hit F12 when the pause occurs, causing the NT kernel to inject a breakpoint into the debugged process. Here's the stack I got:
ntdll.dll!_KiFastSystemCallRet@0() ntdll.dll!_NtDelayExecution@8() + 0xc bytes kernel32.dll!_SleepEx@8() + 0x51 bytes kernel32.dll!_Sleep@4() + 0xf bytes mciseq.dll!_LeaveSeq@0() + 0x13 bytes mciseq.dll!_msOpen@16() + 0x144 bytes mciseq.dll!_mciDriverEntry@16() + 0x7f bytes mciseq.dll!_DriverProc@20() + 0xa4 bytes winmm.dll!_InternalBroadcastDriverMessage@20() + 0xa8 bytes winmm.dll!_DrvSendMessage@16() + 0x18 bytes winmm.dll!_mciSendSingleCommand@28() + 0x5c bytes winmm.dll!_mciSendCommandInternal@20() + 0x10f bytes winmm.dll!_mciSendCommandW@16() + 0x4e bytes winmm.dll!_mciLoadDevice@16() + 0x37b bytes winmm.dll!_mciOpenDevice@12() + 0x267 bytes
That's odd... why is the MCI MIDI driver burning time in Sleep()? Let's look at the disassembly of mciseq.dll!_LeaveSeq():
_LeaveSeq@0: 61D71529 68 E0 60 D7 61 push offset _SeqCritSec (61D760E0h) 61D7152E FF 15 60 10 D7 61 call dword ptr [__imp__LeaveCriticalSection@4 (61D71060h)] 61D71534 6A 04 push 4 61D71536 FF 15 64 10 D7 61 call dword ptr [__imp__Sleep@4 (61D71064h)] 61D7153C C3 ret
WTF is there a Sleep(4) call after a call to LeaveCriticalSection()??? Well, let's look at the caller, _msOpen():
61D716C0 E8 64 FE FF FF call _LeaveSeq@0 (61D71529h) 61D716C5 E8 4E FE FF FF call _EnterSeq@0 (61D71518h) 61D716CA 53 push ebx 61D716CB 8D 45 C4 lea eax,[ebp-3Ch] 61D716CE 50 push eax 61D716CF 8B 45 C0 mov eax,dword ptr [ebp-40h] 61D716D2 6A 0B push 0Bh 61D716D4 FF B0 00 01 00 00 push dword ptr [eax+100h] 61D716DA E8 BA 30 00 00 call _midiSeqMessage@16 (61D74799h) 61D716DF 39 5D D4 cmp dword ptr [ebp-2Ch],ebx 61D716E2 75 0A jne _msOpen@16+16Dh (61D716EEh) 61D716E4 38 5D F8 cmp byte ptr [ebp-8],bl 61D716E7 74 05 je _msOpen@16+16Dh (61D716EEh) 61D716E9 38 5D F9 cmp byte ptr [ebp-7],bl 61D716EC 75 D2 jne _msOpen@16+13Fh (61D716C0h)
Even more strange.
This loop is leaving and then reentering a critical section (mutex) in the middle of a loop, presumably to allow other threads a chance to grab the critical section. Anyone who has written multithreaded code knows that this is a dangerous construct to use because you don't necessarily have a guarantee that the mutex is fair in passing ownership to other threads that may be waiting on it; in the absence of such a guarantee from the mutex design the flow of control between the threads is nondeterministic. This is particularly a problem if the threads are running at different priorities. It looks like someone tried to solve this problem by adding a Sleep(4) call to give other threads, presumably the MIDI sequencer thread, a chance to run.
I'll say it again: Sleep() is not a synchronization mechanism.
I didn't bother to analyze the control flow further, so I tried a quick hack instead. I simply changed the Sleep(4) call to Sleep(0). Suddenly all of the annoying pauses on MIDI track changes were gone, with seemingly no new problems introduced. This surprised me a bit since I don't have a dual-CPU system (it's a laptop, after all) and I would have expected either timing problems on the MIDI playback or CPU usage hitting the roof. Unfortunately, I don't know anyone on the multimedia team at Microsoft, but I am curious as to what that loop is doing. Maybe it's pre-decoding the MIDI file to determine its length? Who knows.