Skip to content

scheduler: prevent extra reschedules from invalid states - #28445

Merged
tgross merged 1 commit into
mainfrom
b-extraneous-reschedules
Aug 21, 2026
Merged

tgross merged 1 commit into
mainfrom
b-extraneous-reschedules

Conversation

@tgross

@tgross tgross commented Aug 20, 2026 •

Copy link
Copy Markdown
Member

While investigating a customer incident, we uncovered several states within the scheduler that should be impossible inputs, but that the scheduler handled badly and created potentially many extra allocations on successive evals. Note that these issues don't appear to be reachable bugs in current versions of Nomad.

Place guardrails around these states:

  • Ensure that the updateByReschedulable only allows force-rescheduling for client-terminal allocations. This is currently guarded in the RPC handler but not in Raft. We're currently protected from write skew by making either the client view or server view canonical for a given field.

  • Ensure that only valid AllocState entries are added to a stopped allocation, to ensure there's no code path where we can reschedule an unknown allocation multiple times by having it no longer show as unknown when we check for reconnect.

Ref: https://hashicorp.atlassian.net/browse/NMD-1681

Contributor Checklist

  • Changelog Entry If this PR changes user-facing behavior, please generate and add a
    changelog entry using the make cl command.
  • Testing Please add tests to cover any new functionality or to demonstrate bug fixes and
    ensure regressions will be caught.
  • Documentation n/a
  • LLM Usage n/a

Reviewer Checklist

  • Backport Labels Please add the correct backport labels as described by the internal
    backporting document.
  • Commit Type Ensure the correct merge method is selected which should be "squash and merge"
    in the majority of situations. The main exceptions are long-lived feature branches or merges where
    history should be preserved.
  • Enterprise PRs If this is an enterprise only PR, please add any required changelog entry
    within the public repository.
  • If a change needs to be reverted, we will roll out an update to the code within 7 days.

Changes to Security Controls

Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.

While investigating a customer incident, we uncovered several states within the
scheduler that should be impossible inputs, but that the scheduler handled badly
and created potentially many extra allocations on successive evals. Note that
these issues don't appear to be reachable bugs in current versions of Nomad.

Place guardrails around these states:

* Ensure that the `updateByReschedulable` only allows force-rescheduling for
  client-terminal allocations. This is currently guarded in the RPC handler but
  not in Raft. We're currently protected from write skew by making either the
  client view or server view canonical for a given field.

* Ensure that only valid `AllocState` entries are added to a stopped
  allocation, to ensure there's no code path where we can reschedule an unknown
  allocation multiple times by having it no longer show as unknown when we check
  for reconnect.

Ref: https://hashicorp.atlassian.net/browse/NMD-1681
Comment thread .changelog/28445.txt
Comment on lines +1 to +3
```release-note:improvement
scheduler: Improved robustness of reschedule logic for invalid states
```

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note to reviewers: I'm only calling this "improvement" because I don't have a concrete reachable user-facing bug to describe. It's a bug for sure internally, so that's why I'm marking it for backports. 🤷

tgross added a commit that referenced this pull request Aug 21, 2026
In #19937 we attempted to fix a potential state store corruption by copying
allocations we were upserting before canonicalizing them. But while I was
working on #28445 I noticed that we weren't actually updating the slice that
we'd then insert into the state store, which means we're throwing away the
canonicalized version.

This was obscured by several state store tests that were asserting that the
allocation being passed to `UpsertPlanResults` was equal to the one we return
from `AllocByID`. This is obviously incorrect because we modify new allocations
coming in from the plan quite a bit. Rework these tests to clarify their intent
and reduce duplication between tests.

Ref: #19937
Ref: #28445
@tgross
tgross marked this pull request as ready for review August 21, 2026 01:24
@tgross
tgross requested review from a team as code owners August 21, 2026 01:24

@jrasell jrasell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks @tgross!

@tgross
tgross merged commit aa026cc into main Aug 21, 2026
58 of 59 checks passed
@tgross
tgross deleted the b-extraneous-reschedules branch August 21, 2026 12:34
tgross added a commit that referenced this pull request Aug 31, 2026
In #19937 we attempted to fix a potential state store corruption by copying
allocations we were upserting before canonicalizing them. But while I was
working on #28445 I noticed that we weren't actually updating the slice that
we'd then insert into the state store, which means we're throwing away the
canonicalized version.

This was obscured by several state store tests that were asserting that the
allocation being passed to `UpsertPlanResults` was equal to the one we return
from `AllocByID`. This is obviously incorrect because we modify new allocations
coming in from the plan quite a bit. Rework these tests to clarify their intent
and reduce duplication between tests.

