Browse Source

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
John C. Allwein 2 năm trước cách đây
mục cha
commit
81f7a21845

+ 17 - 16
panda/src/audiotraits/openalAudioManager.cxx

@@ -959,32 +959,33 @@ 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.
+  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();
 
 
-  // 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;
+    // If pull_used_buffers() encountered an error, the sound was cleaned up.
+    // No need to do anymore work.
+    if (!sound->is_valid()) {
+      continue;
+    }
 
 
-  double rtc = TrueClock::get_global_ptr()->get_short_time();
-  SoundsPlaying::iterator it = _sounds_playing.begin();
-  while (it != _sounds_playing.end()) {
-    PT(OpenALAudioSound) sound = *(it++);
-    sound->pull_used_buffers();
     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));
+      sound->finished();
     }
     }
   }
   }
-
-  for (OpenALAudioSound *sound : sounds_finished) {
-    sound->finished();
-  }
 }
 }
 
 
 
 

+ 1 - 1
panda/src/audiotraits/openalAudioSound.cxx

@@ -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;
         }
         }