fix(playlists): prevent lost playlist edits from concurrent song removals (#2391)#2400
Open
AmrEldeeb5 wants to merge 1 commit into
Open
fix(playlists): prevent lost playlist edits from concurrent song removals (#2391)#2400AmrEldeeb5 wants to merge 1 commit into
AmrEldeeb5 wants to merge 1 commit into
Conversation
…urrently PlaylistPreferencesRepository edited playlists with an unsynchronized read-modify-write: userPlaylistsFlow.first() -> modify list -> updatePlaylist(). Removing several songs in quick succession fired concurrent coroutines that each read the same snapshot, so the last write won and the other removals were silently dropped. The Playlists-menu song count (songIds.size, read from the DB) then stayed stuck high, while the playlist detail screen still looked correct because it updates optimistically per tap. Serialize the read-modify-write editors (add/remove/reorder/rename/updatePlaylist /removeSongFromAllPlaylists) behind a coroutine Mutex so each edit reads and writes atomically. A private updatePlaylistLocked avoids re-entrant locking. Add an instrumentation regression test covering sequential add/remove, the concurrent-removal race, and the exact issue PixelPlayerHQ#2391 reproduction (fails before this change, passes after). Fixes PixelPlayerHQ#2391
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Fixes #2391 — the per-playlist song count in the Playlists menu doesn't update when you remove songs, only when you add them, and the difference between the menu count and the in-playlist count persists.
Root cause
The bug is not "count incremented on add but not decremented on remove" — a single removal works fine. The real cause is a lost-update race.
Every editor in
PlaylistPreferencesRepositorydoes an unsynchronized read-modify-write:Removing several songs in quick succession (the issue's "remove one or two") fires concurrent coroutines (one per tap, via
viewModelScope.launch) that all read the same snapshot. The last writer wins and the other removals are silently dropped. The menu count (songIds.size, read straight from the DB) then stays stuck high, while the playlist detail screen still looks correct because it updates optimistically per tap. That's exactly the reported symptom — and why it only seemed to break on remove.Fix
Serialize the read-modify-write editors behind a coroutine
Mutexso each edit reads and writes atomically:addSongsToPlaylist,removeSongFromPlaylist,reorderSongsInPlaylist,renamePlaylist,removeSongFromAllPlaylists, andupdatePlaylist. A privateupdatePlaylistLockedavoids re-entrant locking, andaddOrRemoveSongFromPlaylistsstays unlocked and delegates to the now-atomic methods (no nesting / no deadlock).It's a coroutine
Mutex(suspends, never blocks a thread), and playlist edits are infrequent, so there's no perceptible cost.Tests
New instrumentation test
PlaylistSongCountTest(in-memory Room + the real repository):menuSongCount_reflectsAddAndRemove— sequential add/remove stays correct.concurrentRemovals_doNotLoseUpdates— 4 concurrent removals all persist.issue2391_quickRemoveThenAdd_keepsCountAccurate— the issue's exact steps.Verified on a physical device (Realme RMX2040, Android 11):
issue2391_quickRemoveThenAdd…failsexpected:<4> but was:<5>andconcurrentRemovals…failsexpected:<1> but was:<4>.