Files
gitea/routers/api/actions/runner/interceptor_test.go
T
Lunny Xiaoandsilverwind 5129ad5439 perf(actions): cache successful runner token verification (#39934)
Every Actions runner RPC (`FetchTask` polling, `UpdateTask`,
`UpdateLog`, ...) goes through the `withRunner` interceptor, which
verifies the runner token with PBKDF2 (`auth_model.HashToken`, ~2 ms).
In a 60s CPU profile of a production instance with many runners, this
accounted for ~49% of all CPU samples.

- Cache successful verifications in a dedicated LRU of up to 10000
runners, keyed by the stored salt and hash and holding only a SHA-256
digest of the token. A hit costs ~100 ns.
- The runner is still loaded from the DB on every RPC and failures are
never cached, so deleted runners and wrong tokens never pass.
- The shared `auth_model.TokenCache` is not used because its default 20
entries miss on every poll once more runners poll in turn.

---------

Co-authored-by: silverwind <me@silverwind.io>
2026-10-23 03:59:11 +00:00

27 lines
866 B
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runner
import (
"crypto/sha256"
"testing"
actions_model "gitea.dev/models/actions"
auth_model "gitea.dev/models/auth"
"github.com/stretchr/testify/assert"
)
func TestVerifyRunnerToken(t *testing.T) {
verifiedRunnerTokens.Purge()
runner := &actions_model.ActionRunner{TokenSalt: "salt", TokenHash: auth_model.HashToken("token", "salt")}
assert.True(t, verifyRunnerToken(runner, "token"))
assert.True(t, verifyRunnerToken(runner, "token"))
assert.False(t, verifyRunnerToken(runner, "wrong"))
other := &actions_model.ActionRunner{TokenSalt: "salt", TokenHash: auth_model.HashToken("other", "salt")}
assert.False(t, verifyRunnerToken(other, "token"))
assert.Equal(t, [][sha256.Size]byte{sha256.Sum256([]byte("token"))}, verifiedRunnerTokens.Values())
}