Skip to content

DRAIN_EXCLUSIVE_ACTIONS implementation #1269

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 1 commit into
base: main
Choose a base branch
from

Conversation

steve-the-edwards
Copy link
Contributor

@steve-the-edwards steve-the-edwards commented Mar 5, 2025

If there are multiple actions that are prepared to be applied that are in separate (exclusive) areas of the tree, then apply them all before rendering again.

Also ensure that we track if any of the chained actions have changed state. If they have, then pass the new rendering to the UI!

Update tests to test this behavior.

Base automatically changed from sedwards/partial-tree to main March 6, 2025 19:07
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch 2 times, most recently from d67e58c to 43bdda4 Compare March 6, 2025 19:22
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch 2 times, most recently from 63af06d to ee71c0b Compare April 15, 2025 21:09
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch from ee71c0b to f4a3d4e Compare April 16, 2025 20:38
@steve-the-edwards steve-the-edwards changed the title WIP: DRAIN_EXCLUSIVE_ACTIONS exploration DRAIN_EXCLUSIVE_ACTIONS implementation Apr 16, 2025
@steve-the-edwards steve-the-edwards changed the base branch from main to sedwards/conflate-timeout-1 April 16, 2025 20:40
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch from f4a3d4e to a7dda2e Compare April 16, 2025 20:43
// We start by yielding, because if we are on an Unconfined dispatcher, we want to give
// other signals (like Workers listening to the same result) a chance to get dispatched
// and queue their actions.
yield()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Passes the sniff test: This is a "tight" loop that doesn't necessarily suspend (I think sendOutput is the only suspending call?), so yielding in the middle seems reasonable even if we weren't using an unconfined dispatcher.

@@ -186,11 +187,42 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
// we don't surprise anyone with an unexpected rendering pass. Show's over, go home.
if (!isActive) return@launch

var drainingActionResult = actionResult
if (runtimeConfig.contains(DRAIN_EXCLUSIVE_ACTIONS)) {
drain@ while (isActive &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we still need this isActive check since yield should throw if the job is cancelled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, its technically not needed. I like it for explicitness sake /readability though. is there an impact of this that i'm not considering and we should remove it?

// Next Render Pass.
var nextRenderAndSnapshot: RenderingAndSnapshot<RenderingT> = runner.nextRendering()

if (runtimeConfig.contains(CONFLATE_STALE_RENDERINGS)) {
conflate@ while (isActive && actionResult is ActionApplied<*> && actionResult.output == null) {
conflate@ while (isActive &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question about isActive.

@@ -96,7 +96,12 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
private val eventActionsChannel =
Channel<WorkflowAction<PropsT, StateT, OutputT>>(capacity = UNLIMITED)
private var state: StateT
private var subtreeStateDidChange: Boolean = true

// Our state or that of one of our descendants changed.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Kdoc

// Our state or that of one of our descendants changed.
private var subtreeStateDirty: Boolean = true

// Our state changed.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Kdoc

with(selector) {
eventActionsChannel.onReceive { action ->
return@onReceive applyAction(action)
if (!skipChangedNodes || !selfStateDirty) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Can either of these properties change between line 221 and here? If not, consider assignin to a local val to make it more clear that both these ifs use the same condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no they can't. and sure I can do that!

private var subtreeStateDirty: Boolean = true

// Our state changed.
private var selfStateDirty: Boolean = true
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe i missed it in the code, but it looks like we never set this on its own (without also setting subtreeStateDirty)? Do we really need it?

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, lines 349-350 we set them using different conditions when the action is being applied.

// Our state changed.
    selfStateDirty = actionApplied.stateChanged
    // Our state changed or one of our children's state changed.
    subtreeStateDirty = aggregateActionApplied.stateChanged

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's key for drain exclusive because subtreeStateDirty is true always on a path all the way to the root no matter what changed in the tree. We use selfStateDirty to determine whether the node is exclusive to the previous action applied because otherwise there would never be any other exclusive nodes that we can process actions on.

@steve-the-edwards steve-the-edwards force-pushed the sedwards/conflate-timeout-1 branch from dbd02dc to 934826b Compare April 17, 2025 20:50
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch from a7dda2e to a877247 Compare April 17, 2025 21:16
Base automatically changed from sedwards/conflate-timeout-1 to main April 22, 2025 14:32
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch 2 times, most recently from 532d1d2 to abd8f88 Compare April 23, 2025 13:38
@steve-the-edwards
Copy link
Contributor Author

steve-the-edwards commented Apr 23, 2025

  • Update to add onUiUpdate() to the WorkflowInterceptor interface
  • Update to check for conflationHasChangedState across both DEA and CSR

@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch from abd8f88 to 6e2a691 Compare April 23, 2025 20:03
@steve-the-edwards steve-the-edwards force-pushed the sedwards/drain-exclusive-actions branch from 6e2a691 to d6aa364 Compare May 7, 2025 20:21
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.

2 participants