Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type DbCache ¶
type DbCache struct {
Conf config.Config
Ctx context.Context
Db *sql.DB
Logger *zap.Logger
Mu sync.Mutex
// Source is the chassis's runtime *sql.DB handle — the live,
// configured connection pool that the rest of the chassis writes
// through. Reload() copies from this handle (via the SQLite online
// backup API over a borrowed Source connection) rather than opening
// its own connection to the file: in WAL mode a second uncoordinated
// connection races the main one's .db-shm state and fails with
// "database is locked" on first boot. Going through the same pool
// means there is no second connection to race.
Source *sql.DB
// OnReload, if set, runs at the end of every Reload against the
// freshly-built in-memory DB while the cache lock is still held —
// so no request ever observes the snapshot before the overlay is
// applied. Used by chassis/sysops to re-apply the trusted system
// opstacks (Reload rebuilds :memory: from the runtime.db dump and
// would otherwise drop them). A hook error is logged, not fatal:
// the previous snapshot stays live rather than going dark.
OnReload func(*sql.DB) error
// contains filtered or unexported fields
}
DbCache structure
func New ¶
func New(conf config.Config, logger *zap.Logger, ctx context.Context, source *sql.DB) (*DbCache, error)
New Create a new in-memory DB cache.
`source` is the chassis's runtime *sql.DB — the live connection pool that Reload() reads from. Required: passing nil here would fail at the first Reload. See DbCache.Source for the WAL rationale.
Critical: go-sqlite3 gives each *connection* in the pool its own `:memory:` database. So if connection #1 loads the schema and a later concurrent query opens connection #2, that second connection sees an empty DB and the query fails with "no such table: ops".
To avoid that, pin the in-memory cache to a single connection. Reads are fast and the cache is read-only on the hot path; serializing through one connection costs nothing visible but guarantees consistency under concurrent load.
func (*DbCache) Reload ¶
Reload a db file into Memory. Sources from the runtime DB only — the auth DB (when present) is owned exclusively by the admin role and is never mirrored into the read cache.
Concurrency: the dump+replay+swap runs under reloadMu (serializing reloads end-to-end) while only the swap+overlay touches Mu — so the expensive dump never blocks Snapshot() readers. Serialization is still required: two concurrent writers each calling Reload after their commits would otherwise dump in parallel (each capturing a snapshot before some of the OTHER writer's commits land), and the reload that finishes its dump LAST would publish a STALE snapshot, silently clobbering durably-committed rows from the mirror. Symptom: a row on disk but missing from the resolver until the next (unrelated) reload happens to dump after every commit settled. reloadMu held across dump+swap keeps the second reload's dump strictly after the first's swap. (This costs serial reloads under write bursts, but the dump was the dominant cost regardless — concurrent dumps were a parallelism mirage.)
func (*DbCache) Snapshot ¶
Snapshot returns the current mirror handle under the lock. Callers that live longer than one reload (e.g. the ingress resolver) MUST call this per use rather than capturing dbc.Db once: Reload() swaps dbc.Db to a fresh *sql.DB, so a captured handle goes stale and never sees rows written after it was captured. The returned *sql.DB stays valid for the caller's immediate query (the old handle isn't closed on swap); at worst it's one reload-cycle stale, which is the same guarantee the rest of the read path has.