Class: OpenReceiveMeta
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- OpenReceiveMeta
- Defined in:
- app/models/open_receive_meta.rb
Overview
Engine-owned key/value/rev rows in the host database (the install generator emits the openreceive_meta table next to openreceive_payments). It holds the durable reconcile gate every Puma worker and process on this database shares, so rapid OpenReceive calls collapse to one real wallet scan per interval, and the installed schema-version marker the engine refuses to run past. Mirrors the JS SQL repository's claimReconcileGate and assertSupportedSchema.
Constant Summary collapse
- RECONCILE_GATE_KEY =
"transaction_scan_gate"- SCHEMA_VERSION_KEY =
"schema_version"- CAS_RETRIES =
6- META_CLOCK_SKEW_SECONDS =
Tolerance when reading a timestamp another worker wrote. Beyond it a claim stamped in the future is a backwards clock step, not a fresh claim: without this clamp the gate would read as busy until wall-clock time caught up.
60
Class Method Summary collapse
-
.assert_supported_schema! ⇒ Object
One probe per process, on the engine's first database touch: a database written by a NEWER library must not be operated by this one (columns or state transitions it does not know about).
-
.cas(key, value, expected_rev) ⇒ Object
Optimistic compare-and-set: INSERT-if-absent at rev 0, or UPDATE ...
-
.claim_reconcile_gate(now:, interval_seconds:) ⇒ Object
Claim the durable global reconcile gate.
Class Method Details
.assert_supported_schema! ⇒ Object
One probe per process, on the engine's first database touch: a database written by a NEWER library must not be operated by this one (columns or state transitions it does not know about). An unreadable or absent marker means "not versioned" — the pre-versioned migrations could not seed a row — and is not a refusal. Mirrors the JS repository's assertSupportedSchema.
28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'app/models/open_receive_meta.rb', line 28 def self.assert_supported_schema! @schema_version_checked ||= begin stored = stored_schema_version if !stored.nil? && stored > OpenReceive::Server::PAYMENTS_SCHEMA_VERSION raise OpenReceive::ConfigurationError, "openreceive_meta reports openreceive schema version #{stored}, newer than this " \ "library's #{OpenReceive::Server::PAYMENTS_SCHEMA_VERSION}. Upgrade openreceive-rails " \ "before serving this database." end true end end |
.cas(key, value, expected_rev) ⇒ Object
Optimistic compare-and-set: INSERT-if-absent at rev 0, or UPDATE ... WHERE rev = expected. Returns true when this caller's write won.
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'app/models/open_receive_meta.rb', line 43 def self.cas(key, value, expected_rev) if expected_rev.nil? begin create!(key: key, value: value, rev: 0) true rescue ActiveRecord::RecordNotUnique false end else where(key: key, rev: expected_rev).update_all(value: value, rev: expected_rev + 1) == 1 end end |
.claim_reconcile_gate(now:, interval_seconds:) ⇒ Object
Claim the durable global reconcile gate. Returns true when this caller may run a wallet scan now; false (gate_busy) when another worker scanned within interval_seconds. The winner is identified by reading back its own token — the portable equivalent of an affected-row count, matching the JS claimReconcileGate. A failed scan leaves claimed_at in place on purpose — the next interval retries without a stampede.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'app/models/open_receive_meta.rb', line 62 def self.claim_reconcile_gate(now:, interval_seconds:) assert_supported_schema! claim = JSON.generate("claimed_at" => Integer(now), "token" => SecureRandom.uuid) CAS_RETRIES.times do row = find_by(key: RECONCILE_GATE_KEY) if row.nil? cas(RECONCILE_GATE_KEY, claim, nil) else claimed_at = parse_claimed_at(row.value) if claimed_at && (Integer(now), claimed_at, Integer(interval_seconds)) return false end cas(RECONCILE_GATE_KEY, claim, row.rev) end return true if where(key: RECONCILE_GATE_KEY).pick(:value) == claim end false end |