[Bug 28413] Sound play in games and programs causes brief "pauses"

wine-bugs at winehq.org wine-bugs at winehq.org
Thu Feb 9 06:31:02 CST 2012


http://bugs.winehq.org/show_bug.cgi?id=28413

--- Comment #23 from Jörg Höhle <hoehle at users.sourceforge.net> 2012-02-09 06:31:02 CST ---
Your change to PlaySound_Free is incorrect. There are error paths that go there
with last_playsound == wps.

>EnterCriticalSection(&WINMM_cs);
Every time I see a critical section, my reviews are delayed by at least one
hour because I must visit and think about all users and uses of the CS.  Even
more when it's a big lock.

I recommend you look into Interlocked* instead, where applicable.

    if(last_playsound == wps)
        last_playsound = NULL;
InterlockedCompareExchangePointer

>    EnterCriticalSection(&WINMM_cs);
>    if (waveOutOpen(&wps->hWave, ...
You should not hold such a big lock for an operation like waveOutOpen that can
take ages loading all msacm codecs.

Instead the pattern is:
. verify preconditions hold
. start an operation locally
. before changing global state, check whether preconditions still hold
. if not, either abort or retry transaction
  -- With playsound you'll abort, not retry:
if (...) /* are we still owners */
    if(waveOutOpen(&hwave, ...
        if (...) /* are we still owners */
            wps->hWave = hwave;
        else /* superseded by another PlaySound */
            waveOutClose(hwave) + goto CleanUp;

Well, that's concurrency theory.  With winmm, you'll prefer to serialize calls
so as to avoid ERROR_DEVICE_IN_USE in the superseding PlaySound.  How to
serialize?  That's tough without the psLastEvent that you removed.


Can we get rid of critical sections entirely?  No, because waveOutReset depends
on mutual exclusion.  The aborting player must be sure that the other player is
still running and not amid waveOutClose.

Your waveOutReset patch part is correct.  My paranoia would make me cache
last_playsound (and use InterlockedExchange):
EnterCS;
if ((running = last_playsound) != NULL) {
    running->bPlaySoundStop = TRUE;
    if (running->hwave)
        waveOutReset(running->hwave);
} or
if ((running = last_playsound) != NULL && !running->bPlaySoundStop) {
    running->bPlaySoundStop = TRUE;
    if (running->hwave)
        waveOutReset(running->hwave);
}
last_playsound = wps;
LeaveCS;

What must the other side do?
CleanUp:
hwave = wps->hwave;
EnterCS;
wps->hwave = 0; /* tell others to keep off */
if (last_playsound == wps) {
    last_playsound = NULL;
}
LeaveCS;
if (hwave)
    waveOutClose(hwave);

It appears that the CS we need is a pure local one.  Hijacking WINMM_cs may
have unforeseen effects.

Do we need a CS when starting up? I don't think so, but you'll have to use
InterLockedExchange() above and be careful about the condition after
waveOutOpen.  A short CS after waveOutOpen may be easier to reason about until
you get acquainted to Interlocked*.  Please give them a try.  I find the
Interlocked* functions easier to reason about than critical sections, esp. when
it comes to liveness properties in concurency.

-- 
Configure bugmail: http://bugs.winehq.org/userprefs.cgi?tab=email
Do not reply to this email, post in Bugzilla using the
above URL to reply.
------- You are receiving this mail because: -------
You are watching all bug changes.


More information about the wine-bugs mailing list