gameengineer

gameengineer

Correct thread-safe way to share a resource using Android Studio, kotlin and java?

We are developing on Samsung Tab Active 4 Pro using Android Studio, kotlin and java. We are getting what we think are app deadlocks. The app runs for a few minutes then freezes. I am not new to developing with threads and synchronization methods however I am very new to Android development. I assume I am doing something wrong and will share what I am doing. But to set up the situation, we are using OSMDroid for the map viewing and are using layers of the map for draw icons that represent aircraft flying over the map. The main android app is done in kotlin and the UDP over ethernet is written in java files. Aircraft telemetry is received at about 1 hz over ethernet inside protobuf packets. The protobuf is received and parsed in a Listener java class using a thread and implemented in the run() method. In the udp handler where we receive and parse protobuf we call a kotlin object “class” (unfortunate use of terms with kotlin) that has a method UpdateEntity(). Here is a snippet of this code that handles a proto message type called Entity. EntityClass is the java wrapper created. This part is working fine, no issues here, just showing for context.

(java)

 if (msg.getMessageTypeEnum() == MessageWrapperClass.MessageWrapper.MessageTypeEnum.MESSAGE_TYPE_ENTITY) {
                    EntityClass.Entity entity = msg.getMessageEntity();

               . . . (code deleted for brevity)

                    Datasource.INSTANCE.UpdateEntity(unit);  // calls Kotlin object

                } 

Datasource.INSTANCE.UpdateEntity(unit); call accesses the share list. Here is that method in total. Note Datasource is that singleton-like object construct that has all public “static” methods. (I’m a C++ guy mostly :slight_smile: New to kotlin)


/**
 * [Datasource] holds the data that is utilized by the UI
 */
object Datasource {


    val entityUnitListMutex  = Mutex(false)
 
    /*
    This is the list of units. Any unit added to this list will get added to the map.
     */
    val entityUnitList =  mutableStateListOf<EntityUnit>()

    fun UpdateEntity(entity:EntityUnit)
    {
        entity.lifeTime_ms = System.currentTimeMillis() // update its life time

        try {
            // NOTE - FindEntity() call must remain OUTSIDE of runblocking and mutex because
            // FindEntity also has mutex and we do not want to nest mutex calls. Deadlock may occur
            val existingEntity = FindEntity(entity.id)

            runBlocking {
                entityUnitListMutex.withLock {
                    if (existingEntity != null) {
                        entity.status = existingEntity.status
                        entity.missionCount = existingEntity.missionCount
                        val index = entityUnitList.indexOf(existingEntity)

                        entityUnitList[index] = entity

                    } else {
                        entityUnitList.add(entity)
                    }
                }// end mutex
            }
        }
        catch(e:Exception)
        {
            Log.e(ContentValues.TAG, "Datasource::UpdateEntity " + e.toString())
        }
    }


