Skip to main content

Data model and durability

Database​

One SQLite file, default ${XDG_STATE_HOME:-$HOME/.local/state}/session-bus/bus.db, overridden with BUS_DB. Every command that opens it uses the same rule and creates the directory if needed.

Settings: journal_mode=WAL, synchronous=NORMAL, busy_timeout=5000, foreign_keys=ON. Writes run in BEGIN IMMEDIATE transactions.

TableColumns
principalsid, created_at, disabled_at
tokensid, hash, prefix, scopes, creator_id, label, created_at, expires_at, revoked_at, last_used_at
sessionsname, owner_principal, description, created_at, last_seen
messagesid (autoincrement), to_session, from_session, from_principal, token_id, body, thread_id, created_at, acked_at
metaschema (1), bus_instance_id (a UUID)

Times are milliseconds since the epoch. messages has an index on (to_session, id) for inbox reads.

Durability​

  • WAL with synchronous=NORMAL survives a process crash without losing committed transactions. scripts/e2e.sh sends a message, kills serve with kill -9, restarts it and reads the message back.
  • A power loss or OS crash can lose the most recent transactions. Accepted.
  • A clean shutdown (SIGTERM, SIGINT) checkpoints the WAL and closes the file.
  • Pruning deletes only acked messages whose acked_at is more than 7 days old. It never deletes unacked mail.

Lock files​

serve, restore and the push shim share one lock routine.

  • The lock file holds {pid, boot_ms}. It is written to a temp file, fsynced, and hard-linked into place, so it appears atomically.
  • A lock is live if the pid exists (or signalling it gives EPERM), the boot time matches within 60 s, and it is not our own pid. Anything else is stale and is reclaimed once.
  • Two processes reclaiming the same stale lock at the same moment can race. There is no flock. This is a known shortcut.
LockTaken byEffect
<BUS_DB>.lockserve, before opening the DB or binding the porta second serve exits 3 (serve already running (pid N))
<BUS_DB>.lockrestore, for its whole runrestore exits 3 while serve runs
shim-<name>.lock in the state dirthe push shima second shim for the same name exits 3

Backup and restore rules​

  • backup <path> runs VACUUM INTO <path>, which is safe while serve runs. The copy is a single file (journal mode DELETE) and gets a fresh bus_instance_id. The target must not exist.
  • restore <backup> requires serve to be stopped. It holds the lock, copies the backup to bus.db.tmp, checks it is a bus database, gives it a fresh bus_instance_id, fsyncs it, deletes the old -wal and -shm files, renames it into place and fsyncs the directory. If any step fails, the live database is untouched.
  • There is no --force.
  • Because the instance id changes, push shims reset their cursor and push unacked mail again. Delivery stays at-least-once.

Shim state​

The shim never opens SQLite. It writes shim-<name>.json (mode 0600) with {bus_instance_id, high_water_id}, via a temp file and rename. A missing or corrupt file means "start from 0".