Skip to content

Write concurrent oneof payload visits back to the message - #324

Merged
Vaughan-Temporal merged 3 commits into
mainfrom
vaughan/fix-oneof-concurrent-visit
Nov 5, 2026
Merged

Vaughan-Temporal merged 3 commits into
mainfrom
vaughan/fix-oneof-concurrent-visit

Conversation

@Vaughan-Temporal

Copy link
Copy Markdown
Contributor

What changed?
The payload visitor visits payloads held directly in a oneof through the field of the oneof wrapper already in the message, instead of through a local copy that is then assigned back. This covers DescribeNexusOperationExecutionResponse.result, PollNexusOperationExecutionResponse.result, and OnCompleteRequest.success.

Why?
With ConcurrencyLimit > 1, visitPayload returns before the visitor runs. Assigning the oneof from the copy then keeps the original payload, drops the visitor's result, and races with the visitor's later write to the copy.

How did you test it?

  • TestVisitPayloadsConcurrentOneofPayload covers all three fields. It fails on main, with the original payload returned and a data race reported under -race, and passes with this change.
  • make check test

Potential risks
The existing oneof wrapper is updated in place instead of replaced, matching how plain payload fields are visited. Sequential visiting is otherwise unchanged.

The payload visitor special-cases payloads held directly in a oneof
(DescribeNexusOperationExecutionResponse.result,
PollNexusOperationExecutionResponse.result, OnCompleteRequest.success) by
visiting a local copy and assigning a new oneof wrapper from it. With
ConcurrencyLimit > 1, visitPayload returns before the visitor runs, so the
wrapper is assigned from the original payload, the visitor's result is
dropped, and its later write to the copy races with that assignment.

Visit through the field of the wrapper already in the message instead, so a
deferred visitor writes into the message as it does for plain payload fields.
@Vaughan-Temporal
Vaughan-Temporal requested review from a team as code owners November 3, 2026 23:19
@CLAassistant

CLAassistant commented Nov 3, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment thread proxy/interceptor_test.go Outdated
Visitor: func(ctx *VisitPayloadsContext, p []*common.Payload) ([]*common.Payload, error) {
// Let the generated code move on before the result exists, as a
// visitor doing real work would.
time.Sleep(10 * time.Millisecond)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think there's some time bubble thing you can use to avoid sleeps / nondetermitism? Might be worth a property test

cf / get claude to https://github.com/flyingmutant/rapid

Hold the visitor on a channel until the walk returns, instead of sleeping,
so its result always arrives after the walk is done. The test then fails on
the copy-and-assign code every run, without relying on timing or -race.
{{if and (eq $type "*workflowservice.DescribeNexusOperationExecutionResponse") (eq . "Result")}}
if o.GetResult() != nil {
result := o.GetResult()
if err := visitPayload(ctx, options, o, concState, &result); err != nil { return err }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I didn't realise this doesn't block and just does background work.

😡 this is ... not good as a pattern imho

@Vaughan-Temporal
Vaughan-Temporal enabled auto-merge (squash) November 5, 2026 22:19
@Vaughan-Temporal
Vaughan-Temporal merged commit 84bb220 into main Nov 5, 2026
4 checks passed
@Vaughan-Temporal
Vaughan-Temporal deleted the vaughan/fix-oneof-concurrent-visit branch November 5, 2026 22:22
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.

4 participants