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

Zuber
How to make a website like webnovel and wattpadd where subscribers and logged in user post stories like their own Novel in a website and ...
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
prego4444
how can i make a border like this to be exactly on the midle of the edge? i could only found border in inside and outside but nothing on ...
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
JessicaW33
Hello All, How would you implement a navigation system in React Native? Could not find a way so I ask the community can someone help me ...
New
Fl4m3Ph03n1x
Background I have a button that may be disabled or not, depending on a set of conditions. I want to disable/enable the button without hav...
New
Fl4m3Ph03n1x
Background I have Phoenix umbrella application. When inside said application, I can run it without issues if MIX_ENV=prod. However, if I ...
New
pjamesrud
I am creating an app that allows user to enter values and depending on the value a number of textviews are changed programmatically from ...
New
Julien0577
Hi all, Anybody knows how to do this menu animation? (from BBVA APP, they have the same for both android and iOS app). Is it custom?...
New
SteelFork2819
hi does anyone know how to render a cloud-stored 3D file and move a camera around in it using native html5 functions? i really don’t know...
New

Other popular topics Top

PragmaticBookshelf
Learn from the award-winning programming series that inspired the Elixir language, and go on a step-by-step journey through the most impo...
New
dasdom
No chair. I have a standing desk. This post was split into a dedicated thread from our thread about chairs :slight_smile:
New
AstonJ
poll poll Be sure to check out @Dusty’s article posted here: An Introduction to Alternative Keyboard Layouts It’s one of the best write-...
New
AstonJ
Thanks to @foxtrottwist’s and @Tomas’s posts in this thread: Poll: Which code editor do you use? I bought Onivim! :nerd_face: https://on...
New
AstonJ
Just done a fresh install of macOS Big Sur and on installing Erlang I am getting: asdf install erlang 23.1.2 Configure failed. checking ...
New
PragmaticBookshelf
Build highly interactive applications without ever leaving Elixir, the way the experts do. Let LiveView take care of performance, scalabi...
New
New
PragmaticBookshelf
Programming Ruby is the most complete book on Ruby, covering both the language itself and the standard library as well as commonly used t...
New
First poster: bot
zig/http.zig at 7cf2cbb33ef34c1d211135f56d30fe23b6cacd42 · ziglang/zig. General-purpose programming language and toolchain for maintaini...
New
New