Player UI list to kotlin#11829
Conversation
2e23e77 to
5b92de4
Compare
15d5174 to
15eca50
Compare
|
Okay I noticed a funny thing where threads would compete for modifying our UI list, so I added a Mutex around it. |
2f32929 to
cbade0b
Compare
|
|
The code quality thingy is mostly that I marked a function as deprecated and have not yet changed all use-sites because it’s not in-scope of this PR |
Isira-Seneviratne
left a comment
There was a problem hiding this comment.
This looks like a good change, overall.
| fun call(consumer: java.util.function.Consumer<PlayerUi>) { | ||
| // copy the list out of the mutex before calling the consumer which might block | ||
| val new = playerUis.runWithLockSync { | ||
| lockData.toMutableList() | ||
| } | ||
| for (ui in new) { | ||
| consumer.accept(ui) | ||
| } | ||
| } |
There was a problem hiding this comment.
| fun call(consumer: java.util.function.Consumer<PlayerUi>) { | |
| // copy the list out of the mutex before calling the consumer which might block | |
| val new = playerUis.runWithLockSync { | |
| lockData.toMutableList() | |
| } | |
| for (ui in new) { | |
| consumer.accept(ui) | |
| } | |
| } | |
| fun call(consumer: Consumer<PlayerUi>) { | |
| playerUis.forEach(consumer) | |
| } |
There was a problem hiding this comment.
Does this work even when called from Java?
There was a problem hiding this comment.
Yeah, but Unit.INSTANCE has to be returned.
There was a problem hiding this comment.
I changed the parameter type back to Consumer; the synchronized list implementation automatically handles the synchronization needed.
| for (ui in lockData) { | ||
| if (playerUiType.isInstance(ui)) { | ||
| when (val r = playerUiType.cast(ui)) { | ||
| // try all UIs before returning null | ||
| null -> continue | ||
| else -> return@runWithLockSync r | ||
| } | ||
| } | ||
| } | ||
| return@runWithLockSync null |
There was a problem hiding this comment.
| for (ui in lockData) { | |
| if (playerUiType.isInstance(ui)) { | |
| when (val r = playerUiType.cast(ui)) { | |
| // try all UIs before returning null | |
| null -> continue | |
| else -> return@runWithLockSync r | |
| } | |
| } | |
| } | |
| return@runWithLockSync null | |
| synchronized(playerUis) { | |
| playerUis.firstNotNullOfOrNull { playerUiType.kotlin.safeCast(it) } | |
| } |
| import java.util.Optional | ||
|
|
||
| class PlayerUiList(vararg initialPlayerUis: PlayerUi) { | ||
| var playerUis = GuardedByMutex(mutableListOf<PlayerUi>()) |
There was a problem hiding this comment.
Collections.synchronizedList() could be used instead, which would remove the need for a separate mutex class.
| var playerUis = GuardedByMutex(mutableListOf<PlayerUi>()) | |
| val playerUis = Collections.synchronizedList(mutableListOf<PlayerUi>()) |
|
The rest looks good to me, too, thanks! |
| fun <T> destroyAll(playerUiType: Class<T?>) { | ||
| val toDestroy = mutableListOf<PlayerUi>() | ||
|
|
||
| // short blocking removal from class to prevent interfering from other threads | ||
| playerUis.runWithLockSync { | ||
| val new = mutableListOf<PlayerUi>() | ||
| for (ui in lockData) { | ||
| if (playerUiType.isInstance(ui)) { | ||
| toDestroy.add(ui) | ||
| } else { | ||
| new.add(ui) | ||
| } | ||
| } | ||
| lockData = new | ||
| } | ||
| // then actually destroy the UIs | ||
| for (ui in toDestroy) { | ||
| ui.destroyPlayer() | ||
| ui.destroy() | ||
| } |
There was a problem hiding this comment.
In combination with the synchronized list change:
| fun <T> destroyAll(playerUiType: Class<T?>) { | |
| val toDestroy = mutableListOf<PlayerUi>() | |
| // short blocking removal from class to prevent interfering from other threads | |
| playerUis.runWithLockSync { | |
| val new = mutableListOf<PlayerUi>() | |
| for (ui in lockData) { | |
| if (playerUiType.isInstance(ui)) { | |
| toDestroy.add(ui) | |
| } else { | |
| new.add(ui) | |
| } | |
| } | |
| lockData = new | |
| } | |
| // then actually destroy the UIs | |
| for (ui in toDestroy) { | |
| ui.destroyPlayer() | |
| ui.destroy() | |
| } | |
| fun <T : PlayerUi> destroyAll(playerUiType: Class<T>) { | |
| // short blocking removal from class to prevent interfering from other threads | |
| val (toDestroy, toKeep) = synchronized(playerUis) { | |
| playerUis.partition { playerUiType.isInstance(it) } | |
| } | |
| playerUis.retainAll(toKeep) | |
| // then actually destroy the UIs | |
| for (ui in toDestroy) { | |
| ui.destroyPlayer() | |
| ui.destroy() | |
| } |
|
@Isira-Seneviratne I don’t understand your review, you said this looks like a good change, but then you proposed to replace everything with SynchronizedList instead of using a Mutex? |
I meant that the idea of refactoring to ensure thread safety is a good one. Sorry if I caused any confusion. |
|
Personally I don’t like the concept of a SynchronizedList very much, because you have to remember to only use it inside a |
That's only needed when iterating through it without the Java forEach method. All other operations are synchronized. |
|
Yeah, that makes it ever more devious! |
cbade0b to
a7b5f53
Compare
|
So I’d rather not switch this from a simple Mutex around the list to |
This comment was marked as outdated.
This comment was marked as outdated.
|
Note: this conflicts with #9592, so I'd rather merge it after that one is merged back in the refactor branch |
And simplify the code a little
In Kotlin, dealing with nulls works better so we don’t need optional.
a7b5f53 to
935e6fb
Compare
The new implementation would throw `ConcurrentModificationExceptions` when destroying the UIs. So let’s play it safe and put the list behind a mutex. Adds a helper class `GuardedByMutex` that can be wrapped around a property to force all use-sites to acquire the lock before doing anything with the data.
935e6fb to
a8da994
Compare
|
Okay, since the new Android Auto PR was superseded (and the new one merged), I just rebased this and it still applies cleanly, so I think we can merge. |
|




Small helper module
What is it?
Description of the changes in your PR
Before/After Screenshots/Screen Record
Fixes the following issue(s)
Relies on the following changes
APK testing
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. You can find more info and a video demonstration on this wiki page.
Due diligence