https://bugs.winehq.org/show_bug.cgi?id=48408
Bug ID: 48408 Summary: mixthread monopolizes buffer_list_lock if WaitForSingleObject returns quickly, causing livelock and game freeze Product: Wine Version: 5.0-rc3 Hardware: x86 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: directx-dsound Assignee: wine-bugs@winehq.org Reporter: florian.will@gmail.com Distribution: ---
Created attachment 66179 --> https://bugs.winehq.org/attachment.cgi?id=66179 C source for reproducing this issue, livelocks on my system
The default WaitForSingleObject implementation is not fast enough to trigger this, but the bug manifests itself when starting wine-staging with WINEESYNC=1. I originally reported this at https://github.com/ValveSoftware/Proton/issues/3387 , but I am now able to reproduce this on wine-staging. The problem is in dsound though.
The dsound mixthread locks the buffer_list_lock, then calls PerformMix(), then releases the lock, then waits for something to happen using WaitForSingleObject, then locks the buffer_list_lock again, in a loop.
This works fine when not using esync (I guess Valve's fsync also triggers the same issue), since the default WaitForSingleObject implementation is slow enough to allow other threads to acquire the buffer_list_lock while the mixthread executes WaitForSingleObject. However, when esync is enabled AND the mixthread has a lot of work to do (because there are many secondary sound buffers), it apparently returns from WaitForSingleObject almost immediately, and very quickly re-acquires the lock after releasing it. The lock implementation does not guarantee fairness, and on my system, this results in the mixthread monopolizing the buffer_list_lock.
If the main thread then wants to add a new secondary sound buffer to the list, it attempts to acquire the buffer_list_lock exclusively, but it can take a very long time to acquire the lock. This leads to a game freeze in Zusi 3 and maybe other games / applications if there are lots of sound effects at the same time.
I have added a Sleep(1) in the mixthread before acquiring the lock, and it fixed the freezes (but it will obviously cause audio stuttering to kick in earlier than without the sleep). Disabling esync also fixes the freeze.
I came up with a simple reproducer, C source attached. Output is like this with esync enabled: [..] Added buffer No. 283, took 0.056744 sec! Added buffer No. 284, took 0.616793 sec! Added buffer No. 285, took 16.775508 sec! Added buffer No. 286, took 11.163868 sec! Added buffer No. 287, took 26.308287 sec!
Obviously on more powerful systems with better single-thread CPU performance, more buffers will be needed before it "almost-livelocks" like this. This output is from my Phenom II X4 955. (If sound starts stuttering instead of the freeze/lock, the bug did not manifest itself.)
With esync disabled (or when using my patched dsound.dll with "Sleep(1)"), it never takes longer than 0.1 sec to add a buffer.