Skip to content

fix(playlists): prevent lost playlist edits from concurrent song removals (#2391)#2400

Open
AmrEldeeb5 wants to merge 1 commit into
PixelPlayerHQ:masterfrom
AmrEldeeb5:fix/2391-playlist-song-count
Open

fix(playlists): prevent lost playlist edits from concurrent song removals (#2391)#2400
AmrEldeeb5 wants to merge 1 commit into
PixelPlayerHQ:masterfrom
AmrEldeeb5:fix/2391-playlist-song-count

Conversation

@AmrEldeeb5

Copy link
Copy Markdown

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 PlaylistPreferencesRepository does an unsynchronized read-modify-write:

existing = userPlaylistsFlow.first()  ->  modify the list  ->  updatePlaylist(...)

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 Mutex so each edit reads and writes atomically: addSongsToPlaylist, removeSongFromPlaylist, reorderSongsInPlaylist, renamePlaylist, removeSongFromAllPlaylists, and updatePlaylist. A private updatePlaylistLocked avoids re-entrant locking, and addOrRemoveSongFromPlaylists stays 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):

  • Before the fix: issue2391_quickRemoveThenAdd… fails expected:<4> but was:<5> and concurrentRemovals… fails expected:<1> but was:<4>.
  • After the fix: all three pass.

Note: these are androidTest instrumentation tests, so they need a device/emulator and aren't executed by the current CI build job — they're for local/manual verification and to document the regression.

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: number of songs counted in a playlist

1 participant