-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(model-api): node resolution inside coroutines
Moved the class ContextValue to a new library and removed the duplication. Then properly implemented the integration between coroutines and threads, also for JS.
- Loading branch information
Showing
31 changed files
with
530 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
plugins { | ||
`maven-publish` | ||
id("org.jetbrains.kotlin.multiplatform") | ||
} | ||
|
||
kotlin { | ||
jvm() | ||
js(IR) { | ||
browser {} | ||
nodejs { | ||
testTask( | ||
Action { | ||
useMocha { | ||
timeout = "30s" | ||
} | ||
}, | ||
) | ||
} | ||
useCommonJs() | ||
} | ||
sourceSets { | ||
val commonMain by getting { | ||
dependencies { | ||
implementation(libs.kotlin.coroutines.core) | ||
} | ||
} | ||
val commonTest by getting { | ||
dependencies { | ||
implementation(kotlin("test")) | ||
implementation(libs.kotlin.coroutines.test) | ||
} | ||
} | ||
val jvmMain by getting { | ||
dependencies { | ||
} | ||
} | ||
val jvmTest by getting { | ||
dependencies { | ||
} | ||
} | ||
val jsMain by getting { | ||
dependencies { | ||
} | ||
} | ||
val jsTest by getting { | ||
dependencies { | ||
} | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
kotlin-utils/src/commonMain/kotlin/org/modelix/kotlin/utils/ContextValue.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* 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.modelix.kotlin.utils | ||
|
||
expect class ContextValue<E> { | ||
|
||
constructor() | ||
constructor(defaultValue: E) | ||
|
||
fun getValue(): E | ||
fun getValueOrNull(): E? | ||
fun getAllValues(): List<E> | ||
fun <T> computeWith(newValue: E, body: () -> T): T | ||
|
||
suspend fun <T> runInCoroutine(newValue: E, body: suspend () -> T): T | ||
} | ||
|
||
fun <E, T> ContextValue<E>.offer(value: E, body: () -> T): T { | ||
return if (getAllValues().isEmpty()) { | ||
computeWith(value, body) | ||
} else { | ||
body() | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
kotlin-utils/src/commonMain/kotlin/org/modelix/kotlin/utils/RunSynchronized.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
* Copyright (c) 2023. | ||
* | ||
* 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.modelix.kotlin.utils | ||
|
||
expect inline fun <R> runSynchronized(lock: Any, block: () -> R): R |
68 changes: 68 additions & 0 deletions
68
kotlin-utils/src/commonTest/kotlin/org/modelix/kotlin/utils/ContextValueTests.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright (c) 2023. | ||
* | ||
* 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.modelix.kotlin.utils | ||
|
||
import kotlinx.coroutines.coroutineScope | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.test.runTest | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.time.Duration.Companion.milliseconds | ||
|
||
class ContextValueTests { | ||
|
||
@Test | ||
fun multipleCoroutines() = runTest { | ||
val contextValue = ContextValue<String>("a") | ||
assertEquals("a", contextValue.getValueOrNull()) | ||
coroutineScope { | ||
launch { | ||
for (i in 1..10) { | ||
contextValue.runInCoroutine("b1") { | ||
contextValue.computeWith("b11") { | ||
assertEquals("b11", contextValue.getValueOrNull()) | ||
} | ||
assertEquals("b1", contextValue.getValueOrNull()) | ||
contextValue.computeWith("b12") { | ||
assertEquals("b12", contextValue.getValueOrNull()) | ||
} | ||
delay(1.milliseconds) | ||
} | ||
} | ||
} | ||
launch { | ||
for (i in 1..10) { | ||
contextValue.runInCoroutine("b2") { | ||
assertEquals("b2", contextValue.getValueOrNull()) | ||
delay(1.milliseconds) | ||
} | ||
} | ||
} | ||
for (i in 1..5) { | ||
contextValue.runInCoroutine("c") { | ||
assertEquals("c", contextValue.getValueOrNull()) | ||
delay(1.milliseconds) | ||
} | ||
} | ||
} | ||
contextValue.computeWith("d") { | ||
assertEquals("d", contextValue.getValueOrNull()) | ||
} | ||
assertEquals("a", contextValue.getValueOrNull()) | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
kotlin-utils/src/jsMain/kotlin/org/modelix/kotlin/utils/ContextValue.js.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* 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.modelix.kotlin.utils | ||
|
||
import kotlinx.coroutines.currentCoroutineContext | ||
import kotlinx.coroutines.withContext | ||
import kotlin.coroutines.AbstractCoroutineContextElement | ||
import kotlin.coroutines.Continuation | ||
import kotlin.coroutines.ContinuationInterceptor | ||
import kotlin.coroutines.CoroutineContext | ||
|
||
actual class ContextValue<E> { | ||
private val initialStack: List<E> | ||
private var synchronousValueStack: List<E>? = null | ||
private var stackFromCoroutine: List<E>? = null | ||
private val contextElementKey = object : CoroutineContext.Key<ContextValueElement<E>> {} | ||
private var isInSynchronousBlock = false | ||
|
||
actual constructor() { | ||
initialStack = emptyList() | ||
} | ||
|
||
actual constructor(defaultValue: E) { | ||
initialStack = listOf(defaultValue) | ||
} | ||
|
||
actual fun getValue(): E { | ||
return getAllValues().last() | ||
} | ||
|
||
actual fun getValueOrNull(): E? { | ||
return getAllValues().lastOrNull() | ||
} | ||
|
||
actual fun <T> computeWith(newValue: E, body: () -> T): T { | ||
val oldStack = synchronousValueStack | ||
val newStack = getAllValues() + newValue | ||
val wasInSynchronousBlock = isInSynchronousBlock | ||
try { | ||
isInSynchronousBlock = true | ||
synchronousValueStack = newStack | ||
return body() | ||
} finally { | ||
synchronousValueStack = oldStack | ||
isInSynchronousBlock = wasInSynchronousBlock | ||
} | ||
} | ||
|
||
actual fun getAllValues(): List<E> { | ||
return (if (isInSynchronousBlock) synchronousValueStack else stackFromCoroutine) ?: initialStack | ||
} | ||
|
||
private suspend fun getAllValuesFromCoroutine(): List<E>? { | ||
return currentCoroutineContext()[contextElementKey]?.stack | ||
} | ||
|
||
actual suspend fun <T> runInCoroutine(newValue: E, body: suspend () -> T): T { | ||
return withContext(ContextValueElement((getAllValuesFromCoroutine() ?: initialStack) + newValue)) { | ||
val parentInterceptor = checkNotNull(currentCoroutineContext()[ContinuationInterceptor]) { | ||
"No ContinuationInterceptor found in the context" | ||
} | ||
withContext(Interceptor(parentInterceptor)) { | ||
body() | ||
} | ||
} | ||
} | ||
|
||
private inner class Interceptor( | ||
private val dispatcher: ContinuationInterceptor, | ||
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { | ||
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> { | ||
return dispatcher.interceptContinuation(object : Continuation<T> { | ||
override val context get() = continuation.context | ||
|
||
override fun resumeWith(result: Result<T>) { | ||
stackFromCoroutine = context[contextElementKey]?.stack ?: emptyList() | ||
continuation.resumeWith(result) | ||
} | ||
}) | ||
} | ||
|
||
override fun releaseInterceptedContinuation(continuation: Continuation<*>) { | ||
super.releaseInterceptedContinuation(continuation) | ||
stackFromCoroutine = null | ||
} | ||
} | ||
|
||
inner class ContextValueElement<E>(val stack: List<E>) : AbstractCoroutineContextElement(contextElementKey) | ||
} |
21 changes: 21 additions & 0 deletions
21
kotlin-utils/src/jsMain/kotlin/org/modelix/kotlin/utils/RunSynchronized.js.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Copyright (c) 2023. | ||
* | ||
* 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.modelix.kotlin.utils | ||
|
||
actual inline fun <R> runSynchronized(lock: Any, block: () -> R): R { | ||
return block() | ||
} |
54 changes: 54 additions & 0 deletions
54
kotlin-utils/src/jvmMain/kotlin/org/modelix/kotlin/utils/ContextValue.jvm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* 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.modelix.kotlin.utils | ||
|
||
import kotlinx.coroutines.asContextElement | ||
import kotlinx.coroutines.withContext | ||
|
||
actual class ContextValue<E>(private val initialStack: List<E>) { | ||
|
||
private val valueStack = ThreadLocal.withInitial { initialStack } | ||
|
||
actual constructor() : this(emptyList()) | ||
|
||
actual constructor(defaultValue: E) : this(listOf(defaultValue)) | ||
|
||
actual fun <T> computeWith(newValue: E, body: () -> T): T { | ||
val oldStack: List<E> = valueStack.get() | ||
return try { | ||
valueStack.set(oldStack + newValue) | ||
body() | ||
} finally { | ||
valueStack.set(oldStack) | ||
} | ||
} | ||
|
||
actual suspend fun <T> runInCoroutine(newValue: E, body: suspend () -> T): T { | ||
return withContext(valueStack.asContextElement(getAllValues() + newValue)) { | ||
body() | ||
} | ||
} | ||
|
||
actual fun getValue(): E { | ||
return valueStack.get().last() | ||
} | ||
|
||
actual fun getValueOrNull(): E? { | ||
return valueStack.get().lastOrNull() | ||
} | ||
|
||
actual fun getAllValues(): List<E> { | ||
return valueStack.get() | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
kotlin-utils/src/jvmMain/kotlin/org/modelix/kotlin/utils/RunSynchronized.jvm.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* Copyright (c) 2023. | ||
* | ||
* 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.modelix.kotlin.utils | ||
|
||
actual inline fun <R> runSynchronized(lock: Any, block: () -> R): R { | ||
return synchronized(lock, block) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.