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

Fix race condition in scope resolveInstance #1465

Merged
merged 2 commits into from Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions core/koin-core/build.gradle
Expand Up @@ -49,6 +49,7 @@ kotlin {
dependencies {
implementation kotlin("test-common")
implementation kotlin("test-annotations-common")
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not testImplementation

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's within the scope of commonTest.

}
}

Expand Down
Expand Up @@ -218,13 +218,17 @@ data class Scope(
val parameters = parameterDef?.invoke()
if (parameters != null) {
_koin.logger.log(Level.DEBUG) { "| put parameters on stack $parameters " }
_parameterStack.addFirst(parameters)
KoinPlatformTools.synchronized(this@Scope) {
_parameterStack.addFirst(parameters)
}
}
val instanceContext = InstanceContext(_koin, this, parameters)
val value = resolveValue<T>(qualifier, clazz, instanceContext, parameterDef)
if (parameters != null) {
_koin.logger.log(Level.DEBUG) { "| remove parameters from stack" }
_parameterStack.removeFirstOrNull()
KoinPlatformTools.synchronized(this@Scope) {
_parameterStack.removeFirstOrNull()
}
}
return value
}
Expand Down Expand Up @@ -252,7 +256,9 @@ data class Scope(
findInOtherScope<T>(clazz, qualifier, parameterDef)
}
?: run {
_parameterStack.clear()
KoinPlatformTools.synchronized(this@Scope) {
_parameterStack.clear()
}
_koin.logger.log(Level.DEBUG) { "| clear parameter stack" }
throwDefinitionNotFound(qualifier, clazz)
})
Expand Down
@@ -1,13 +1,18 @@
package org.koin.core

import kotlin.test.assertEquals
import kotlin.test.Test
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.test.runTest
import org.koin.Simple
import org.koin.core.logger.Level
import org.koin.core.parameter.parametersOf
import org.koin.core.qualifier.named
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class ParametersInjectionTest {
Expand Down Expand Up @@ -44,7 +49,7 @@ class ParametersInjectionTest {
assertNull(a.id)
}

internal class MyOptionalSingle(val i : Int, val o : String? = null)
internal class MyOptionalSingle(val i: Int, val o: String? = null)

@Test
fun nullable_injection_param_in_graph() {
Expand All @@ -53,15 +58,15 @@ class ParametersInjectionTest {
printLogger(Level.DEBUG)
modules(
module {
single { p -> MyOptionalSingle(p.get(),getOrNull()) }
single { p -> MyOptionalSingle(p.get(), getOrNull()) }
})
}

val koin = app.koin
val value = 42
val a: MyOptionalSingle = koin.get { parametersOf(value)}
val a: MyOptionalSingle = koin.get { parametersOf(value) }

assertEquals(value,a.i)
assertEquals(value, a.i)
assertNull(a.o)
}

Expand Down Expand Up @@ -118,9 +123,9 @@ class ParametersInjectionTest {
val app = koinApplication {
printLogger(Level.DEBUG)
modules(
module {
single { (i: Int?) -> Simple.MySingleWithNull(i) }
})
module {
single { (i: Int?) -> Simple.MySingleWithNull(i) }
})
}

val koin = app.koin
Expand Down Expand Up @@ -227,4 +232,28 @@ class ParametersInjectionTest {
assertEquals(42, f.ints.id)
assertEquals("42", f.strings.s)
}

@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun `inject across multiple threads`() = runTest {
val app = koinApplication {
modules(
module {
factory { (i: Int) -> Simple.MyIntFactory(i) }
})
}

val koin = app.koin

repeat(1000) {
val range = (0 until 1000)
val deferreds = range.map {
async(Dispatchers.Default) {
koin.get<Simple.MyIntFactory> { parametersOf(it) }
}
}
val values = awaitAll(*deferreds.toTypedArray())
assertEquals(range.map { it }, values.map { it.id })
}
}
}