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
- Variables
- func BeginBounded(ctx context.Context, db *sql.DB) (*sql.Tx, context.Context, context.CancelFunc, error)
- func Exec(ctx context.Context, q Querier, stmt string, args ...any) (int64, error)
- func InTx(ctx context.Context, db *sql.DB, ...) error
- func RowJSON(cols []string, args []any) ([]byte, error)
- func With(ctx context.Context, t Token) context.Context
- type Deadliner
- type Holder
- type Querier
- type Source
- type State
- type Token
- type UserSource
Constants ¶
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.
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 ¶
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
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 ¶
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 ¶
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.
Types ¶
type Deadliner ¶
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
Deadline forwards the installed source's local lease deadline, none without one.
func (*Holder) FenceUser ¶ added in v0.40.15
FenceUser answers for a user's group from the installed source. A source that cannot resolve users counts as missing.
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 ¶
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 )