    /*
    Find the entity in the list of active entities
     */
    fun FindEntity(entityName: String): EntityUnit? {
        try {
            var returnval: EntityUnit? = null
            runBlocking {
                entityUnitListMutex.withLock {
                    for (i in entityUnitList.indices) {
                        if (entityUnitList[i].id == entityName) {
                            returnval = entityUnitList[i]
                            break
                        }
                    }
                }// end mutex
            }

            return returnval
        } catch (e: Exception) {
            Log.e(ContentValues.TAG, "Datasource::FindEntity " + e.toString())

            return null
        }
    }

My understanding of kotlin and android is that you need to use a coroutine if you want to use a mutex. I used a mutex to protect the shared resource, the entityUnitList of type mutableStateListOf

The Listener is started up on the main, UI thread however it uses its own thread to receive udp packets. The call to Datasource::UpdateEntity() is done in that thread, not the UI thread. Datasource is used by the UI thread as well and it needs access to the entityUnitLst to read from. The UI thread does not write to any of the elements in the list nor does it add or subtract elements from that list.

So the problem is the app will lock (not crash and disappear) and be non-responsive or frozen. This happens at various times but typically within a minute or two. My best guess is a thead race condition.

There was someone else developing the UI portions of this app and they said the UI only updates when a data element of it changes. So in UpdateEntity() these two lines do cause a UI refresh.

entityUnitList[index] = entity
entityUnitList.add(entity)

Here is another place accessing that list and is in the UI thread


fun PutStuffOnMap(
    uiState: FOXUiState, mapView: MapView, onEntityClicked: (entityClicked: String) -> Unit
) {
    try {
        val items = ArrayList<OverlayItem>()

        runBlocking {
            Datasource.entityUnitListMutex.withLock {
                for (i in entityUnitList.indices) {
                    val entity = entityUnitList[i]
                    PlotEntity(mapView, entity)?.let { items.add(it) }
                }
            }// end mutex
        }
. . . .

There is more to that function but that function is called from here in a global method tha tis used in the map drawing code.

I am thinking we have a mutex locking unlocking issue where it stays locked and the UI blocks with that runBlocking{ } block waiting for the unlock. The Listener gets in and gets out when updating the entity pretty quick. The only link to the UI thread is Datasource::UpdateEntity(). When we comment out UpdateEntity() we never deaklock or freeze the app. We can even pre-load entityUnitList with entity class elements and interact with them again with no app freeze. Its only when we enable getting movement updates over ethernet (using USB/Ethernet adapter plugged into the tablet) when we freeze the app eventually.

So I think using runBlocking is wrong so what is the correct way to share that entityUnitList over multiple threads safely? We do not have to use a mutex and we could consider other list types that are better with thread synchronization.

Where Next?

Popular Frontend topics Top

pillaiindu
Some days ago I came across a video teaching the internals of git. It had some nice diagrams and animations. The diagrams looked like han...
New
ClaudiaGiv
I have a sveltekit project that is using GoTrue library for authentication. In development mode (npm run dev) everything works but when I...
New
axelson
In Elixir I love to use the library GitHub - sasa1977/boundary: Manage and restrain cross-module dependencies in Elixir projects to enfor...
New
beberardinelli
Hi! I just started coding a few months ago and I am trying to get all the help I can get. My friend showed me this debugging tool called...
New
pavanforza
I have a requirement to extract data from firebase which is used to build serverless applications. Can we connect Firebase no-sql databa...
New
sona11
I have a 1D array of numbers and need help splitting them into groups using a jagged array so that I can perform a series of computations...
New
sona11
I was working on a project that required me to update data in a SQL database. I initiated a transaction and modified the data with a seri...
New
Fl4m3Ph03n1x
Background I have created a fresh Phoenix app using mix phx.new.web web_interface --no-dashboard --no-ecto --no-gettext --no-mailer insid...
New
harwind
I’m currently working on a front-end development project and I’m facing an issue with aligning items using CSS Flexbox. I want to horizon...
New
Fl4m3Ph03n1x
Background I have a custom component in my LiveView, which is basically a group of checkboxes. I want to add a new attribute to my custo...
New

Other popular topics Top

New
siddhant3030
I’m thinking of buying a monitor that I can rotate to use as a vertical monitor? Also, I want to know if someone is using it for program...
New
AstonJ
You might be thinking we should just ask who’s not using VSCode :joy: however there are some new additions in the space that might give V...
New
PragmaticBookshelf
Rust is an exciting new programming language combining the power of C with memory safety, fearless concurrency, and productivity boosters...
New
AstonJ
In case anyone else is wondering why Ruby 3 doesn’t show when you do asdf list-all ruby :man_facepalming: do this first: asdf plugin-upd...
New
AstonJ
Continuing the discussion from Thinking about learning Crystal, let’s discuss - I was wondering which languages don’t GC - maybe we can c...
New
AstonJ
Biggest jackpot ever apparently! :upside_down_face: I don’t (usually) gamble/play the lottery, but working on a program to predict the...
New
New
DevotionGeo
I have always used antique keyboards like Cherry MX 1800 or Cherry MX 8100 and almost always have modified the switches in some way, like...
New
New