go-ruby-pstore

Ruby's PStore transaction engine in pure Go β€” MRI-compatible, no cgo.

pure Go Β· zero cgo PStore transactional commit / abort read-only transactions Marshal-backed MRI file-compatible injected backend 100% coverage 6 arches
Documentation GitHub
Documentation (MkDocs Material + mike) License: BSD-3-Clause Go 1.26.4+ Coverage 100%

go-ruby-pstore is a pure-Go (no cgo) reimplementation of the transaction engine at the heart of Ruby's PStore β€” MRI's transactional, Marshal-backed object store. It runs the load β†’ transaction-body β†’ commit/abort state machine over a Hash "table" and serialises that table with go-ruby-marshal, so the on-disk bytes are byte-compatible with a file written by MRI's PStore: a real ruby -rpstore file loads here, and a file this engine commits is read back unchanged by MRI. The file half β€” opening the store, flock, the atomic-rename / rewind-truncate save strategies β€” is injected as a two-method Backend; rbgo wires the real os.File + syscall.Flock, tests use an in-memory backend. It was extracted from rbgo's internals into a reusable standalone library: no dependency on the Ruby runtime, the dependency runs the other way. It is the PStore backend for go-embedded-ruby, bound by rbgo as a native module just like go-ruby-regexp and go-ruby-erb β€” differential-tested against MRI, 100% coverage, CI green across 6 arches and 3 OSes.

Transaction state machine ready

Transaction(readOnly, body) runs PStore’s load β†’ body β†’ commit/abort machine over a Hash table: a read-write body that returns normally commits, refusing a nested transaction and raising outside one.

Commit on normal exit ready

A read-write transaction whose body returns normally Marshal-dumps the table back through the backend β€” but only if the bytes changed (MRI’s checksum/size guard); an unchanged transaction performs no write.

Commit / Abort early exit ready

Return t.Commit() or t.Abort() from the body to exit it early (MRI’s throw :pstore_abort_transaction): Abort discards every change, Commit persists the work so far, and the body does not continue past either.

Read-only transactions & error taxonomy ready

In a read-only transaction Set and Delete raise PStore::Error and the backend is never written; a single *pstore.Error carries MRI’s exact messages β€” "not in transaction", "in read-only transaction", "nested transaction", and the rest.

MRI-compatible on-disk Marshal ready

The table is Marshal.dump / loaded via go-ruby-marshal, so a file this engine commits is read back unchanged by MRI’s PStore, and a real ruby -rpstore file loads here β€” byte-compatible both directions.

Differential oracle & coverage ready

Real ruby -rpstore files loaded here, the table round-tripped both directions (MRIβ†’Go and Goβ†’MRI), and MRI’s own commit / abort / read-only sequence replayed and asserted; 100% coverage, gofmt + go vet clean, green across all six 64-bit Go arches and three OSes.

A faithful port of Ruby's PStore transaction engine in pure Go, cgo disabled, so it cross-compiles and embeds anywhere. It runs the load β†’ body β†’ commit/abort state machine β€” commit on normal exit (skipping the write when nothing changed), Commit / Abort early exit, read-only transactions, and MRI's exact error taxonomy β€” and serialises the table through go-ruby-marshal, so the on-disk bytes are byte-compatible with MRI's PStore. The file/locking layer is injected through a two-method Backend. Validated differentially against the system ruby binary β€” real ruby -rpstore files round-tripped both directions. It is a standalone, reusable module extracted from rbgo's internals, and the PStore backend for the sibling org github.com/go-embedded-ruby.