Skip to content
quarry Artifact [776731b888]
Login

Artifact [776731b888]

Artifact 776731b888bd39d05f3fd7117bb2b7d3cc66a0efad398754af70c9cd83fc70bf:


package api

import (
	"sort"
	"sync"
)

// Repos is the set of stores quarry serves.
//
// It is a type rather than the bare map it replaced because repositories
// can be added and removed while the server runs. Every op reads this on
// the request path, so a plain map mutated by an admin call would be a
// data race with no symptom until it corrupted one — the failure mode
// Go's map implementation deliberately makes loud, but only sometimes.
//
// The lock is held for the lookup and nothing else: an op takes a *Repo
// out and works with it unlocked, so a slow read never blocks a
// registration. That is safe because a *Repo is immutable once
// published — removal drops it from the map, and a request already
// holding one finishes against a store that is still open.
type Repos struct {
	mu sync.RWMutex
	m  map[string]*Repo
}

// NewRepos builds a registry, optionally seeded — the shape tests and
// the composition root both want.
func NewRepos(seed map[string]*Repo) *Repos {
	m := make(map[string]*Repo, len(seed))
	for k, v := range seed {
		m[k] = v
	}
	return &Repos{m: m}
}

func (r *Repos) Get(name string) (*Repo, bool) {
	if r == nil {
		return nil, false
	}
	r.mu.RLock()
	defer r.mu.RUnlock()
	repo, ok := r.m[name]
	return repo, ok
}

// Put registers a store, replacing any existing entry of that name.
func (r *Repos) Put(name string, repo *Repo) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.m == nil {
		r.m = map[string]*Repo{}
	}
	r.m[name] = repo
}

// Delete unregisters a store and returns what was removed, so the
// caller can release what it owns — an index handle and a pipeline
// outlive the map entry unless somebody closes them.
func (r *Repos) Delete(name string) (*Repo, bool) {
	r.mu.Lock()
	defer r.mu.Unlock()
	repo, ok := r.m[name]
	delete(r.m, name)
	return repo, ok
}

// Names lists registered stores in sorted order. Sorted because callers
// render it: map order would reshuffle the repository list on every
// page load.
func (r *Repos) Names() []string {
	if r == nil {
		return nil
	}
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]string, 0, len(r.m))
	for name := range r.m {
		out = append(out, name)
	}
	sort.Strings(out)
	return out
}

func (r *Repos) Len() int {
	if r == nil {
		return 0
	}
	r.mu.RLock()
	defer r.mu.RUnlock()
	return len(r.m)
}