mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 07:39:19 -05:00
Each request for a General registry tarball whose hash differed from the loaded name map started a goroutine that downloaded the whole registry from upstream again, with no limit on concurrent loads and no deadline. Alternating between two valid snapshots made every request cost a full download and parse, and left the map at whichever snapshot came last, older ones included. The refresh now only loads the hash /registries currently lists, read through the metadata cache, runs one load at a time and skips while one is running, and stops after five minutes. Registry.toml is read up to 64 MB.
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func juliaRegistryTarball(t *testing.T, packageName string) []byte {
|
|
t.Helper()
|
|
body := "name = \"General\"\n[packages]\n295af30f-e4ad-537b-8983-00126c2a3abe = { name = \"" + packageName + "\", path = \"R/X\" }\n"
|
|
var buf bytes.Buffer
|
|
gw := gzip.NewWriter(&buf)
|
|
tw := tar.NewWriter(gw)
|
|
if err := tw.WriteHeader(&tar.Header{Name: "Registry.toml", Mode: 0o644, Size: int64(len(body))}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, _ = tw.Write([]byte(body))
|
|
_ = tw.Close()
|
|
_ = gw.Close()
|
|
return buf.Bytes()
|
|
}
|
|
|
|
type fakeJuliaUpstream struct {
|
|
mu sync.Mutex
|
|
advertised string
|
|
tarballs map[string][]byte
|
|
downloads map[string]int
|
|
}
|
|
|
|
func (u *fakeJuliaUpstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
u.mu.Lock()
|
|
defer u.mu.Unlock()
|
|
if r.URL.Path == "/registries" {
|
|
_, _ = w.Write([]byte("/registry/" + juliaGeneralRegistryUUID + "/" + u.advertised + "\n"))
|
|
return
|
|
}
|
|
hash := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
|
|
body, ok := u.tarballs[hash]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
u.downloads[hash]++
|
|
_, _ = w.Write(body)
|
|
}
|
|
|
|
func (u *fakeJuliaUpstream) count(hash string) int {
|
|
u.mu.Lock()
|
|
defer u.mu.Unlock()
|
|
return u.downloads[hash]
|
|
}
|
|
|
|
func TestJuliaRefreshOnlyLoadsAdvertisedRegistry(t *testing.T) {
|
|
oldHash, newHash := strings.Repeat("a", 40), strings.Repeat("b", 40)
|
|
up := &fakeJuliaUpstream{
|
|
advertised: newHash,
|
|
tarballs: map[string][]byte{
|
|
oldHash: juliaRegistryTarball(t, "OldName"),
|
|
newHash: juliaRegistryTarball(t, "NewName"),
|
|
},
|
|
downloads: map[string]int{},
|
|
}
|
|
server := httptest.NewServer(up)
|
|
defer server.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
h := NewJuliaHandlerWithUpstream(proxy, server.URL)
|
|
ctx := context.Background()
|
|
|
|
// An older snapshot neither replaces the map nor is downloaded for it.
|
|
for range 5 {
|
|
h.refreshNamesFromRegistry(ctx, juliaGeneralRegistryUUID, oldHash)
|
|
}
|
|
if n := up.count(oldHash); n != 0 {
|
|
t.Fatalf("old registry downloaded %d times for the name map", n)
|
|
}
|
|
|
|
h.refreshNamesFromRegistry(ctx, juliaGeneralRegistryUUID, newHash)
|
|
if got := h.resolveName("295af30f-e4ad-537b-8983-00126c2a3abe"); got != "NewName" {
|
|
t.Fatalf("name = %q, want NewName", got)
|
|
}
|
|
|
|
// The loaded hash is not fetched again, and an old one doesn't roll back.
|
|
h.refreshNamesFromRegistry(ctx, juliaGeneralRegistryUUID, newHash)
|
|
h.refreshNamesFromRegistry(ctx, juliaGeneralRegistryUUID, oldHash)
|
|
if n := up.count(newHash); n != 1 {
|
|
t.Errorf("current registry downloaded %d times, want 1", n)
|
|
}
|
|
if got := h.resolveName("295af30f-e4ad-537b-8983-00126c2a3abe"); got != "NewName" {
|
|
t.Errorf("name rolled back to %q", got)
|
|
}
|
|
}
|
|
|
|
func TestJuliaRefreshSkipsWhileLoading(t *testing.T) {
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
h := NewJuliaHandlerWithUpstream(proxy, "http://127.0.0.1:1")
|
|
h.loadMu.Lock()
|
|
done := make(chan struct{})
|
|
go func() {
|
|
h.refreshNamesFromRegistry(context.Background(), juliaGeneralRegistryUUID, strings.Repeat("c", 40))
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("refresh waited for a running load instead of skipping")
|
|
}
|
|
h.loadMu.Unlock()
|
|
}
|
|
|
|
func TestJuliaExtractRegistryNamesLimitsRegistryToml(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
gw := gzip.NewWriter(&buf)
|
|
tw := tar.NewWriter(gw)
|
|
size := int64(maxJuliaRegistryTomlSize + 1)
|
|
if err := tw.WriteHeader(&tar.Header{Name: "Registry.toml", Mode: 0o644, Size: size}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write(bytes.Repeat([]byte("#"), int(size))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = tw.Close()
|
|
_ = gw.Close()
|
|
if _, err := extractRegistryNames(&buf); err == nil {
|
|
t.Fatal("oversized Registry.toml was accepted")
|
|
}
|
|
}
|