Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS Concurrent modification exception when closing a scope #2058

Open
dasdranagon opened this issue Nov 18, 2024 · 4 comments
Open

iOS Concurrent modification exception when closing a scope #2058

dasdranagon opened this issue Nov 18, 2024 · 4 comments
Labels
multiplatform kmp status:wait_feedback Need more details or follow-up type:issue
Milestone

Comments

@dasdranagon
Copy link

Our iOS application occasionally crashes with ConcurrentModificationException.

Uncaught Kotlin exception: kotlin.ConcurrentModificationException
    at 0   kfun:kotlin.Exception#<init>(kotlin.String?;kotlin.Throwable?){} + 143 
    at 1   kfun:kotlin.RuntimeException#<init>(kotlin.String?;kotlin.Throwable?){} + 143 
    at 2   kfun:kotlin.ConcurrentModificationException#<init>(kotlin.String?;kotlin.Throwable?){} + 143 
    at 3   kfun:kotlin.ConcurrentModificationException#<init>(){} + 99 
    at 4   kfun:kotlin.collections.HashMap.Itr#checkForComodification(){} + 247 
    at 5   kfun:kotlin.collections.HashMap.ValuesItr#next(){}1:1 + 219 
    at 6   kfun:kotlin.collections.MutableIterator#next(){}1:0-trampoline + 99 
    at 7   kfun:co.touchlab.stately.collections.ConcurrentMutableIterator.next$lambda$1#internal + 143 
    at 8   kfun:co.touchlab.stately.collections.ConcurrentMutableIterator.$next$lambda$1$FUNCTION_REFERENCE$13.invoke#internal + 79 
    at 9   kfun:kotlin.Function0#invoke(){}1:0-trampoline + 99 
    at 10  kfun:co.touchlab.stately.concurrency.Synchronizable#runSynchronized(kotlin.Function0<0:0>){0§<kotlin.Any?>}0:0 + 355 
    at 11  kfun:co.touchlab.stately.collections.ConcurrentMutableIterator#next(){}1:0 + 271 
    at 12  kfun:kotlin.collections.Iterator#next(){}1:0-trampoline + 99 
    at 13  kfun:org.koin.core.registry.InstanceRegistry#dropScopeInstances(org.koin.core.scope.Scope){} + 459
    at 14  kfun:org.koin.core.registry.ScopeRegistry#deleteScope(org.koin.core.scope.Scope){} + 231 
    at 15  kfun:org.koin.core.scope.Scope.close$lambda$5#internal + 871 
    at 16  kfun:org.koin.core.scope.Scope.$close$lambda$5$FUNCTION_REFERENCE$4.invoke#internal + 71 
    at 17  kfun:org.koin.core.scope.Scope.$close$lambda$5$FUNCTION_REFERENCE$4.$<bridge-DNN>invoke(){}#internal + 71 
    at 18  kfun:kotlin.Function0#invoke(){}1:0-trampoline + 99 
    at 19  kfun:org.koin.mp.KoinPlatformTools#synchronized(org.koin.mp.Lockable;kotlin.Function0<0:0>){0§<kotlin.Any?>}0:0 + 375 
    at 20  kfun:org.koin.core.scope.Scope#close(){} + 223 

Methods like createAllEagerInstances and close in InstanceRegistry can cause concurrent mutation because using an iterator over a collection will cause an exception even if ConcurrentMutableMap was used. We assume that reimplementing access to _instances in a way similar to createAllEagerInstances (in the same file) should solve the problem.

Koin module and version:
koin-core:3.5.6

@arnaudgiuliani
Copy link
Member

arnaudgiuliani commented Dec 18, 2024

Do you have some code to reproduce? We can contact touchlab team for stately collection 👍
Trying local concurrent loops exercise, but nothing here to make it break

@arnaudgiuliani arnaudgiuliani modified the milestones: 4.0.1, 4.1.0 Dec 18, 2024
@arnaudgiuliani arnaudgiuliani added the status:wait_feedback Need more details or follow-up label Dec 18, 2024
@thoutbeckers
Copy link

The behaviour is expected, if you use an iterator it will detect modification while iterating.

Since iteration works by multiple method calls a "safe" collection that wraps access to the methods with reentrant locks does not work to prevent this. Also note that Stately locks (as e.g. synchronized on JVM is) are thread based anyway, so even if you would wrap the entire iteration loop in the same reentrantlock, if you do the modification while iterating from the same thread as the iteration, you'll still get this exception.

The simple fix is to copy the values you want to an array before iterating, as done in e.g.

val instances = arrayListOf(*eagerInstances.values.toTypedArray())

contrast with the method in the stacktrace above :

_instances.values.filterIsInstance<ScopedInstanceFactory<*>>().forEach { factory -> factory.drop(scope) }

Similar bugfix with test in our own project:
splendo/kaluga@d936e81

@arnaudgiuliani arnaudgiuliani changed the title Concurrent modification exception when closing a scope iOS Concurrent modification exception when closing a scope Jan 9, 2025
@arnaudgiuliani
Copy link
Member

Yeah the JVM magic is not helping much on iOS world
Ok, thanks @thoutbeckers

@arnaudgiuliani
Copy link
Member

Will publish a new 4.0.2-RC2. Will be happy to have feedback 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
multiplatform kmp status:wait_feedback Need more details or follow-up type:issue
Projects
None yet
Development

No branches or pull requests

3 participants