Class: Reeve::Audit::IsolatedRecorder
- Defined in:
- lib/reeve/audit/isolated_recorder.rb
Overview
The ledger row, written on a connection of its own.
Recorder writes in a savepoint, which protects the trace from a transaction the
tool opens and rolls back. It cannot protect the trace from a transaction the
host opened around the whole invocation — a controller that wraps each request in
one, a job runner, a test suite with transactional fixtures. A savepoint released
into that transaction goes down with it, and the envelope has no way to learn
otherwise: the write reported success, the invocation returned records, and no row
survives to say so. Constitution II says every call leaves a trace; on one
connection that is not achievable, only detectable, which is why Recorder warns.
A second connection is the only thing that actually closes it. A transaction on connection A cannot roll back an INSERT committed on connection B, so the row lands whatever the host's transaction does next.
config.audit_recorder = Reeve::Audit::IsolatedRecorder
Why this is not the default
It cannot be. On SQLite the host's open transaction holds the database write lock,
so the second connection blocks until it times out and every guarded call fails —
the isolation is not merely unavailable there, it is actively worse than the
savepoint. Rather than degrade quietly on the database most hosts develop against,
this refuses to run on SQLite and says why. Recorder remains the default, and a
host that needs durability under a wrapping transaction opts in on a database where
opting in means something.
It also costs a connection per pool, and moves the ledger write outside the host's transaction in both directions: the row survives a rollback, which is the point, and it also survives a rollback of an invocation the host meant to undo entirely. A ledger of what was attempted is the intended reading of Constitution II, but it is a choice, and it should be made rather than inherited.
Constant Summary collapse
- UNSUPPORTED_ADAPTERS =
%w[sqlite sqlite3].freeze
Constants inherited from Recorder
Class Method Summary collapse
-
.available? ⇒ Boolean
True when a second connection can actually be used here.
- .record(attributes) ⇒ Object
- .verify_supported! ⇒ Object
Instance Method Summary collapse
-
#initialize(config: nil) ⇒ IsolatedRecorder
constructor
A new instance of IsolatedRecorder.
-
#record(attributes) ⇒ Object
Checked out and returned around each write rather than held: the pool exists to keep the ledger off the host's connection, not to keep a connection per thread alive for the life of the process.
Constructor Details
#initialize(config: nil) ⇒ IsolatedRecorder
Returns a new instance of IsolatedRecorder.
69 70 71 |
# File 'lib/reeve/audit/isolated_recorder.rb', line 69 def initialize(config: nil) super(entry_class: IsolatedEntry, config: config) end |
Class Method Details
.available? ⇒ Boolean
True when a second connection can actually be used here. Lets a host branch in an initializer — SQLite in development, PostgreSQL in production — rather than discovering the answer on the first guarded call.
48 49 50 51 52 53 |
# File 'lib/reeve/audit/isolated_recorder.rb', line 48 def available? verify_supported! true rescue Reeve::Error false end |
.record(attributes) ⇒ Object
41 42 43 |
# File 'lib/reeve/audit/isolated_recorder.rb', line 41 def record(attributes) new.record(attributes) end |
.verify_supported! ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/reeve/audit/isolated_recorder.rb', line 55 def verify_supported! adapter = IsolatedEntry.adapter_name return unless UNSUPPORTED_ADAPTERS.include?(adapter.to_s.downcase) raise ConfigurationError, "Reeve::Audit::IsolatedRecorder cannot be used on #{adapter}: an open " \ "transaction holds the database write lock, so a second connection " \ "blocks until it times out and every guarded call fails. Use " \ "Reeve::Audit::Recorder here — it warns when a host transaction is " \ "wrapped around an invocation — and configure this recorder only where " \ "the database supports concurrent writers." end |
Instance Method Details
#record(attributes) ⇒ Object
Checked out and returned around each write rather than held: the pool exists to keep the ledger off the host's connection, not to keep a connection per thread alive for the life of the process.
76 77 78 79 |
# File 'lib/reeve/audit/isolated_recorder.rb', line 76 def record(attributes) self.class.verify_supported! IsolatedEntry.connection_pool.with_connection { super } end |