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

View File

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