fix(migrations): use all configured GitHub tokens (#38841)

GitHub migrations accept multiple comma-separated OAuth tokens, but
clients with unknown rate data are never selected. After the first
client is used, every later token stays unknown and can never
participate in quota-aware selection.

Select each client with unknown rate data once before falling back to
the existing highest-remaining-rate choice. The regression test covers
initial probing of all clients and then selection by remaining quota.

Fixes https://github.com/go-gitea/gitea/issues/34342

Assisted-by: Codex:GPT-5

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Ban Yongping
2026-08-13 22:46:45 +08:00
committed by GitHub
parent d2be79a942
commit 68feaba2ed
2 changed files with 29 additions and 2 deletions
+6 -2
View File
@@ -152,12 +152,16 @@ func (g *GithubDownloaderV3) waitAndPickClient(ctx context.Context) {
var recentIdx int var recentIdx int
var maxRemaining int var maxRemaining int
for i := 0; i < len(g.clients); i++ { for i := 0; i < len(g.clients); i++ {
if g.rates[i] != nil && g.rates[i].Remaining > maxRemaining { if g.rates[i] == nil { // probe unknown clients once, else their rate never gets learned
g.curClientIdx = i
return
}
if g.rates[i].Remaining > maxRemaining {
maxRemaining = g.rates[i].Remaining maxRemaining = g.rates[i].Remaining
recentIdx = i recentIdx = i
} }
} }
g.curClientIdx = recentIdx // if no max remain, it will always pick the first client. g.curClientIdx = recentIdx
for g.rates[g.curClientIdx] != nil && g.rates[g.curClientIdx].Remaining <= GithubLimitRateRemaining { for g.rates[g.curClientIdx] != nil && g.rates[g.curClientIdx].Remaining <= GithubLimitRateRemaining {
timer := time.NewTimer(time.Until(g.rates[g.curClientIdx].Reset.Time)) timer := time.NewTimer(time.Until(g.rates[g.curClientIdx].Reset.Time))
+23
View File
@@ -14,6 +14,7 @@ import (
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
base "gitea.dev/modules/migration" base "gitea.dev/modules/migration"
"github.com/google/go-github/v89/github"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -473,3 +474,25 @@ func TestGithubMultiToken(t *testing.T) {
}) })
} }
} }
func TestGithubMultiTokenClientSelection(t *testing.T) {
downloader := &GithubDownloaderV3{
clients: make([]*github.Client, 3),
rates: make([]*github.Rate, 3),
}
downloader.waitAndPickClient(t.Context())
assert.Equal(t, 0, downloader.curClientIdx)
downloader.rates[0] = &github.Rate{Remaining: 100}
downloader.waitAndPickClient(t.Context())
assert.Equal(t, 1, downloader.curClientIdx)
downloader.rates[1] = &github.Rate{Remaining: 200}
downloader.waitAndPickClient(t.Context())
assert.Equal(t, 2, downloader.curClientIdx)
downloader.rates[2] = &github.Rate{Remaining: 50}
downloader.waitAndPickClient(t.Context())
assert.Equal(t, 1, downloader.curClientIdx)
}