Skip to content

1311: Enable applying Multiple Actions on immediate dispatcher for CONFLATE_STALE_RENDERINGS #1344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

steve-the-edwards
Copy link
Contributor

@steve-the-edwards steve-the-edwards commented Jun 13, 2025

First commits adds a failing Android test to demonstrate the problem reported in #1311 .

Second commit fixes this for CONFLATE_STALE_RENDERINGS by yield()ing before applying the first action.

The issue with yield() in the middle of CSR's inner loop was that it breaks our workflow runtime stabilizing into two main thread messages (yield() always posts to the Handler). We want to keep the guarantee of each Workflow update in response to an event as happening within 1 main thread message.

This solution satisfies that constraint by yield()ing before we apply the first action. With these optimizations enabled, when select resumes with a deferrable action ready to be processed, we provide it to the runtime loop as a Deferred - then we yield() before applying it. This allows any other possible collectors to go ahead and queue actions. We can then drain them manually without select in the inner loop for conflate (and later DEA).

Note: We add a field for isDeferrable on action. Worker actions are by default deferrable. This allows us to opt actions from UI handlers out of this type of optimization.

We also add tests on Main.immediate that confirm that non-deferrable actions are handled in one main thread message from the callback. Otherwise they are handled in one main thread message from the action application.

This also adds new methods for fetching those available actions to apply without select (using tryReceive) to the WorkflowRunner and WorkflowNode as well as WorkflowChildNode.

Fixes #1311

key = "Worker3"
) {
action("") {
// Update the rendering in order to show conflation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you mean "Update the state"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the test be more rigorous if you accumulated a bunch of deltas to the state rather than clobbering each time? So at the end you could assert that the rendering = "initial+1+2+3+4"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a much better test!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


collectionJob.cancel()

// 2 renderings (initial and then the update.) Not *5* renderings.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😗 👌

}

@Test
fun with_conflate_we_conflate_stacked_actions_from_side_effects_into_one_rendering() =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What made you decide both tests were necessary? (Don't get me wrong, more is better as far as I'm concerned, just curious.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was early on when i wondered if the Worker machinery was introducing any impedence in resuming to get the actions queued. Its not.

import org.junit.Test
import kotlin.test.assertEquals

@OptIn(WorkflowExperimentalRuntime::class, ExperimentalCoroutinesApi::class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you add a test that simulates having an action enqueued via an event handler lambda on a rendering? End to end demonstration of the primary promise we're trying to make to ourselves. Perhaps it should be parameterized to run w/all the config cominbations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these tests (both for deferrable and non deferrable actions). You and @pyricau can double check my logic for asserting on the main thread message.

@@ -348,6 +368,59 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
}
}

//
// /**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented out b/c not necessary after all? Or is this the thing you have to add to make it actually work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary. I just forgot to remove this.

@steve-the-edwards steve-the-edwards force-pushed the sedwards/update-csr branch 2 times, most recently from f62f5d7 to 455068b Compare June 13, 2025 20:19
@steve-the-edwards steve-the-edwards marked this pull request as ready for review June 13, 2025 20:19
@steve-the-edwards steve-the-edwards requested a review from rjrjr June 13, 2025 20:19
A deferrable action is one that can wait a main thread post - such as from an async trigger (Worker). Not a UI action.

To allow other actions to queue up, so they can be drained.
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@steve-the-edwards
Copy link
Contributor Author

So this adds the ability to set an action as deferrable in order to try and keep the keyUp in the same main thread for UI interactions.

Ultimately I think we don't want that. we would rather have everything from Workflow's perspective in one main thread message (including teh click action handling). SO I think eventually I'd prefer to make all actions deferrable. but we can go one step at a time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

With the Main.immediate dispatcher process side effects in time to drain multiple actions without yield().
2 participants