Ref: #19937
Ref: #28445
tgross added a commit that referenced this pull request Sep 1, 2026
In #19937 we attempted to fix a potential state store corruption by copying
allocations we were upserting before canonicalizing them. But while I was
working on #28445 I noticed that we weren't actually updating the slice that
we'd then insert into the state store, which means we're throwing away the
canonicalized version.

As it turns out, the original reasoning behind #19937 was faulty and we
shouldn't have been copying at all. These allocations are always coming from the
`Plan.Submit` RPC and not from the state store. And we send the mutated version
back in the response to the RPC.

This means that we haven't been upgrading allocations or canonicalizing them on
plan submit, but in practice it also hasn't mattered. Canonicalizing populates
missing resources from deprecated resources, for the case where we have pre-0.11
schedulers running. And it recursively canonicalizes the `Job`, which comes over
the wire as nil in the plan and gets populated on the leader anyways. So fixing
this has no production impact today but keeps our behavior correct the next time
we need to upgrade allocations.

Ref: #19937
Ref: #28445
Ref: https://hashicorp.atlassian.net/browse/NMD-1681
tgross added a commit that referenced this pull request Sep 2, 2026
In #19937 we attempted to fix a potential state store corruption by copying
allocations we were upserting before canonicalizing them. But while I was
working on #28445 I noticed that we weren't actually updating the slice that
we'd then insert into the state store, which means we're throwing away the
canonicalized version.

As it turns out, the original reasoning behind #19937 was faulty and we
shouldn't have been copying at all. These allocations are always coming from the
`Plan.Submit` RPC and not from the state store. And we send the mutated version
back in the response to the RPC.

This means that we haven't been upgrading allocations or canonicalizing them on
plan submit, but in practice it also hasn't mattered. Canonicalizing populates
missing resources from deprecated resources, for the case where we have pre-0.11
schedulers running. And it recursively canonicalizes the `Job`, which comes over
the wire as nil in the plan and gets populated on the leader anyways. So fixing
this has no production impact today but keeps our behavior correct the next time
we need to upgrade allocations.

Ref: #19937
Ref: #28445
Ref: https://hashicorp.atlassian.net/browse/NMD-1681
schmichael pushed a commit to vercel/nomad that referenced this pull request Sep 25, 2026
…8445)

While investigating a customer incident, we uncovered several states within the
scheduler that should be impossible inputs, but that the scheduler handled badly
and created potentially many extra allocations on successive evals. Note that
these issues don't appear to be reachable bugs in current versions of Nomad.

Place guardrails around these states:

* Ensure that the `updateByReschedulable` only allows force-rescheduling for
  client-terminal allocations. This is currently guarded in the RPC handler but
  not in Raft. We're currently protected from write skew by making either the
  client view or server view canonical for a given field.

* Ensure that only valid `AllocState` entries are added to a stopped
  allocation, to ensure there's no code path where we can reschedule an unknown
  allocation multiple times by having it no longer show as unknown when we check
  for reconnect.

Ref: https://hashicorp.atlassian.net/browse/NMD-1681
schmichael pushed a commit to vercel/nomad that referenced this pull request Sep 25, 2026
…rp#28447)

In hashicorp#19937 we attempted to fix a potential state store corruption by copying
allocations we were upserting before canonicalizing them. But while I was
working on hashicorp#28445 I noticed that we weren't actually updating the slice that
we'd then insert into the state store, which means we're throwing away the
canonicalized version.

As it turns out, the original reasoning behind hashicorp#19937 was faulty and we
shouldn't have been copying at all. These allocations are always coming from the
`Plan.Submit` RPC and not from the state store. And we send the mutated version
back in the response to the RPC.

This means that we haven't been upgrading allocations or canonicalizing them on
plan submit, but in practice it also hasn't mattered. Canonicalizing populates
missing resources from deprecated resources, for the case where we have pre-0.11
schedulers running. And it recursively canonicalizes the `Job`, which comes over
the wire as nil in the plan and gets populated on the leader anyways. So fixing
this has no production impact today but keeps our behavior correct the next time
we need to upgrade allocations.

Ref: hashicorp#19937
Ref: hashicorp#28445
Ref: https://hashicorp.atlassian.net/browse/NMD-1681

This branch was successfully deployed

1 active deployment
Preview — 0685d191 Deployed Aug 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport/ent/1.10.x+ent backport to 1.10.x+ent release line backport/ent/1.11.x+ent backport to 1.11.x+ent release line backport/2.0.x backport to 2.0.x release line theme/disconnect theme/restart/reschedule theme/scheduling type/bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants