Skip to content

Handed a notification-scheduled Workflow Task back with the completion. - #12

Closed
moetemp wants to merge 6 commits into
moe/AI-198-srv-8-linked-channelfrom
moe/AI-198-srv-9-inline-handoff
Closed

moetemp wants to merge 6 commits into
moe/AI-198-srv-8-linked-channelfrom
moe/AI-198-srv-9-inline-handoff

Conversation

@moetemp

@moetemp moetemp commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

This PR hands a Workflow Task scheduled by a channel notification back with the completion response.

What changed?

  • RespondWorkflowTaskCompleted creates the next Workflow Task when the run holds a pending channel notification, either kind, next to the buffered-events case, so the scheduled event that carries the notification and the task's start land in the completion's write.
  • The task comes back in the completion response under the rules a buffered-Signal task follows: return_new_workflow_task set, the worker eligible, sticky as today. Without the request the task goes through matching as before.
  • MutableState.HasPendingChannelNotifications is the read the handler asks, resolved through the same read-only view the transaction close uses for HasPendingStreamData.
  • A native stream whose frontier moved past a subscription's cursor while the task ran takes the same path through HasPendingStreamData. The slices the handed task carries are decided before the commit, since staging a range on the cursor is a write of the completion's transaction.
  • The transaction close finds the task already scheduled and schedules nothing. The fold rules, the snapshot onto the scheduled event and the describe view are unchanged.
  • tests/inline_handoff_test.go covers both channel kinds, a native stream, the no-request path and a subscribe command handed the channel's latest.

Part of AI-198 (epic AI-37).

Why?

The linked channel measurement put the listener at about 120 state transitions against the Signal's 103 for the same 41 tasks. The difference was the task start: a Signal's task is handed to the completing worker in the completion response, so scheduled and started are one write, while a task scheduled from a pending notification went through matching and its start was a write of its own. With this change the channel pays what a Signal pays on the listener side.

How did you test it?

  • Unit Tests
  • Staging
  • End to End Tests

go build ./..., golangci-lint and the errortype vet are clean on the touched packages. Unit tests ran for ./service/history/api/respondworkflowtaskcompleted/..., ./service/history/workflow/..., ./chasm/lib/channel/... and ./chasm/lib/workflow/.... The functional tests ran with -tags disable_grpc_modules,test_dep: for a subscribed channel and for a linked one, a notify during a running task comes back with the completion response as a started task whose scheduled event carries it, for one state transition rather than two, a completion without the request leaves the task to matching, a repeat of what the handed task carries waits as pending and schedules its own task, and a subscribe command on a channel that already holds a notification gets the latest on the handed task. For a native stream, an append during a running task comes back the same way with the slice on the handed task, and the no-request path still goes through matching. The existing notification channel, linked channel and native stream suites pass.

A notification that lands while a task runs was carried by a task the transaction close scheduled, so matching dispatched it and its start was a second write. The completion handler now creates that task the way it does for a buffered Signal and returns it when the worker asks for the next task.
A subscription's cursor left behind while the task ran had the same gap as a pending notification: the transaction close scheduled the task and matching started it. The slices the handed task carries are now decided before the commit, since staging a range on the cursor is a write of the completion's transaction.
@moetemp

moetemp commented Oct 3, 2026

Copy link
Copy Markdown
Owner Author

Replaced by #18, #19, #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #30, #31, #32, #33, #34, #35, #36 and #37.

Same content, split into 20 PRs in the v3 series: the notification channel first, then the streaming interface, then native streams and the rest. The branch stays as a pin.

@moetemp moetemp closed this Oct 3, 2026
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.

1 participant