Skip to content

fix(github-runner): bound etag and per-repo caches#7698

Merged
wozniakjan merged 4 commits into
kedacore:mainfrom
mateenali66:fix/7685-github-runner-cache-bounds
May 25, 2026
Merged

fix(github-runner): bound etag and per-repo caches#7698
wozniakjan merged 4 commits into
kedacore:mainfrom
mateenali66:fix/7685-github-runner-cache-bounds

Conversation

@mateenali66

Copy link
Copy Markdown
Contributor

the etags map is keyed by the request URL, and workflow run job URLs include the run ID, so the cache grows once per workflow run for the lifetime of the operator pod. previousJobs and previousWfrs are keyed by repo names returned from the API, so a misbehaving or malicious server that rotates repo names accumulates entries the same way.

added pruneCaches that drops previousWfrs entries for repos no longer in the current list and caps all three maps at 5000 entries. called from GetWorkflowQueueLength when enableEtags is on, matching the gate that populates the maps in the first place.

Fixes #7685

@mateenali66 mateenali66 requested a review from a team as a code owner April 28, 2026 16:12
@snyk-io

snyk-io Bot commented Apr 28, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

Copy link
Copy Markdown

Thank you for your contribution! 🙏

Please understand that we will do our best to review your PR and give you feedback as soon as possible, but please bear with us if it takes a little longer as expected.

While you are waiting, make sure to:

  • Add an entry in our changelog in alphabetical order and link related issue
  • Update the documentation, if needed
  • Add unit & e2e tests for your changes
  • GitHub checks are passing
  • Is the DCO check failing? Here is how you can fix DCO issues

Once the initial tests are successful, a KEDA member will ensure that the e2e tests are run. Once the e2e tests have been successfully completed, the PR may be merged at a later date. Please be patient.

Learn more about our contribution guide.

@keda-automation keda-automation requested a review from a team April 28, 2026 16:14
@JorTurFer JorTurFer requested a review from Copilot May 3, 2026 17:52

Copilot AI left a comment

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.

Pull request overview

This PR mitigates unbounded memory growth in the GitHub Runner scaler when enableEtags is enabled by pruning and bounding internal per-URL/per-repo caches.

Changes:

  • Add pruneCaches + evictExcess to cap cache maps (etags/previousJobs/previousWfrs) and drop previousWfrs entries for repos no longer returned.
  • Call cache pruning from GetWorkflowQueueLength when EnableEtags is on.
  • Add unit tests for cache pruning and update the changelog entry.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
