Skip to content

Commit bf75158

Browse files
Merge pull request #1339 from square/sedwards/render-context-leak
1338: Clean up caches after WorkflowNode torn down
2 parents 82ca010 + cdf8890 commit bf75158

File tree

3 files changed

+35
-5
lines changed

3 files changed

+35
-5
lines changed

workflow-core/api/workflow-core.api

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,17 @@ public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/
284284
public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
285285
}
286286

287+
public final class com/squareup/workflow1/StatelessWorkflow$StatelessAsStatefulWorkflow : com/squareup/workflow1/StatefulWorkflow {
288+
public fun <init> (Lcom/squareup/workflow1/StatelessWorkflow;)V
289+
public final fun clearCache ()V
290+
public synthetic fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object;
291+
public fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)V
292+
public synthetic fun render (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;)Ljava/lang/Object;
293+
public fun render (Ljava/lang/Object;Lkotlin/Unit;Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;)Ljava/lang/Object;
294+
public synthetic fun snapshotState (Ljava/lang/Object;)Lcom/squareup/workflow1/Snapshot;
295+
public fun snapshotState (Lkotlin/Unit;)Lcom/squareup/workflow1/Snapshot;
296+
}
297+
287298
public final class com/squareup/workflow1/TypedWorker : com/squareup/workflow1/Worker {
288299
public fun <init> (Lkotlin/reflect/KType;Lkotlinx/coroutines/flow/Flow;)V
289300
public fun doesSameWorkAs (Lcom/squareup/workflow1/Worker;)Z

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/StatelessWorkflow.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public abstract class StatelessWorkflow<PropsT, OutputT, out RenderingT> :
227227
* Class type returned by [asStatefulWorkflow].
228228
* See [statefulWorkflow] for the instance.
229229
*/
230-
private inner class StatelessAsStatefulWorkflow :
230+
inner class StatelessAsStatefulWorkflow :
231231
StatefulWorkflow<PropsT, Unit, OutputT, RenderingT>() {
232232

233233
/**
@@ -268,6 +268,23 @@ public abstract class StatelessWorkflow<PropsT, OutputT, out RenderingT> :
268268
}
269269

270270
override fun snapshotState(state: Unit): Snapshot? = null
271+
272+
/**
273+
* When we are finished with at least one node that holds on to this workflow instance,
274+
* then we clear the cache. The reason we do that every time is that it *might* be the last
275+
* node that is caching this instance, and if so, we do not want to leak these cached
276+
* render contexts.
277+
*
278+
* Yes, that means that it might have to be re-created again when this instance is used
279+
* multiple times. The current design for how we get a [StatefulWorkflow] from the
280+
* [StatelessWorkflow] is a failed compromise between performance (caching) and type-safe
281+
* brevity (erasing the `StateT` type from the concerns of [StatelessWorkflow]). It needs
282+
* to be fixed with a bigger re-write (https://github.com/square/workflow-kotlin/issues/1337).
283+
*/
284+
fun clearCache() {
285+
cachedStatelessRenderContext = null
286+
canonicalStatefulRenderContext = null
287+
}
271288
}
272289

273290
private val statefulWorkflow: StatefulWorkflow<PropsT, Unit, OutputT, RenderingT> =

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.squareup.workflow1.RuntimeConfig
99
import com.squareup.workflow1.RuntimeConfigOptions
1010
import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING
1111
import com.squareup.workflow1.StatefulWorkflow
12+
import com.squareup.workflow1.StatelessWorkflow
1213
import com.squareup.workflow1.TreeSnapshot
1314
import com.squareup.workflow1.Workflow
1415
import com.squareup.workflow1.WorkflowAction
@@ -234,11 +235,12 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
234235
* after calling this method.
235236
*/
236237
fun cancel(cause: CancellationException? = null) {
237-
// No other cleanup work should be done in this function, since it will only be invoked when
238-
// this workflow is *directly* discarded by its parent (or the host).
239-
// If you need to do something whenever this workflow is torn down, add it to the
240-
// invokeOnCompletion handler for the Job above.
241238
coroutineContext.cancel(cause)
239+
lastRendering = NullableInitBox()
240+
(
241+
cachedWorkflowInstance as?
242+
StatelessWorkflow<PropsT, OutputT, RenderingT>.StatelessAsStatefulWorkflow
243+
)?.clearCache()
242244
}
243245

244246
/**

0 commit comments

Comments
 (0)