EN VI

Android - Mutex doesn not stop more than one coroutine to modify or read the data?

2024-03-10 02:00:09
How to Android - Mutex doesn not stop more than one coroutine to modify or read the data

I have a service that holds a list of trains (important data that I want to share between coroutines). It also has methods to modify the list. So, I use mutex in these methods. This is the code that calls the methods (it is inside a coroutine):

val contours: List<MatOfPoint> = ArrayList()
val hierarchy = Mat()

Imgproc.findContours(result, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE)

Imgproc.cvtColor(result, result, Imgproc.COLOR_GRAY2BGR)

for (contour in contours) {
    if (contour.toArray().size > 100) {
       val mask = Mat.zeros(foreground.size(), CvType.CV_8UC1)
       val white = Scalar.all(255.0)
       Imgproc.drawContours(mask, listOf(contour), -1, white, -1)

       val meanColor = Core.mean(foreground, mask)

       trainService.addTrain(contour, meanColor)

       Imgproc.drawContours(result, listOf(contour), -1, meanColor, FILLED)
    } else {
        Imgproc.drawContours(result, listOf(contour), -1, Scalar(0.0, 0.0, 0.0), FILLED)
    }
}

trainService.removeUnusedTrains()

val resultBitmap =
            Bitmap.createBitmap(result.width(), result.height(), Bitmap.Config.ARGB_8888)
Utils.matToBitmap(result, resultBitmap)

This is the train service

class TrainService {
    private var trains : MutableList<Train> = emptyList<Train>().toMutableList()

    private val mutex = Mutex()

    suspend fun addTrain(newTrain: MatOfPoint, trainColor: Scalar) = mutex.withLock {
        for(train in trains) {
            when {
                train.isSame(newTrain) -> return true
            }
        }
        val train = Train(newTrain, trainColor)
        trains.add(train)
    }

    suspend fun removeUnusedTrains() = mutex.withLock {
        for(train in trains) {
            if (!train.changed) {
                trains.remove(train)
            } else {
                train.changed = false
            }
        }
        Log.d("TrainService", "Trains now: ${trains.joinToString()}")
    }
}

I expect that to lock it so only one at the time can modify or read the list. But when I try to run it I get this error:

time: 1709989371723
msg: java.util.ConcurrentModificationException
stacktrace: java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.next(ArrayList.java:860)
    at com.xcerna11.traincontroller.TrainService.removeUnusedTrains(TrainService.kt:25)
    at com.xcerna11.traincontroller.OpenCVDetector$run$2.invokeSuspend(OpenCVDetector.kt:74)
    at com.xcerna11.traincontroller.OpenCVDetector$run$2.invoke(Unknown Source:8)
    at com.xcerna11.traincontroller.OpenCVDetector$run$2.invoke(Unknown Source:4)
    at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
    at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
    at com.xcerna11.traincontroller.OpenCVDetector.run(OpenCVDetector.kt:24)
    at com.xcerna11.traincontroller.TrainAnalyzer$analyze$1$1.invokeSuspend(TrainAnalyzer.kt:20)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source:1)
    at com.xcerna11.traincontroller.TrainAnalyzer.analyze(TrainAnalyzer.kt:19)
    at androidx.camera.core.ImageAnalysis.lambda$setAnalyzer$2(ImageAnalysis.java:528)
    at androidx.camera.core.ImageAnalysis$$ExternalSyntheticLambda2.analyze(D8$$SyntheticClass:0)
    at androidx.camera.core.ImageAnalysisAbstractAnalyzer.lambda$analyzeImage$0$androidx-camera-core-ImageAnalysisAbstractAnalyzer(ImageAnalysisAbstractAnalyzer.java:286)
    at androidx.camera.core.ImageAnalysisAbstractAnalyzer$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
    at java.lang.Thread.run(Thread.java:1012)

Solution:

The problem does not come from the mutex, but from the fact that you call remove in a for each loop. The list is modified while being browsed, which it does not support.

To make your remove function work, you should use a listIterator, which supports modifications at the same time than looping:

    suspend fun removeUnusedTrains() = mutex.withLock {
        val iter = traines.listIterator()
        while (iter.hasNext()) {
            val train = iter.next()
            if (!train.changed) {
                // Ask iterator to remove current element from the list
                iter.remove()
            } else {
                train.changed = false
            }
        }
        Log.d("TrainService", "Trains now: ${trains.joinToString()}")
    }
Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login