background

package
v0.2.0-rc.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 24 Imported by: 0

README

background

Keeps local GitHub metadata fresh while the daemon runs: discovery polls of every picked repository, then reads of the saved reviews' evidence. It never queues or starts generation. The daemon hands each successful snapshot to its automatic scheduler through Discovered.

A pass

Run makes a pass at least every 30 seconds, or sooner when a repository is due sooner, as one settling after a new head is. A pass (Sync):

  1. Probes (discovery.Probe), at most once per interval (a minute). A repository whose open pull requests changed since its last poll polls now (PollNow), past discovery's backoff. A pull request opened or pushed from anywhere therefore shows within about a minute. A probe that succeeds after one that failed makes every repository whose poll failed meanwhile poll at once.
  2. Polls every due repository, pollConcurrency (3) at a time. Each poll stops once GitHub has not answered it for pollTimeout (45 s), so a busy repository's sweep may take minutes. Discovery keeps what it read when time runs out. The polls share the credential's GitHub budget, which discovery checks before every request. Their results are handled one at a time: Discovered, the repository's state, and the reads each poll makes due.
  3. Reads the due saved pull requests within refreshBudget: those GitHub reported changed first, then the oldest. A rate-limited read pauses reads on that host (Paused, Limited), which the PR page's facts share. A read that finds nothing new leaves the saved document unwritten, its evidence_refreshed_at included (store.Store.RefreshCurrent), and still counts as the pull request's last read, in memory. See Which pull requests are read.
  4. Reconciles retained review refs (Retain) once a day per common git directory.

Which pull requests are read

Only pull requests with a saved review in a clone's store (Saved) are read. Discovery already carries the list fields of the other open ones.

  • Open. Read when discovery shows a change since the read was last due: a newer updatedAt (a push, edit, review or comment), another head, another check rollup (a check finishing moves no update time) or another count of unresolved threads (neither does resolving one). The update time is compared with GitHub's own time at the last read, so a comment made while that read ran is read again. Otherwise read once its evidence is openReadAge (2 hours) old.
  • Closed or merged. A pull request that is not among the open ones is read once more, to record its final state, while its saved document still says open or draft. Once the document records merged or closed, it is retired: the background refresh never reads it again. A document keeps a closed draft as draft, so its state cannot show that the final read was published: a closed draft is retired once a final read in this process succeeded, and each new process reads it once more. A read that failed after GitHub answered does not retire it. If it is reopened, discovery lists it open again and the rules above apply.
  • Failures. A failed read is tried again after the interval, doubling. After maxReadFailures (3) failures in a row, it waits for a change: an open pull request is read again when its updatedAt or head moves, or after stoppedRetry (a day); a closed one only in the next daemon process. Reads that failed while the probes failed (offline) are tried again once GitHub answers. A conflict with a generation that published first is no failure.
  • Refresh asked for. The app's refresh of a repository (Asked, wired to POST /api/prs/refresh) reads its open saved reviews once more, and its failed reads, stopped or not. Retired reviews stay retired.
  • Restart. Reads live in memory. In a new process, or under a new identity, the reads that fall due by age or by check rollup, and final reads, are spread over restartSpread (30 minutes) by a hash of the pull request, so a restart does not read every saved review at once. One GitHub reports updated or pushed is read at once. Until this process has read a pull request, its update time is compared with the last observation's read, or with when its generation began.

Budget and time limits. The reads make at most refreshRequestsPerHour (1,000) GraphQL requests in any hour, each at least one point of the token's usual 5,000; the count survives an identity change. They also wait while discovery last read fewer than refreshReserve (1,000) GraphQL points left in the current window, or met a secondary limit (Budget, discovery.Service.GraphQLBudget), leaving those to discovery and generation. Each GitHub request of a read may take readRequestTimeout (30 s), so a huge pull request takes as many requests as it needs while a stalled one fails. readLimit (10 minutes) only stops a read that never ends; refreshBudget bounds the reads a pass starts, not one under way. Neither budget sets the repository's error.

RefreshNow (hook events) reads what it is asked to, retired, stopped or past the budget, honouring only the host's rate pause. Its requests count toward the hour's budget, and a successful read clears the pull request's failures.

A repository's state (GET /api/background) records its last success, its next refresh and the error of the last pass: discovery_unavailable, discovery_timeout (the poll ran out of time), rate_limited, metadata_unavailable, store_unreadable or cache_unavailable. A failed poll backs off exponentially, for at most 15 minutes. A settling poll is due when discovery says.

The daemon routes pass callbacks through the index watcher's content check. An unchanged pass publishes no index event at its poll or read boundary. Repository error transitions also participate in that check, so the app's refresh health updates even when the pull request rows did not change. Routine success and next-refresh times, and an unchanged discovery poll's metadata fetch timestamp, alone do not announce an index change. API readers still get the actual timestamps; applied observation times remain in the comparison.

Tests

just oss-go test -race ./internal/background

Documentation

Overview

Package background keeps local GitHub metadata fresh without a generation queue, model backend, checkout export or hosted baum service. It hands discovery snapshots to Service.Discovered and nothing else: automatic generation is the daemon's separate scheduler.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Local

type Local struct {
	Client assemble.GitHub
	Config config.Config
}

func (Local) Refresh

func (l Local) Refresh(ctx context.Context, clone repos.Clone, number int) (result discovery.Observation, returnedErr error)

type Problem

type Problem struct {
	PR     int    `json:"pr"`
	Reason string `json:"reason"`
}

type Registry

type Registry interface {
	Repositories(context.Context) ([]repos.Repository, error)
}

type Retention

type Retention struct {
	Log *slog.Logger
}

Retention reconciles the refs that keep saved review versions' commits in a clone (reviewrefs.Reconcile, within reviewrefs.DefaultLimits). A clone that cannot be opened or reconciled is skipped with a log line.

func (Retention) Retain

func (r Retention) Retain(ctx context.Context, clone repos.Clone) (bool, error)

type SavedReview added in v0.2.0

type SavedReview struct {
	Number int
	// State is the saved document's pr_state: open, draft, closed or merged.
	State string
	// Head is the head the latest version reviewed.
	Head string
	// Generated is when the latest version was generated, and Started when
	// its generation began to read the evidence; zero when unknown.
	Generated, Started time.Time
}

SavedReview is a pull request with a saved review in a clone's store, as its latest version records it.

func Saved

func Saved(ctx context.Context, clone repos.Clone) ([]SavedReview, error)

Saved lists the clone's saved reviews of its repository, from the store's summaries: no document is read.

type Service

type Service struct {
	Registry  Registry
	Discovery Discovery
	// Refresh must only read GitHub/local evidence and CAS-publish the latest
	// saved review. It must never enqueue or invoke generation.
	Refresh func(context.Context, repos.Clone, int) (discovery.Observation, error)
	// Saved lists the clone's pull requests with a saved review: only those
	// are read in the background.
	Saved func(context.Context, repos.Clone) ([]SavedReview, error)
	// Budget is the credential's GraphQL points budget as discovery last read
	// it (discovery.Service.GraphQLBudget); nil leaves it unchecked. Reads
	// wait while less than refreshReserve of it is left.
	Budget func(context.Context, github.Repository) (discovery.RateLimit, error)
	// Discovered receives every snapshot a discovery poll returned without
	// error. The daemon passes them to its automatic generation scheduler
	// (internal/automatic), which alone decides whether to queue a job; this
	// service never enqueues or invokes generation itself.
	Discovered func(context.Context, discovery.Snapshot)
	// Retain reconciles a clone's retained review refs with its stores and
	// reports whether a bound left work for another pass (Retention.Retain);
	// nil skips it. Sync calls it per common git directory, see retainDue.
	Retain   func(context.Context, repos.Clone) (bool, error)
	Changed  func()
	Now      func() time.Time
	Interval time.Duration
	// contains filtered or unexported fields
}

func (*Service) Asked added in v0.2.0

func (s *Service) Asked(repository string)

Asked records a refresh of repository the person asked for: the next pass reads its open saved reviews once more, and again those whose reads failed, stopped or not. Retired reviews of closed pull requests stay retired.

func (*Service) Errors added in v0.2.0

func (s *Service) Errors() map[string]string

Errors is the state the app uses to show repository refresh failures. Routine poll times and retry counts do not change this answer.

func (*Service) Handler

func (s *Service) Handler() http.Handler

func (*Service) Limited

func (s *Service) Limited(host string, until time.Time)

Limited records a rate limit another reader of the account's GitHub budget hit, so refreshes wait it out too.

func (*Service) Paused

func (s *Service) Paused(host string) time.Time

Paused is when the host's rate limit lets refreshes resume, for another reader of the same account's GitHub budget, such as the PR page's facts (internal/facts), to wait it out too.

func (*Service) RefreshInterval

func (s *Service) RefreshInterval() time.Duration

RefreshInterval is how often the background refresh polls.

func (*Service) RefreshNow

func (s *Service) RefreshNow(ctx context.Context, clone repos.Clone, numbers []int) ([]discovery.Observation, error)

RefreshNow refreshes some pull requests of one clone at once, as Sync refreshes each of them, outside Sync's schedule: a hook event said they changed. It reads every pull request it is asked to, whatever Sync's rules say of it: retired, stopped after failures, or past the hour's request budget, which its reads still count toward. It honors the host's rate pause, which a rate-limited refresh here extends for Sync too, and returns the observations it made. It never enqueues or invokes generation.

func (*Service) ResetIdentity

func (s *Service) ResetIdentity()

ResetIdentity drops credential-dependent backoff without waiting for an old network pass. Its late state/rate writes are fenced by the captured epoch.

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run starts immediately in its caller's goroutine. The host runs it separately from HTTP startup; a slow/offline GitHub never holds the cached inbox hostage. One pass runs at a time: its polls run pollConcurrency at once, its reads one at a time.

func (*Service) Sync

func (s *Service) Sync(ctx context.Context)

Sync is a single serialized scheduling pass, exposed for deterministic tests. Discovery itself owns conditional requests and credential-wide rate budgets. It polls every due repository first, pollConcurrency at a time, so no repository's pull requests wait behind another's reads, then reads the saved pull requests that are due, those GitHub reported changed first, within refreshBudget.

type State

type State struct {
	Problems    []Problem `json:"problems,omitempty"`
	Repository  string    `json:"repository"`
	LastSuccess time.Time `json:"last_success"`
	NextRefresh time.Time `json:"next_refresh"`
	Error       string    `json:"error,omitempty"`
	Failures    int       `json:"failures"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL