openal: fix potential iterator invalidation in OpenALAudioManager::update

OpenALAudioManager::update iterates through all currently playing sounds
via a std::set / phash_set object, _sounds_playing.

If a stream queue corruption was detected during OpenALAudioSound::pull_used_buffers,
the logic added in a895890 would call cleanup() on
the sound if we could not successfully locate the target buffer and log an error.

However, the act of calling OpenALAudioSound::cleanup would lead to calling
stop() (since the sound was actively playing). In OpenALAudioSound::stop(),
we would then proceed to call _manager->stopping_sound which would erase
the current sound from _sounds_playing (while we still held an iterator
to it). Per STL standard and real-world observation, std::set::erase
will invalidate the current iterator held in update (https://en.cppreference.com/w/cpp/container/set/erase).
This leads to a segmentation fault when we attempt the next iteration on the loop.

To resolve this, let's ensure we don't hold onto invalid iterators during the updating of playing sounds.

Fixes #1452
This commit is contained in:
John C. Allwein 2023-01-29 14:58:24 -07:00 committed by rdb
parent 1c37522026
commit 81f7a21845
2 changed files with 18 additions and 17 deletions

View File

@ -959,33 +959,34 @@ stop_all_sounds() {
*/ */
void OpenALAudioManager:: void OpenALAudioManager::
update() { update() {
ReMutexHolder holder(_lock); ReMutexHolder const holder(_lock);
// See if any of our playing sounds have ended we must first collect a // See if any of our playing sounds have ended.
// seperate list of finished sounds and then iterated over those again double const rtc = TrueClock::get_global_ptr()->get_short_time();
// calling their finished method. We can't call finished() within a loop SoundsPlaying::iterator i = _sounds_playing.begin();
// iterating over _sounds_playing since finished() modifies _sounds_playing while (i != _sounds_playing.end()) {
SoundsPlaying sounds_finished; // The post-increment syntax is *very* important here.
// As both OpenALAudioSound::pull_used_buffers and OpenALAudioSound::finished can modify the list of sounds playing
double rtc = TrueClock::get_global_ptr()->get_short_time(); // by erasing 'sound' from the list, thus invaliding the iterator.
SoundsPlaying::iterator it = _sounds_playing.begin(); PT(OpenALAudioSound) sound = *(i++);
while (it != _sounds_playing.end()) {
PT(OpenALAudioSound) sound = *(it++);
sound->pull_used_buffers(); sound->pull_used_buffers();
// If pull_used_buffers() encountered an error, the sound was cleaned up.
// No need to do anymore work.
if (!sound->is_valid()) {
continue;
}
sound->push_fresh_buffers(); sound->push_fresh_buffers();
sound->restart_stalled_audio(); sound->restart_stalled_audio();
sound->cache_time(rtc); sound->cache_time(rtc);
if (sound->_source == 0 || if (sound->_source == 0 ||
(sound->_stream_queued.empty() && (sound->_stream_queued.empty() &&
(sound->_loops_completed >= sound->_playing_loops))) { (sound->_loops_completed >= sound->_playing_loops))) {
sounds_finished.insert(std::move(sound));
}
}
for (OpenALAudioSound *sound : sounds_finished) {
sound->finished(); sound->finished();
} }
} }
}
/** /**

View File

@ -536,7 +536,7 @@ pull_used_buffers() {
} }
} }
if (!found_culprit) { if (!found_culprit) {
audio_error("corruption in stream queue"); audio_error(get_name() << ": corruption in stream queue");
cleanup(); cleanup();
return; return;
} }