Help us shape the future of Go! Take the 2026 Go Developer Survey

fence

package
v0.40.22 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package fence carries a group lease's fence token to the writes that must be refused when the lease is gone (the single-owner design, 4.3). A guarded write puts rivolt_lease_ok(group, pod, epoch) in the same statement as the write, so there is no check-then-write gap; the function is VOLATILE and reads statement_timestamp(), so a statement that starts after the lease expiry is refused even if nobody has taken the group yet.

Index

Constants

View Source
const OK = "{{ok}}"

OK marks where a write statement takes the lease check, in its WHERE clause (or the WHERE of an INSERT … SELECT). Without a token in the context Exec replaces it with TRUE and the statement runs as it always did; with one it becomes the result of rivolt_lease_ok, evaluated once at the start of the statement.

View Source
const TxCap = 2 * time.Second

TxCap bounds a guarded transaction from BEGIN: with the 2 s statement timeout and 1 s idle-in-transaction timeout below, an old owner's last commit lands within 3 s of the lease expiry (Postgres 16 has no transaction_timeout), which the new owner's 5 s warm-up covers.

Variables

View Source
var ErrFenced = errors.New("fenced: group lease no longer held")

ErrFenced means a write was refused because the pod no longer holds the group lease (or never did, or the pod's local lease deadline has passed). The caller stops acting for the group.

Functions

func BeginBounded

func BeginBounded(ctx context.Context, db *sql.DB) (*sql.Tx, context.Context, context.CancelFunc, error)

BeginBounded starts a transaction for a guarded write: the context is capped at TxCap and the session gets SET LOCAL statement and idle-in-transaction timeouts. Callers use the returned context for the transaction's statements and call cancel when done.

func Exec added in v0.40.13

func Exec(ctx context.Context, q Querier, stmt string, args ...any) (int64, error)

Exec runs a data-modifying statement (INSERT, UPDATE or DELETE, without its own RETURNING) that carries OK where the lease check belongs, and returns the rows it affected.

With no token in ctx it is the plain statement (OK is TRUE). With a token the statement is wrapped so the check and the write are one statement: rivolt_lease_ok is a MATERIALIZED CTE (one evaluation, at statement_timestamp()), and the statement says in its own result whether it was refused, so a refusal (ErrFenced) is told apart from a write that matched no row. The token's three values are appended after the caller's arguments.

A guarded statement always runs bounded: on a *sql.DB it gets its own InTx (2 s cap, 2 s statement and 1 s idle-in-transaction timeouts); on a *sql.Tx the caller's transaction must already be one (InTx or BeginBounded). So one that passed the lease check and then waits on a lock is cancelled by the server, and an old owner's last commit lands within the bound the new owner's warm-up covers, whichever caller forgot.

func InTx

func InTx(ctx context.Context, db *sql.DB, fn func(ctx context.Context, tx *sql.Tx) error) error

InTx runs fn in a bounded transaction (see BeginBounded) and commits it. Every guarded write that is not already a transaction goes through here, so a statement that passed the lease check and then waits (a row lock, a slow disk) is cancelled by the server after 2 s: an old owner's last write lands within about 3 s of the lease expiry, which the new owner's 5 s warm-up covers.

func RowJSON

func RowJSON(cols []string, args []any) ([]byte, error)

RowJSON turns a column list and its statement arguments into one JSON document for json_populate_record(NULL::table, doc), so a guarded INSERT ... SELECT keeps every column's own type with no per-column casts. Arguments are the values the plain statement binds: driver.Valuers (sql.NullFloat64 and friends) are unwrapped, a []byte is a JSON document and is embedded as one (a jsonb column), and uuid and time values use their own JSON form.

func With

func With(ctx context.Context, t Token) context.Context

With returns ctx carrying the token.

Types

type Deadliner

type Deadliner interface {
	Deadline(vehicleID string) (time.Time, bool)
}

Deadliner is the optional part of a Source that knows when a vehicle's local lease deadline falls: the monotonic moment 20 s after the last renew was sent, always before the database expiry. A Rivian call made after it must not go out, whatever the process was doing in between (a pause makes a context deadline fire on resume, where a poll of a flag would not).

type Holder added in v0.40.15

type Holder struct {
	// contains filtered or unexported fields
}

Holder is the fence that consumers built before the lease store exists read through. It answers from the installed source on every call, so installing it late reaches consumers already running. Until then it answers Off, or Lost once Require(true) has said lead mode needs a fence: a consumer that starts before the lease store exists (the OTA syncer's on-connect fetch, the Live Activity source, the left-open job, the receipts syncer) then does nothing, rather than acting for a group this pod may not hold.

func (*Holder) Deadline added in v0.40.15

func (h *Holder) Deadline(vehicleID string) (time.Time, bool)

Deadline forwards the installed source's local lease deadline, none without one.

func (*Holder) Fence added in v0.40.15

func (h *Holder) Fence(vehicleID string) (Token, State)

Fence answers from the installed source.

func (*Holder) FenceUser added in v0.40.15

func (h *Holder) FenceUser(ctx context.Context, uid string) (Token, State, error)

FenceUser answers for a user's group from the installed source. A source that cannot resolve users counts as missing.

func (*Holder) Require added in v0.40.15

func (h *Holder) Require(on bool)

Require says lead mode is on: an uninstalled holder reads Lost, not Off. Call it before any consumer starts.

func (*Holder) Set added in v0.40.15

func (h *Holder) Set(s Source)

Set installs the fence source (the lease store).

type Querier added in v0.40.13

type Querier interface {
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

Querier is what Exec runs on: a *sql.DB or a *sql.Tx.

type Source

type Source interface {
	Fence(vehicleID string) (Token, State)
}

Source resolves a Rivian vehicle id to its fence state. The lease store implements it.

type State

type State int

State is what a pod may do for a vehicle right now.

const (
	// Off: lead mode is off; writes are unguarded, as before M2.
	Off State = iota
	// Held: the pod holds the vehicle's group; writes carry the token.
	Held
	// Warming: the group was just taken from a holder that did not release it; writes wait out
	// the warm-up so the old holder's last commits land first.
	Warming
	// Lost: no live token (the group is not ours, or the local deadline passed); no write.
	Lost
)

type Token

type Token struct {
	Group string
	Pod   string
	Epoch int64
}

Token is the proof a pod holds a group.

func From

func From(ctx context.Context) (Token, bool)

From returns the token in ctx, if any. A write with no token is unguarded: that is how everything outside the owner's recorder (imports, repairs, user-triggered writes) writes.

type UserSource added in v0.40.15

type UserSource interface {
	FenceUser(ctx context.Context, uid string) (Token, State, error)
}

UserSource resolves a user to the fence state of their group (the lease store implements it); the receipts syncer asks it.

Jump to

Keyboard shortcuts

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