Current version

v1.10.4 (stable)

Navigation

Main page
Archived news
Downloads
Documentation
   Capture
   Compiling
   Processing
   Crashes
Features
Filters
Plugin SDK
Knowledge base
Contact info
 
Other projects
   Altirra

Archives

Blog Archive

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.

Comments

This blog was originally open for comments when this entry was first posted, but was later closed and then removed due to spam and after a migration away from the original blog software. Unfortunately, it would have been a lot of work to reformat the comments to republish them. The author thanks everyone who posted comments and added to the discussion.