diff --git a/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/WorkStealingDispatcherAndroidDispatchersTest.kt b/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/WorkStealingDispatcherAndroidDispatchersTest.kt new file mode 100644 index 000000000..838ee765f --- /dev/null +++ b/workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/android/WorkStealingDispatcherAndroidDispatchersTest.kt @@ -0,0 +1,96 @@ +package com.squareup.workflow1.android + +import com.squareup.workflow1.internal.WorkStealingDispatcher +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlinx.coroutines.yield +import org.junit.Test +import kotlin.test.assertEquals + +class WorkStealingDispatcherAndroidDispatchersTest { + + @Test fun dispatch_runsImmediatelyWhenDelegateIsMainImmediate_onMainThread() = runTest { + val dispatcher = WorkStealingDispatcher(Dispatchers.Main.immediate) + + runOnMainThread { + expect(0) + dispatcher.dispatch { + expect(1) + } + expect(2) + } + } + + @Test fun dispatchNested_enqueuesWhenDelegateIsMainImmediate_onMainThread() = runTest { + val dispatcher = WorkStealingDispatcher(Dispatchers.Main.immediate) + + runOnMainThread { + expect(0) + dispatcher.dispatch { + expect(1) + + // This dispatch should get enqueued to Unconfined's threadlocal queue. + dispatcher.dispatch { + expect(3) + } + + expect(2) + } + expect(4) + } + } + + @Test fun dispatch_queues_whenDelegateisMain_onMainThread() = runTest { + val dispatcher = WorkStealingDispatcher(Dispatchers.Main) + + runOnMainThread { + expect(0) + dispatcher.dispatch { + expect(2) + } + expect(1) + + yield() + expect(3) + } + } + + @Test fun dispatch_runsMultipleTasksInOrder_whenDelegateIsMain_onMainThread() = runTest { + val dispatcher = WorkStealingDispatcher(Dispatchers.Main) + + runOnMainThread { + expect(0) + dispatcher.dispatch { + expect(3) + } + expect(1) + dispatcher.dispatch { + expect(4) + } + expect(2) + + yield() + expect(5) + } + } + + private suspend fun runOnMainThread(block: suspend CoroutineScope.() -> Unit) { + withContext(Dispatchers.Main, block) + } + + private fun CoroutineDispatcher.dispatch(block: () -> Unit) { + dispatch(this) { block() } + } + + private val expectLock = Any() + private var current = 0 + private fun expect(expected: Int) { + synchronized(expectLock) { + assertEquals(expected, current, "Expected to be at step $expected but was at $current") + current++ + } + } +} diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/Synchronization.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/Synchronization.kt index fd98cb9c5..781734170 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/Synchronization.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/Synchronization.kt @@ -1,5 +1,5 @@ package com.squareup.workflow1.internal -internal expect class Lock() +public expect class Lock() -internal expect inline fun Lock.withLock(block: () -> R): R +public expect inline fun Lock.withLock(block: () -> R): R diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkStealingDispatcher.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkStealingDispatcher.kt index c7f23b38d..b8a80c42a 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkStealingDispatcher.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkStealingDispatcher.kt @@ -38,7 +38,7 @@ import kotlin.coroutines.resume * delegate scheduling behavior to. This can either be a confined or unconfined dispatcher, and its * behavior will be preserved transparently. */ -internal open class WorkStealingDispatcher protected constructor( +public open class WorkStealingDispatcher protected constructor( private val delegateInterceptor: ContinuationInterceptor, lock: Lock?, queue: LinkedHashSet? diff --git a/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/Synchronization.jvm.kt b/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/Synchronization.jvm.kt index e84a03123..b6af39442 100644 --- a/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/Synchronization.jvm.kt +++ b/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/Synchronization.jvm.kt @@ -1,5 +1,5 @@ package com.squareup.workflow1.internal -internal actual typealias Lock = Any +public actual typealias Lock = Any -internal actual inline fun Lock.withLock(block: () -> R): R = synchronized(this, block) +public actual inline fun Lock.withLock(block: () -> R): R = synchronized(this, block)