Skip to content

Side effects / Workers / LifecycleWorker methods not called if rendered/not rendered without ever yielding dispatch #1093

@steve-the-edwards

Description

@steve-the-edwards

since worker is built on runningSideEffect, the easier way to reproduce this is:

 runningSideEffect("test") {
   try {
   } finally {
     error()
   }
}

If this is rendered and not rendered within the same thread frame (e.g. using the Main.Immediate dispatcher and cycling back and forth), error() will never get called.

At the very least this needs to be updated in the documentation.

This is because the Job's for side effects are started using CoroutineStart.LAZY so that we don't end up sending anything to the actionSink until after the render() method is complete and the RenderContext frozen (then we call start() on all the side effect jobs - see here.

    baseRenderContext.unfreeze()
    val rendering = interceptor.intercept(workflow, this)
      .render(props, state, context)
    baseRenderContext.freeze()

    // Tear down workflows and workers that are obsolete.
    subtreeManager.commitRenderedChildren()
    // Side effect jobs are launched lazily, since they can send actions to the sink, and can only
    // be started after context is frozen.
    sideEffects.forEachStaging { it.job.start() }
    sideEffects.commitStaging { it.job.cancel() }

What would be better if we provided a hook to the side effect's Job's invokeOnCompletion which will be invoked even if cancelled before the coroutine gets dispatched. E.g. see this test:

fun main() = runBlocking {

    val job = launch(start = CoroutineStart.LAZY) {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("I'm still run in the finally block.")
        }
    }
    job.invokeOnCompletion { cause ->
        println("I'm complete! $cause")
    }
    job.start()
    //yield()
    job.cancel()
    println("done test.")
}

Which prints "I'm complete" but not what is in the finally block (without the yield()).

I propose this could be called.

RenderContext.onNoLongerRendered(). It has to be outside of runningSideEffect's lambda because that won't ever get invoked. We want to attach this right on the Job we create for the side effect.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions