Skip to content
quarry Artifact [a627e86393]
Login

Artifact [a627e86393]

Artifact a627e86393c5925226a5245f4e06f293f7d3f46bc474165cf2e4c88402e00922:


package obs

import (
	"context"
	"strconv"
	"sync/atomic"
)

type ctxRequest struct{}

var requestSeq atomic.Uint64

// WithRequest tags a context with a fresh request id and returns both.
//
// The id is what lets a store call name the page that caused it. Before
// it, the subprocess panel and the render panel were two independent
// lists: "GET /search took 1132ms" and "zit artifact ran 499 times"
// could not be connected even when one caused the other, so the page
// could find a slow route and never say why.
//
// A counter rather than a UUID: this identifies a request WITHIN one
// process's memory for as long as the rings hold it, and nothing
// compares ids across processes. A cheap monotonic value is the whole
// requirement.
func WithRequest(ctx context.Context) (context.Context, string) {
	id := strconv.FormatUint(requestSeq.Add(1), 36)
	return context.WithValue(ctx, ctxRequest{}, id), id
}

// RequestID is the tag on this context, empty when there is none —
// which is the ordinary case for work with no request behind it: ingest
// rounds, warm passes, the doc indexer. Callers record the empty string
// rather than guessing, because "background" is a true answer and a
// wrong attribution is not.
func RequestID(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	id, _ := ctx.Value(ctxRequest{}).(string)
	return id
}

// ctxReason tags a context with WHY the work beneath it is happening,
// for calls that have no request behind them.
type ctxReason struct{}

// The reasons quarry names. A closed set on purpose: an operator scans
// this column, and a free-text field would grow one spelling per call
// site until the column could not be grouped.
const (
	ReasonSync     = "sync"      // pulling from an upstream mirror
	ReasonIngest   = "ingest"    // folding new artifacts into the index
	ReasonWarm     = "warm"      // building a cache before anybody asks
	ReasonDocIndex = "doc index" // the documentation sweep
	ReasonCI       = "ci"        // cloning or driving a CI run
)

// WithReason names the background activity a subprocess serves. The
// request path needs none of this — RequestID already says which render
// caused the call, and the route is a better answer than any word here.
// This is for the work that answers to nobody: without it every ingest
// round, warm pass and doc sweep records the same empty string and the
// panel can only say "background".
func WithReason(ctx context.Context, reason string) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, ctxReason{}, reason)
}

// Reason is why this context's work is happening, empty when a request
// is behind it or when nobody said.
func Reason(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	r, _ := ctx.Value(ctxReason{}).(string)
	return r
}