diff --git a/core/koin-core/build.gradle b/core/koin-core/build.gradle index 0f3c6512c..cfd4955f2 100644 --- a/core/koin-core/build.gradle +++ b/core/koin-core/build.gradle @@ -75,6 +75,7 @@ kotlin { dependsOn commonMain dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' + implementation "co.touchlab:stately-concurrency:1.2.2" } } diff --git a/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt b/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt index 1852d8fec..f39daeea9 100644 --- a/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt +++ b/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt @@ -33,6 +33,7 @@ import org.koin.ext.getFullName import org.koin.mp.KoinPlatformTimeTools import org.koin.mp.KoinPlatformTools import org.koin.mp.Lockable +import org.koin.mp.ThreadLocal import kotlin.reflect.KClass @Suppress("UNCHECKED_CAST") @@ -58,7 +59,7 @@ class Scope( private val _callbacks = arrayListOf() @KoinInternalApi - val _parameterStack = ArrayDeque() + val _parameterStackLocal = ThreadLocal>() private var _closed: Boolean = false val logger: Logger get() = _koin.logger @@ -223,19 +224,17 @@ class Scope( throw ClosedScopeException("Scope '$id' is closed") } val parameters = parameterDef?.invoke() + var localDeque: ArrayDeque? = null if (parameters != null) { _koin.logger.log(Level.DEBUG) { "| >> parameters $parameters " } - KoinPlatformTools.synchronized(this@Scope) { - _parameterStack.addFirst(parameters) - } + localDeque = _parameterStackLocal.get() ?: ArrayDeque().also(_parameterStackLocal::set) + localDeque.addFirst(parameters) } val instanceContext = InstanceContext(_koin.logger, this, parameters) val value = resolveValue(qualifier, clazz, instanceContext, parameterDef) - if (parameters != null) { + if (localDeque != null) { _koin.logger.debug("| << parameters") - KoinPlatformTools.synchronized(this@Scope) { - _parameterStack.removeFirstOrNull() - } + localDeque.removeFirstOrNull() } return value } @@ -244,41 +243,31 @@ class Scope( qualifier: Qualifier?, clazz: KClass<*>, instanceContext: InstanceContext, - parameterDef: ParametersDefinition?, - ) = ( - _koin.instanceRegistry.resolveInstance(qualifier, clazz, this.scopeQualifier, instanceContext) - ?: run { - // try resolve in injected param - _koin.logger.debug("|- ? t:'${clazz.getFullName()}' - q:'$qualifier' look in injected parameters") - _parameterStack.firstOrNull()?.getOrNull(clazz) - } - ?: run { - // try resolve in scope source - _koin.logger.debug("|- ? t:'${clazz.getFullName()}' - q:'$qualifier' look at scope source") - _source?.let { source -> - if (source::class == clazz && qualifier == null) { - _source as? T - } else { - null - } - } - } - ?: run { - // try resolve in other scopes - _koin.logger.debug("|- ? t:'${clazz.getFullName()}' - q:'$qualifier' look in other scopes") - findInOtherScope(clazz, qualifier, parameterDef) + parameterDef: ParametersDefinition? + ) = (_koin.instanceRegistry.resolveInstance(qualifier, clazz, this.scopeQualifier, instanceContext) + ?: run { + _koin.logger.debug("|- ? t:'${clazz.getFullName()}' - q:'$qualifier' look in injected parameters") + _parameterStackLocal.get()?.firstOrNull()?.getOrNull(clazz) + } + ?: run { + _koin.logger.debug("|- ? t:'${clazz.getFullName()}' - q:'$qualifier' look at scope source" ) + _source?.let { source -> + if (source::class == clazz && qualifier == null) { + _source as? T + } else null } - ?: run { - // in case of error - if (parameterDef != null) { - KoinPlatformTools.synchronized(this@Scope) { - _parameterStack.removeFirstOrNull() - } - _koin.logger.debug("|- << parameters") - } - throwDefinitionNotFound(qualifier, clazz) + } + ?: run { + _koin.logger.debug("|- ? t:'${clazz.getFullName()}' - q:'$qualifier' look in other scopes" ) + findInOtherScope(clazz, qualifier, parameterDef) + } + ?: run { + if (parameterDef != null) { + _parameterStackLocal.remove() + _koin.logger.debug("|- << parameters") } - ) + throwDefinitionNotFound(qualifier, clazz) + }) @Suppress("UNCHECKED_CAST") private fun getFromSource(clazz: KClass<*>): T? { diff --git a/core/koin-core/src/commonMain/kotlin/org/koin/mp/KoinPlatformTools.kt b/core/koin-core/src/commonMain/kotlin/org/koin/mp/KoinPlatformTools.kt index 3b38108da..5621bb32f 100644 --- a/core/koin-core/src/commonMain/kotlin/org/koin/mp/KoinPlatformTools.kt +++ b/core/koin-core/src/commonMain/kotlin/org/koin/mp/KoinPlatformTools.kt @@ -32,3 +32,9 @@ expect object KoinPlatformTools { } expect open class Lockable() + +expect open class ThreadLocal() { + fun get(): T? + fun set(value: T?) + fun remove() +} \ No newline at end of file diff --git a/core/koin-core/src/commonTest/kotlin/org/koin/core/ParameterStackTest.kt b/core/koin-core/src/commonTest/kotlin/org/koin/core/ParameterStackTest.kt index de4c68c9f..5de507e9f 100644 --- a/core/koin-core/src/commonTest/kotlin/org/koin/core/ParameterStackTest.kt +++ b/core/koin-core/src/commonTest/kotlin/org/koin/core/ParameterStackTest.kt @@ -28,6 +28,6 @@ class ParameterStackTest { koin.get { parametersOf(KoinPlatformTools.generateId()) } } - assertTrue(koin.scopeRegistry.rootScope._parameterStack.isEmpty()) + assertTrue(koin.scopeRegistry.rootScope._parameterStackLocal.get()!!.isEmpty()) } } diff --git a/core/koin-core/src/commonTest/kotlin/org/koin/core/ParametersInjectionTest.kt b/core/koin-core/src/commonTest/kotlin/org/koin/core/ParametersInjectionTest.kt index 46d163589..f7f05bfab 100644 --- a/core/koin-core/src/commonTest/kotlin/org/koin/core/ParametersInjectionTest.kt +++ b/core/koin-core/src/commonTest/kotlin/org/koin/core/ParametersInjectionTest.kt @@ -74,8 +74,8 @@ class ParametersInjectionTest { val koin = app.koin val a: Simple.MySingleAndNull = koin.get { parametersOf(42) } - assertEquals(42, a.ms.id) - assertTrue { koin.scopeRegistry.rootScope._parameterStack.isEmpty() } + assertEquals(42, a.ms.id, "id is the right injected") + assertTrue("parameter stack is empty") { koin.scopeRegistry.rootScope._parameterStackLocal.get()?.isEmpty() == true } } @Test diff --git a/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt b/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt index 2510a7fec..3612f0a69 100644 --- a/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt +++ b/core/koin-core/src/jsMain/kotlin/org/koin/mp/PlatformTools.kt @@ -1,5 +1,22 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.koin.mp +import co.touchlab.stately.concurrency.ThreadLocalRef import org.koin.core.context.GlobalContext import org.koin.core.context.KoinContext import org.koin.core.logger.* @@ -38,3 +55,5 @@ actual object KoinPlatformTools { } actual typealias Lockable = Any + +actual typealias ThreadLocal = ThreadLocalRef \ No newline at end of file diff --git a/core/koin-core/src/jvmMain/kotlin/org/koin/mp/KoinPlatformTools.kt b/core/koin-core/src/jvmMain/kotlin/org/koin/mp/KoinPlatformTools.kt index 621c3dca2..4980d1e04 100644 --- a/core/koin-core/src/jvmMain/kotlin/org/koin/mp/KoinPlatformTools.kt +++ b/core/koin-core/src/jvmMain/kotlin/org/koin/mp/KoinPlatformTools.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2017-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.koin.mp import org.koin.core.context.GlobalContext @@ -22,3 +38,5 @@ actual object KoinPlatformTools { } actual typealias Lockable = Any + +actual typealias ThreadLocal = java.lang.ThreadLocal \ No newline at end of file diff --git a/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt b/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt index 6a6085288..4b92b9a67 100644 --- a/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt +++ b/core/koin-core/src/nativeMain/kotlin/org/koin/mp/KoinPlatformTools.kt @@ -2,6 +2,7 @@ package org.koin.mp import co.touchlab.stately.concurrency.Lock import co.touchlab.stately.concurrency.withLock +import co.touchlab.stately.concurrency.ThreadLocalRef import org.koin.core.context.KoinContext import org.koin.core.context.globalContextByMemoryModel import org.koin.core.logger.Level @@ -33,3 +34,5 @@ actual object KoinPlatformTools { actual open class Lockable { internal val lock = Lock() } + +actual typealias ThreadLocal = ThreadLocalRef \ No newline at end of file