fence

package
v0.40.13 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 8 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 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.

Jump to

Keyboard shortcuts

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