Fix TestWorkflowStartConflict flaky test (#9776)

## What changed?
Fix flaky test
`TestUpdateWithStartSuite/TestWorkflowStartConflict/workflow_id_conflict_policy_fail:_use-existing`
handling both possible orderings of the race condition it tests.

## Why?

The test injects a hook (`UpdateWithStartInBetweenLockAndStart`) that
fires a concurrent `StartWorkflowExecution` between the
update-with-start lock acquisition and start attempt to simulate a race
condition. The test assumes the update always lands in a second
speculative WFT:

1. WFT #1 polled with no messages → completed (empty)
2. UWS retry creates speculative WFT #2 → polled with update →
accepted/completed

This assumption broke after the `parallelsuite` migration, which gives
each test its own isolated `testcore.NewEnv` running in parallel. With a
dedicated env, the retryable history client's immediate retry of the
Unavailable error races tightly with `RecordWorkflowTaskStarted`:

- Ordering A (retry wins the lock first): the update is admitted while
WFT #1 is still scheduled, so it attaches to WFT #1's messages when the
worker polls it. No WFT #2 is ever created.
- Ordering B (RecordWorkflowTaskStarted wins): WFT #1 starts and
completes with no messages, then the retry runs with no pending WFT,
creating speculative WFT #2 with the update.

The original two-poll design panicked in ordering A (index out of range
[0] with length 0 on task.Messages[0] in the empty-response first poll).
The initial single-poll fix panicked in ordering B for the same reason.
Neither ordering is
guaranteed, so the test must handle both.

## How did you test it?
- [ ] built
- [X] run locally and tested manually - tested with `-count=50` locally
- [ ] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks
None, test only
This commit is contained in:
Sean Kane
2026-04-02 16:48:04 +00:00
committed by GitHub
parent e0c2a9391a
commit c3e0e167e0
+25 -14
View File
@@ -5596,21 +5596,32 @@ func (s *UpdateWithStartSuite) TestWorkflowStartConflict() {
uwsCh := s.sendUpdateWithStart(env, startReq, updateReq)
_, err := env.TaskPoller().PollAndHandleWorkflowTask(env.Tv(),
func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) {
return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil
})
s.NoError(err)
// Two orderings are possible depending on lock timing:
// A) UWS retry acquires the workflow lock before RecordWorkflowTaskStarted: update is admitted
// while WFT #1 is still scheduled, so it attaches to WFT #1 messages when WFT #1 starts.
// B) RecordWorkflowTaskStarted wins the lock first: WFT #1 starts with no messages, completes,
// then UWS retry runs with no pending WFT and creates a speculative WFT #2 for the update.
updateHandled := false
for range 2 {
_, err := env.TaskPoller().PollAndHandleWorkflowTask(env.Tv(),
func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) {
if len(task.Messages) == 0 {
return &workflowservice.RespondWorkflowTaskCompletedRequest{}, nil
}
updateHandled = true
return &workflowservice.RespondWorkflowTaskCompletedRequest{
Messages: env.UpdateAcceptCompleteMessages(env.Tv(), task.Messages[0]),
}, nil
})
s.NoError(err)
if updateHandled {
break
}
}
s.True(updateHandled, "update not processed in either WFT")
_, err = env.TaskPoller().PollAndHandleWorkflowTask(env.Tv(),
func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) {
return &workflowservice.RespondWorkflowTaskCompletedRequest{
Messages: env.UpdateAcceptCompleteMessages(env.Tv(), task.Messages[0]),
}, nil
})
s.NoError(err)
<-uwsCh
uwsRes := <-uwsCh
s.NoError(uwsRes.err)
})
}