pkg/scalers/github_runner_scaler.go Introduces cache size cap + pruning logic; invoked during queue-length polling when ETags are enabled.
pkg/scalers/github_runner_scaler_test.go Adds tests covering pruning of absent repos and cache size bounding.
CHANGELOG.md Documents the fix for bounding ETag/per-repo caches (Issue #7685).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/scalers/github_runner_scaler_test.go
Comment thread pkg/scalers/github_runner_scaler.go
Comment thread pkg/scalers/github_runner_scaler.go Outdated
@rickbrouwer rickbrouwer added the merge-conflict This PR has a merge conflict label May 3, 2026
When enableEtags is on, three caches in the GitHub Runner scaler grow
indefinitely for the lifetime of the operator pod:

- etags is keyed by apiURL, and workflow run job URLs include the run ID,
  so each new workflow run leaks an entry.
- previousJobs is keyed by the workflow run repository name, so a
  misbehaving or malicious API returning rotating repo names accumulates
  entries.
- previousWfrs is keyed by repo from getRepositories, with the same
  rotation risk.

Add pruneCaches that drops previousWfrs entries for repos no longer in
the current list and caps all three maps at a fixed size. Called once
per scaling cycle from GetWorkflowQueueLength when enableEtags is on,
matching the gate that populates the maps.

Signed-off-by: Mateen Anjum <[email protected]>
addresses review feedback. when pruneCaches evicts a previousJobs or
previousWfrs entry but its etag stays, the next request returns 304 and
the scaler errored with "previous ... is not set". drop the stale etag
and retry without If-None-Match so the response repopulates the cache.

also corrected the comment on githubScalerMaxCacheEntries to mention
previousWfrs alongside etags and previousJobs.

Signed-off-by: Mateen Anjum <[email protected]>
@mateenali66 mateenali66 force-pushed the fix/7685-github-runner-cache-bounds branch from 0b70fe5 to 3a817e9 Compare May 3, 2026 18:27
@rickbrouwer rickbrouwer removed the merge-conflict This PR has a merge conflict label May 3, 2026
@rickbrouwer rickbrouwer requested a review from Copilot May 3, 2026 18:43

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/scalers/github_runner_scaler_test.go
Comment thread pkg/scalers/github_runner_scaler_test.go
addresses race-detector feedback. the httptest handler goroutine writes
saw* flags that the test goroutine reads, with no explicit
happens-before edge, so make test-race could flag it. swap plain bools
for go.uber.org/atomic.Bool (KEDA's depguard rejects sync/atomic).

Signed-off-by: Mateen Anjum <[email protected]>
reverts the go.mod change. the go.uber.org/atomic indirect-to-direct
promotion tripped Snyk's manifest-change scan (0 vulnerabilities, but
the policy gate marked the check incomplete). plain sync.Mutex around
the saw* flags satisfies the race detector without touching go.mod.

Signed-off-by: Mateen Anjum <[email protected]>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@rickbrouwer

rickbrouwer commented May 6, 2026

Copy link
Copy Markdown
Member

/run-e2e github*
Update: You can check the progress here

@rickbrouwer rickbrouwer added Awaiting/2nd-approval This PR needs one more approval review required:keda-v2.20 This is absolutely mandatory to bring along labels May 6, 2026

@JorTurFer JorTurFer 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.

Looks really nice!
Does it make sense to expose the githubScalerMaxCacheEntries parameter to bring the option to users?

@wozniakjan wozniakjan 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.

Does it make sense to expose the githubScalerMaxCacheEntries parameter to bring the option to users?

for sure can be added, alternatively we can also consider refactoring this to use https://github.com/hashicorp/golang-lru.

Given this PR already has two approvals, it looks in a good shape addressing the unbounded growth, I propose we merge for 2.20 release and iteratively improve if desired with follow-up PRs.

@wozniakjan wozniakjan merged commit 31d7ac7 into kedacore:main May 25, 2026
30 checks passed
@rickbrouwer rickbrouwer removed the Awaiting/2nd-approval This PR needs one more approval review label May 25, 2026
shcherbak pushed a commit to shcherbak/keda that referenced this pull request Jun 3, 2026
* fix(github-runner): bound etag and per-repo caches

When enableEtags is on, three caches in the GitHub Runner scaler grow
indefinitely for the lifetime of the operator pod:

- etags is keyed by apiURL, and workflow run job URLs include the run ID,
  so each new workflow run leaks an entry.
- previousJobs is keyed by the workflow run repository name, so a
  misbehaving or malicious API returning rotating repo names accumulates
  entries.
- previousWfrs is keyed by repo from getRepositories, with the same
  rotation risk.

Add pruneCaches that drops previousWfrs entries for repos no longer in
the current list and caps all three maps at a fixed size. Called once
per scaling cycle from GetWorkflowQueueLength when enableEtags is on,
matching the gate that populates the maps.

Signed-off-by: Mateen Anjum <[email protected]>

* fix(github-runner): treat 304 with missing previous as cache miss

addresses review feedback. when pruneCaches evicts a previousJobs or
previousWfrs entry but its etag stays, the next request returns 304 and
the scaler errored with "previous ... is not set". drop the stale etag
and retry without If-None-Match so the response repopulates the cache.

also corrected the comment on githubScalerMaxCacheEntries to mention
previousWfrs alongside etags and previousJobs.

Signed-off-by: Mateen Anjum <[email protected]>

* test(github-runner): use atomic.Bool in stale-etag retry tests

addresses race-detector feedback. the httptest handler goroutine writes
saw* flags that the test goroutine reads, with no explicit
happens-before edge, so make test-race could flag it. swap plain bools
for go.uber.org/atomic.Bool (KEDA's depguard rejects sync/atomic).

Signed-off-by: Mateen Anjum <[email protected]>

* test(github-runner): use sync.Mutex instead of atomic.Bool

reverts the go.mod change. the go.uber.org/atomic indirect-to-direct
promotion tripped Snyk's manifest-change scan (0 vulnerabilities, but
the policy gate marked the check incomplete). plain sync.Mutex around
the saw* flags satisfies the race detector without touching go.mod.

Signed-off-by: Mateen Anjum <[email protected]>

---------

Signed-off-by: Mateen Anjum <[email protected]>
Signed-off-by: Yurii Shcherbak <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

required:keda-v2.20 This is absolutely mandatory to bring along

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GitHub Runner Scaler ETag State Accumulation

5 participants