diff --git a/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ConcurrencyTest.kt b/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ConcurrencyTest.kt new file mode 100644 index 000000000..d38984f61 --- /dev/null +++ b/projects/core/koin-core/src/commonTest/kotlin/org/koin/core/ConcurrencyTest.kt @@ -0,0 +1,104 @@ +package org.koin.core + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.component.getScopeId +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.dsl.module +import org.koin.mp.KoinPlatform +import org.koin.mp.KoinPlatformTools +import org.koin.mp.Lockable +import org.koin.mp.generateId +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ConcurrencyTest { + data class Counter(val id : String = KoinPlatformTools.generateId()) + class CounterService { + + private var counter = 0 + private val lock = Lockable() + + fun increment() = KoinPlatformTools.synchronized(lock) { + counter++ + } + + fun getCounter(): Int = KoinPlatformTools.synchronized(lock) { counter } + } + + val jobList = 100 + val jobRepeat = 100 + val scopeList = 10_000 + + @Test + fun parallel_access() = runTest { + + startKoin { + modules(module { + single { CounterService() } + }) + } + + try { + // Use a coroutine scope for concurrency + coroutineScope { + val jobs = List(jobList) { + launch { + repeat(jobRepeat) { + KoinPlatform.getKoin().get().increment() + } + } + } + jobs.joinAll() + } + + assertEquals(jobList * jobRepeat, KoinPlatform.getKoin().get().getCounter()) + } finally { + stopKoin() + } + } + + + @OptIn(KoinInternalApi::class) + @Test + fun parallel_create_scopes() = runTest { + + startKoin { + modules(module { + scope{ + scoped { CounterService() } + } + }) + } + + val result = KoinPlatformTools.safeHashMap() + + try { + coroutineScope { + val startScopes = List(scopeList) { + launch { + KoinPlatform.getKoin().apply { + val counter = Counter() + val scope = createScope(counter.getScopeId()) + scope.get().increment() + result[scope.id] = scope.get().getCounter() + scope.close() + } + } + } + startScopes.joinAll() + } + assertTrue(KoinPlatform.getKoin().instanceRegistry.instances.values.none { it.isCreated() }) + } finally { + stopKoin() + } + assertEquals(scopeList, result.size) + assertTrue(result.values.all { it == 1 }) + } + +} \ No newline at end of file diff --git a/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt b/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt index 3dc23992f..ea834e06c 100644 --- a/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt +++ b/projects/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt @@ -17,6 +17,7 @@ package org.koin.mp import co.touchlab.stately.collections.ConcurrentMutableMap +import co.touchlab.stately.collections.ConcurrentMutableSet import org.koin.core.context.GlobalContext import org.koin.core.context.KoinContext import org.koin.core.logger.* @@ -31,5 +32,5 @@ actual object KoinPlatformTools { actual fun defaultContext(): KoinContext = GlobalContext actual fun synchronized(lock: Lockable, block: () -> R) = block() actual fun safeHashMap(): MutableMap = ConcurrentMutableMap() - actual fun safeSet(): MutableSet = mutableSetOf() + actual fun safeSet(): MutableSet = ConcurrentMutableSet() } diff --git a/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt b/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt index 2b758266f..c1450f83d 100644 --- a/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt +++ b/projects/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt @@ -1,6 +1,7 @@ package org.koin.mp import co.touchlab.stately.collections.ConcurrentMutableMap +import co.touchlab.stately.collections.ConcurrentMutableSet import co.touchlab.stately.concurrency.withLock import org.koin.core.context.KoinContext import org.koin.core.context.globalContextByMemoryModel @@ -21,10 +22,8 @@ actual object KoinPlatformTools { actual fun defaultLazyMode(): LazyThreadSafetyMode = LazyThreadSafetyMode.PUBLICATION actual fun defaultLogger(level: Level): Logger = PrintLogger(level) actual fun defaultContext(): KoinContext = defaultContext - - actual fun synchronized(lock: Lockable, block: () -> R): R = lock.lock.withLock { block() } - + actual fun synchronized(lock: Lockable, block: () -> R): R = lock.lock.withLock(block) actual fun safeHashMap(): MutableMap = ConcurrentMutableMap() - actual fun safeSet(): MutableSet = mutableSetOf() + actual fun safeSet(): MutableSet = ConcurrentMutableSet() } diff --git a/projects/core/koin-core/src/wasmJsMain/kotlin/org/koin/mp/PlatformTools.kt b/projects/core/koin-core/src/wasmJsMain/kotlin/org/koin/mp/PlatformTools.kt index 5f144f194..49d189666 100644 --- a/projects/core/koin-core/src/wasmJsMain/kotlin/org/koin/mp/PlatformTools.kt +++ b/projects/core/koin-core/src/wasmJsMain/kotlin/org/koin/mp/PlatformTools.kt @@ -16,6 +16,8 @@ package org.koin.mp +import co.touchlab.stately.collections.ConcurrentMutableMap +import co.touchlab.stately.collections.ConcurrentMutableSet import org.koin.core.context.GlobalContext import org.koin.core.context.KoinContext import org.koin.core.logger.* @@ -30,6 +32,6 @@ actual object KoinPlatformTools { actual fun defaultLogger(level: Level): Logger = PrintLogger(level) actual fun defaultContext(): KoinContext = GlobalContext actual fun synchronized(lock: Lockable, block: () -> R) = block() - actual fun safeHashMap(): MutableMap = HashMap() - actual fun safeSet(): MutableSet = mutableSetOf() + actual fun safeHashMap(): MutableMap = ConcurrentMutableMap() + actual fun safeSet(): MutableSet = ConcurrentMutableSet() } \ No newline at end of file