5959// still holding a snapshot of survives until that reader is done with it.
6060// Between push and drain the instance is fully alive, which is what SetIdle's
6161// "is this parent condemned" walk below relies on.
62+ // Both lists are reached from command threads as well as the main loop --
63+ // SetIdle/SwitchToInsertedPlaylist condemn instances from whatever thread runs
64+ // the transition, and a Load() on a command thread parks replaced entries -- so
65+ // every access holds PL_CLEANUPS_LOCK. Deletes never run under it.
66+ static std::mutex PL_CLEANUPS_LOCK ;
6267static std::list<Playlist*> PL_CLEANUPS ;
6368// Entries deleted while one of their own methods may still be on the
6469// call stack (e.g. a "Start Playlist" command entry that reloads this
@@ -1135,27 +1140,48 @@ int Playlist::Process(void) {
11351140 // LogExcess(VB_PLAYLIST, "Playlist::Process: %s, section %s, position: %d\n", m_name.c_str(), m_currentSectionStr.c_str(), m_sectionPosition);
11361141
11371142 if (tl_processDepth == 1 ) {
1138- if (!PL_CLEANUPS .empty ()) {
1139- PL_CLEANUPS .sort ();
1140- PL_CLEANUPS .unique ();
1141- while (!PL_CLEANUPS .empty ()) {
1142- Playlist* p = PL_CLEANUPS .front ();
1143- // Drops the player's owning reference rather than deleting.
1144- // If a reader thread still holds a snapshot of p, the instance
1145- // stays alive until that snapshot releases; its destructor then
1146- // runs on this thread in a later DrainRetiredPlaylists().
1147- Player::INSTANCE .ReleasePlaylist (p);
1148- PL_CLEANUPS .pop_front ();
1143+ {
1144+ // Held across the whole condemned pass (not swap-and-release) so
1145+ // SetIdle's condemned-parent walk on another thread sees either the
1146+ // full pre-drain list or the post-release state, never a moment
1147+ // where a condemned instance has left the list but still looks
1148+ // current-able. ReleasePlaylist only nests the owner's registry
1149+ // lock inside; nothing takes these two in the other order.
1150+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1151+ if (!PL_CLEANUPS .empty ()) {
1152+ PL_CLEANUPS .sort ();
1153+ PL_CLEANUPS .unique ();
1154+ while (!PL_CLEANUPS .empty ()) {
1155+ Playlist* p = PL_CLEANUPS .front ();
1156+ // Drops the player's owning reference rather than deleting.
1157+ // If a reader thread still holds a snapshot of p, the
1158+ // instance stays alive until that snapshot releases; its
1159+ // destructor then runs on this thread in a later
1160+ // DrainRetiredPlaylists().
1161+ Player::INSTANCE .ReleasePlaylist (p);
1162+ PL_CLEANUPS .pop_front ();
1163+ }
11491164 }
11501165 }
11511166 // Runs the destructors the shared_ptr deleter deferred — here, between
11521167 // the two lists, because ~Playlist parks its entries on
11531168 // PL_ENTRY_CLEANUPS and those must be freed in the same tick, exactly
1154- // as when the drain above did the delete itself.
1169+ // as when the drain above did the delete itself. Not under the lock:
1170+ // those destructors push onto PL_ENTRY_CLEANUPS themselves.
11551171 Player::INSTANCE .DrainRetiredPlaylists ();
1156- while (!PL_ENTRY_CLEANUPS .empty ()) {
1157- delete PL_ENTRY_CLEANUPS .front ();
1158- PL_ENTRY_CLEANUPS .pop_front ();
1172+ // Entry destructors can park further entries (nested sub-entries), so
1173+ // swap-and-delete until a pass drains nothing.
1174+ while (true ) {
1175+ std::list<PlaylistEntryBase*> entries;
1176+ {
1177+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1178+ entries.swap (PL_ENTRY_CLEANUPS );
1179+ }
1180+ if (entries.empty ())
1181+ break ;
1182+ for (auto * e : entries) {
1183+ delete e;
1184+ }
11591185 }
11601186 }
11611187 std::unique_lock<std::recursive_mutex> lck (m_playlistMutex);
@@ -1431,15 +1457,20 @@ Playlist* Playlist::SwitchToInsertedPlaylist(bool isStopping) {
14311457 Player::INSTANCE .SetCurrentPlaylist (pl);
14321458 if (replaceSelf) {
14331459 m_parent = nullptr ;
1460+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
14341461 PL_CLEANUPS .push_back (this );
14351462 }
14361463 return pl;
14371464 }
1438- PL_CLEANUPS .push_back (pl);
1465+ {
1466+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1467+ PL_CLEANUPS .push_back (pl);
1468+ }
14391469 if (replaceSelf && Player::INSTANCE .PlaylistSnapshot ().get () != this ) {
14401470 // pl failed to start and its own SetIdle() already handed the
14411471 // player on to our parent, so nothing will process us again.
14421472 m_parent = nullptr ;
1473+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
14431474 PL_CLEANUPS .push_back (this );
14441475 }
14451476 }
@@ -1499,8 +1530,11 @@ void Playlist::SetIdle(bool exit) {
14991530 // parent can be condemned while we're exiting when a re-entrant stop tore
15001531 // down an intermediate level of an inserted-playlist stack.
15011532 Playlist* par = m_parent;
1502- while (par && std::find (PL_CLEANUPS .begin (), PL_CLEANUPS .end (), par) != PL_CLEANUPS .end ()) {
1503- par = par->m_parent ;
1533+ {
1534+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1535+ while (par && std::find (PL_CLEANUPS .begin (), PL_CLEANUPS .end (), par) != PL_CLEANUPS .end ()) {
1536+ par = par->m_parent ;
1537+ }
15041538 }
15051539 if (par && exit) {
15061540 // par is still owned: it is on the parent chain and not condemned, so
@@ -1509,7 +1543,10 @@ void Playlist::SetIdle(bool exit) {
15091543 if (par->getPlaylistStatus () == FPP_STATUS_PLAYLIST_PAUSED ) {
15101544 par->Resume ();
15111545 }
1512- PL_CLEANUPS .push_back (this );
1546+ {
1547+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1548+ PL_CLEANUPS .push_back (this );
1549+ }
15131550
15141551 if (par->getPlaylistStatus () != FPP_STATUS_IDLE )
15151552 publishIdle = false ;
@@ -1554,19 +1591,28 @@ int Playlist::Cleanup(void) {
15541591 while (m_leadIn.size ()) {
15551592 PlaylistEntryBase* entry = m_leadIn.back ();
15561593 m_leadIn.pop_back ();
1557- PL_ENTRY_CLEANUPS .push_back (entry);
1594+ {
1595+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1596+ PL_ENTRY_CLEANUPS .push_back (entry);
1597+ }
15581598 }
15591599
15601600 while (m_mainPlaylist.size ()) {
15611601 PlaylistEntryBase* entry = m_mainPlaylist.back ();
15621602 m_mainPlaylist.pop_back ();
1563- PL_ENTRY_CLEANUPS .push_back (entry);
1603+ {
1604+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1605+ PL_ENTRY_CLEANUPS .push_back (entry);
1606+ }
15641607 }
15651608
15661609 while (m_leadOut.size ()) {
15671610 PlaylistEntryBase* entry = m_leadOut.back ();
15681611 m_leadOut.pop_back ();
1569- PL_ENTRY_CLEANUPS .push_back (entry);
1612+ {
1613+ std::lock_guard<std::mutex> lk (PL_CLEANUPS_LOCK );
1614+ PL_ENTRY_CLEANUPS .push_back (entry);
1615+ }
15701616 }
15711617 return 1 ;
15721618}
0 commit comments