Skip to content

Advance pri reader ack level past expired tasks - #12473

Open
steve-weiland wants to merge 1 commit into
temporalio:mainfrom
steve-weiland:fix/pri-reader-expired-ack
Open

steve-weiland wants to merge 1 commit into
temporalio:mainfrom
steve-weiland:fix/pri-reader-expired-ack

Conversation

@steve-weiland

Copy link
Copy Markdown

What changed?

priTaskReader.setReadLevelAfterGap now advances the ack level to the new read level whenever outstandingTasks is empty, instead of only when ackLevel == readLevel.

Tests:

  • Added a getTasksHook to testTaskManager so tests can control read timing.
  • Added a holdReadsUntilAcked option to testSkipExpiredTasks and a new case, TestSkipExpiredTasks_ValidThenExpired_AckedBeforeExpiredRead, which forces the reader to ack the valid tasks before it reads the trailing expired ones.
  • Un-skipped TestSkipExpiredTasks_AllExpired for the pri reader.

Why?

Fixes #12472.

processTaskBatch drops expired tasks as it reads them, so they never enter outstandingTasks. Suppose the last outstanding task is acked before the reader reaches a trailing run of expired tasks. Then ackTaskLocked doesn't jump the ack level, because the reader isn't drained yet. Later, setReadLevelAfterGap sees ackLevel != readLevel and does nothing. The ack level stays stuck until another task is written and acked. Meanwhile the expired tasks aren't GC'd, the backlog count isn't reset, and a reload re-reads from the stale ack level. This is also what makes TestSkipExpiredTasks_ValidThenExpired flaky under load.

An empty outstandingTasks means every task up to readLevel was either acked or dropped as expired, so it's safe to ack up to the new read level. That covers the existing idle-queue case and the expired-tasks case.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

On main, the new test and the un-skipped TestSkipExpiredTasks_AllExpired fail 5/5 for the pri reader, and both pass with this change. TestSkipExpiredTasks* and TestExpiredTasksOnRead* (Classic/Pri/Fair) pass ×20 with -race, and go test -race ./service/matching/... passes.

Potential risks

The ack level can now advance in setReadLevelAfterGap in cases where it previously didn't. That relies on every task at or below readLevel being either in outstandingTasks or dropped as expired when read, which is how processTaskBatch works today. The stale-read guard (newReadLevel < tr.readLevel) is unchanged.

## What changed?
priTaskReader.setReadLevelAfterGap now advances the ack level to the
new read level whenever outstandingTasks is empty, instead of only when
ackLevel == readLevel.

Added a getTasksHook to testTaskManager, a holdReadsUntilAcked option
to testSkipExpiredTasks, and a new case,
TestSkipExpiredTasks_ValidThenExpired_AckedBeforeExpiredRead.
Un-skipped TestSkipExpiredTasks_AllExpired for the pri reader.

## Why?
Fixes temporalio#12472.

processTaskBatch drops expired tasks when it reads them, so they never
enter outstandingTasks. If the last outstanding task was acked before
the reader read a trailing run of expired tasks, setReadLevelAfterGap
saw ackLevel != readLevel and left the ack level stuck at the last
acked task. The expired tasks were not GC'd and the backlog count was
not reset until another task was written and acked. This also made
TestSkipExpiredTasks_ValidThenExpired flaky under load.

An empty outstandingTasks means every task up to readLevel was acked
or dropped as expired, so acking up to the new read level is safe.

## How did you test it?
- [x] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks
The ack level can now advance in setReadLevelAfterGap in cases where
it previously did not. This relies on every task at or below readLevel
being in outstandingTasks or dropped as expired when read, which is
how processTaskBatch works today.
@steve-weiland
steve-weiland requested a review from a team as a code owner October 8, 2026 16:19
@CLAassistant

CLAassistant commented Oct 8, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

This branch has not been deployed

No deployments
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.

priTaskReader ack level stalls when trailing tasks are expired (causes flaky TestSkipExpiredTasks_ValidThenExpired)

2 participants