Fix VRC7 data race and use-after-free when reloading/closing modules #106
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
FamiTracker doesn't consistently acquire mutexes when the GUI and audio threads operate on
CSoundGen
. This results in data races, and in the case of the VRC7, the GUI or audio thread can callCAPU::ChangeMachineRate
>CVRC7::SetSampleSpeed
>OPLL_delete
, before the other thread callsOPLL_delete
orOPLL_reset
.The use-after-free results in a crash on ASAN builds (link), and can be reliably reproduced by adding a 1-second sleep in
CVRC7::SetSampleSpeed()
, between callingOPLL_delete()
andOPLL_new()
. See https://github.com/nyanpasu64/Dn-FamiTracker/tree/asan-fix-vrc7 for a reproducer.Fix
I added extra locks to
CSoundGen::m_csAPULock
when necessary. I'm not sure that the current set of locks is sufficient, but ASAN is no longer reporting errors when I open or switch documents, change module properties, or press F12.Additionally I switched
CSoundGen::m_csAPULock
fromCCriticalSection
andCSingleLock
(which didn't work properly), tostd::mutex
andstd::unique_lock
. I'm not sure whyCSingleLock
didn't work. Maybe it's becauseLock()
moves upon return (in the absence of NRVO), and the moved-from object's destructor unlocks theCCriticalSection
. Maybe the lock is released uponstd::this_thread::sleep_for()
. It's not because I tried to lock the same mutex twice in the same thread (since MSVC'sstd::mutex
throws astd::system_error
in that case). In any case,std::unique_lock
works properly.Discussion
Theoretically it's safer to null out
m_pOPLLInt
before callingOPLL_new
(it would be more exception-safe, except not becauseOPLL_new
doesn't throw exceptions), but doing so would prevent ASAN from catching the data races (Windows MSVC doesn't have TSAN right now).I'm not actually sure if the std::atomic is necessary. Not changing it is technically a data race, but not a big deal. It might be faster to use relaxed access,
and switch the compound assignments to something that doesn't write then read(? unsure).There is no wasted write-then-read. See https://en.cppreference.com/w/cpp/atomic/atomic/operator%3D: