Skip to content

Commit c438b1c

Browse files
WIP: Intercept to freeze for sideEffects
1 parent db45814 commit c438b1c

File tree

3 files changed

+31
-9
lines changed

3 files changed

+31
-9
lines changed

workflow-runtime-android/src/androidTest/java/com/squareup/workflow1/AndroidRenderWorkflowInTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ import kotlinx.coroutines.plus
1414
import kotlinx.coroutines.sync.Mutex
1515
import kotlinx.coroutines.test.UnconfinedTestDispatcher
1616
import kotlinx.coroutines.test.runTest
17-
import org.junit.Ignore
1817
import org.junit.Test
1918
import kotlin.test.assertEquals
2019
import kotlin.test.assertTrue
2120

2221
@OptIn(WorkflowExperimentalRuntime::class, ExperimentalCoroutinesApi::class)
2322
class AndroidRenderWorkflowInTest {
2423

25-
@Ignore("#1311: Does not yet work with immediate dispatcher.")
24+
// @Ignore("#1311: Does not yet work with immediate dispatcher.")
2625
@Test
2726
fun with_conflate_we_conflate_stacked_actions_into_one_rendering() =
2827
runTest(UnconfinedTestDispatcher()) {

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ internal class RealRenderContext<out PropsT, StateT, OutputT>(
9898
* Freezes this context so that any further calls to this context will throw.
9999
*/
100100
fun freeze() {
101-
checkNotFrozen("freeze") { "freeze" }
101+
// checkNotFrozen("freeze") { "freeze" }
102102
frozen = true
103103
}
104104

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import com.squareup.workflow1.trace
2626
import kotlinx.coroutines.CancellationException
2727
import kotlinx.coroutines.CoroutineName
2828
import kotlinx.coroutines.CoroutineScope
29-
import kotlinx.coroutines.CoroutineStart.LAZY
29+
import kotlinx.coroutines.CoroutineStart.DEFAULT
3030
import kotlinx.coroutines.DelicateCoroutinesApi
3131
import kotlinx.coroutines.ExperimentalCoroutinesApi
3232
import kotlinx.coroutines.Job
@@ -36,7 +36,10 @@ import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
3636
import kotlinx.coroutines.launch
3737
import kotlinx.coroutines.plus
3838
import kotlinx.coroutines.selects.SelectBuilder
39+
import kotlin.coroutines.Continuation
40+
import kotlin.coroutines.ContinuationInterceptor
3941
import kotlin.coroutines.CoroutineContext
42+
import kotlin.coroutines.CoroutineContext.Key
4043
import kotlin.reflect.KType
4144

4245
/**
@@ -279,9 +282,6 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
279282
workflowTracer.trace("UpdateRuntimeTree") {
280283
// Tear down workflows and workers that are obsolete.
281284
subtreeManager.commitRenderedChildren()
282-
// Side effect jobs are launched lazily, since they can send actions to the sink, and can only
283-
// be started after context is frozen.
284-
sideEffects.forEachStaging { it.job.start() }
285285
sideEffects.commitStaging { it.job.cancel() }
286286
remembered.commitStaging { /* Nothing to clean up. */ }
287287
}
@@ -351,13 +351,36 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
351351
}
352352
}
353353

354+
inner class FrozenContextContinuationInterceptor : ContinuationInterceptor {
355+
override val key: Key<*>
356+
get() = ContinuationInterceptor.Key
357+
358+
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
359+
object : Continuation<T> {
360+
override val context: CoroutineContext
361+
get() = continuation.context
362+
363+
override fun resumeWith(result: Result<T>) {
364+
// Freeze the render context each time we are resuming the side effect continuation.
365+
baseRenderContext.freeze()
366+
continuation.resumeWith(result)
367+
}
368+
}
369+
370+
override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
371+
baseRenderContext.unfreeze()
372+
}
373+
}
374+
354375
private fun createSideEffectNode(
355376
key: String,
356377
sideEffect: suspend CoroutineScope.() -> Unit
357378
): SideEffectNode {
358379
return workflowTracer.trace("CreateSideEffectNode") {
359-
val scope = this + CoroutineName("sideEffect[$key] for $id")
360-
val job = scope.launch(start = LAZY, block = sideEffect)
380+
val scope = this +
381+
CoroutineName("sideEffect[$key] for $id") +
382+
FrozenContextContinuationInterceptor()
383+
val job = scope.launch(start = DEFAULT, block = sideEffect)
361384
SideEffectNode(key, job)
362385
}
363386
}

0 commit comments

Comments
 (0)