@ 261101.7
current
package main

import (
	linked "container/list"
	"strconv"
	"strings"
	"sync"
	"syscall"
)

// defaultCache is the memory a server may spend on cached answers, eg
// which repo a directory lies in and the commits a page's history lists.
const defaultCache = 32 << 20

// cache keeps values up to a byte budget and drops the least recently
// used first. A budget of 0 keeps nothing.
type cache struct {
	mu    sync.Mutex
	max   int64
	used  int64
	order *linked.List // front is the most recently used
	items map[string]*linked.Element
}

type cacheItem struct {
	key  string
	val  any
	size int64
}

func newCache(max int64) *cache {
	return &cache{max: max, order: linked.New(), items: map[string]*linked.Element{}}
}

// gitCache holds git's answers for every tree on the server; serve sets
// its budget from --cache.
var gitCache = newCache(defaultCache)

func (c *cache) get(key string) (any, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	e, ok := c.items[key]
	if !ok {
		return nil, false
	}
	c.order.MoveToFront(e)
	return e.Value.(*cacheItem).val, true
}

// put stores val, whose size counts its key too. A value larger than the
// whole budget is not kept.
func (c *cache) put(key string, val any, size int64) {
	size += int64(len(key))
	c.mu.Lock()
	defer c.mu.Unlock()
	if e, ok := c.items[key]; ok {
		c.remove(e)
	}
	if size > c.max {
		return
	}
	c.items[key] = c.order.PushFront(&cacheItem{key, val, size})
	c.used += size
	for c.used > c.max {
		c.remove(c.order.Back())
	}
}

// reset empties c and gives it a new budget. A server resets the cache
// it shares with what is left of an earlier server in the same process,
// as in tests, rather than replace it under that server's goroutines.
func (c *cache) reset(max int64) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.max, c.used = max, 0
	c.order.Init()
	clear(c.items)
}

func (c *cache) drop(key string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if e, ok := c.items[key]; ok {
		c.remove(e)
	}
}

func (c *cache) remove(e *linked.Element) {
	it := c.order.Remove(e).(*cacheItem)
	delete(c.items, it.key)
	c.used -= it.size
}

// parseSize reads a byte count such as 32M, 512K, 1G or 0, in powers of
// 1024.
func parseSize(s string) (int64, error) {
	bad := &errno{syscall.EINVAL, "bad size " + s + ": want a number with K, M or G, eg 32M"}
	num, mult := s, int64(1)
	for suffix, m := range map[string]int64{"K": 1 << 10, "M": 1 << 20, "G": 1 << 30} {
		if strings.HasSuffix(strings.ToUpper(s), suffix) {
			num, mult = s[:len(s)-1], m
		}
	}
	n, err := strconv.ParseInt(num, 10, 64)
	if err != nil || n < 0 || n > (1<<40)/mult {
		return 0, bad
	}
	return n * mult, nil
}