virgodb 0.4.8-x86_64-linux-gnu → 0.5.0-x86_64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/virgodb/manifest_quiesce.rb +112 -0
- data/lib/virgodb/table.rb +12 -0
- data/lib/virgodb/version.rb +1 -1
- data/lib/virgodb/virgodb_ruby.so +0 -0
- data/lib/virgodb.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9eeffeb389afab6ec06fbb371c526a2191c7422892a82d41d4c5890d70f136d7
|
|
4
|
+
data.tar.gz: c9410d12cb71225253f2610b01658181204dbfc71bdedf944d0e8824610fed93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8536425d8f46324976627c7fef51a137dc7911d226ec4c9703d5db0935f1834217123b7f85d2f21b0392a1e463324b19d037aa384672b0af675311fb5c0f820
|
|
7
|
+
data.tar.gz: 394df85ea9b968c880ea7d81babdb8c4914afe65cc10f7e5ce137906a278bfa4fbbf814ef3d5095034e8dfdee53ec84b2f98e2515a9636d00adf7b44e38e9545
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Virgodb
|
|
4
|
+
# Raised by `Virgodb.with_manifest_quiesced` when a table's maintenance
|
|
5
|
+
# lease is still held by someone else (real background maintenance, or a
|
|
6
|
+
# concurrent quiesce attempt) when `lease_wait_timeout` runs out.
|
|
7
|
+
class LeaseUnavailable < StandardError; end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Claims EVERY `[manifest_path, table_name]` pair in `targets`'
|
|
11
|
+
# maintenance lease (the SAME mutual exclusion the real background
|
|
12
|
+
# maintenance thread already uses against itself across processes), so
|
|
13
|
+
# `holder` gets exclusive access to every manifest file involved for as
|
|
14
|
+
# long as the block runs, then releases every claimed lease afterward
|
|
15
|
+
# -- whether the block succeeds or raises.
|
|
16
|
+
#
|
|
17
|
+
# Takes a `[manifest_path, table_name]` pair per target, not one shared
|
|
18
|
+
# `manifest_path` plus a list of table names -- deliberately, even
|
|
19
|
+
# though every target sharing one physical file (this project's own
|
|
20
|
+
# real production topology) is the common case. A caller with tables
|
|
21
|
+
# split across separate manifest files (a topology this gem already
|
|
22
|
+
# supports -- see `Registry.override_manifest_path!`-style per-table
|
|
23
|
+
# overrides, real in this project's own test suite) needs each table's
|
|
24
|
+
# lease claimed against its OWN file, not all of them against
|
|
25
|
+
# whichever one happens to be listed first -- collapsing to one shared
|
|
26
|
+
# path would silently claim/release the wrong table's lease against
|
|
27
|
+
# the wrong file for every target after the first.
|
|
28
|
+
#
|
|
29
|
+
# Deliberately still per-table leases underneath, not a genuine
|
|
30
|
+
# single manifest-scoped lease: the real maintenance thread's own
|
|
31
|
+
# compaction/vacuum/integrity-check only ever checks the per-table
|
|
32
|
+
# lease, and a brand-new separate file-level lock nothing else looks at
|
|
33
|
+
# would protect nothing. Collapsing the underlying lease itself to
|
|
34
|
+
# one-per-file would also serialize every table's maintenance behind a
|
|
35
|
+
# single lock, regressing the deliberate "tables' maintenance runs
|
|
36
|
+
# independently" property `Manifest::try_claim_maintenance_lease`'s own
|
|
37
|
+
# doc comment describes.
|
|
38
|
+
#
|
|
39
|
+
# Waits up to `lease_wait_timeout` seconds (polling every
|
|
40
|
+
# `lease_poll_interval` seconds) for a lease real maintenance currently
|
|
41
|
+
# holds; raises `LeaseUnavailable` if one never frees up in time. The
|
|
42
|
+
# final `ensure` releases every table unconditionally, not just the
|
|
43
|
+
# ones this call actually reached before raising -- safe because
|
|
44
|
+
# `release_maintenance_lease` is a no-op on a lease this holder never
|
|
45
|
+
# held (see that method's own doc comment), so this needs no separate
|
|
46
|
+
# partial-rollback bookkeeping.
|
|
47
|
+
#
|
|
48
|
+
# `renewal_interval` (seconds, optional): if given, a background thread
|
|
49
|
+
# re-extends every claimed lease this often for as long as the block
|
|
50
|
+
# runs, instead of trusting the single fixed `lease_duration_secs`
|
|
51
|
+
# grant from the initial claim to always be enough -- needed for a
|
|
52
|
+
# caller whose actual work (e.g. a slow first-ever backup upload with
|
|
53
|
+
# no dedup baseline yet) could genuinely outlive one fixed grant and
|
|
54
|
+
# free real maintenance to start compacting/vacuuming mid-work. Uses a
|
|
55
|
+
# background `Thread` rather than checking elapsed time inline, since
|
|
56
|
+
# the caller's own slow work (e.g. a blocking shellout) has no natural
|
|
57
|
+
# checkpoint to interleave a renewal call into; `Thread#kill` in the
|
|
58
|
+
# `ensure` is deliberately not a graceful join -- a lease renewal is
|
|
59
|
+
# just an independent, atomic single-row update per table, nothing here
|
|
60
|
+
# has multi-step state a mid-flight kill could corrupt. Omit (the
|
|
61
|
+
# default) when the block's own work is guaranteed to finish well
|
|
62
|
+
# within `lease_duration_secs` on its own.
|
|
63
|
+
#
|
|
64
|
+
# Extracted here (not left to live per-host-app) because this exact
|
|
65
|
+
# "claim every table sharing a manifest, do work, release every table"
|
|
66
|
+
# dance has no host-app-specific content in it at all -- any embedder
|
|
67
|
+
# needing exclusive access to a shared manifest for an external
|
|
68
|
+
# operation (a backup tool, a manual admin task, anything else) needs
|
|
69
|
+
# the identical coordination, and every independent reimplementation of
|
|
70
|
+
# it is a real chance for the per-table lease and whatever whole-file
|
|
71
|
+
# operation it's meant to protect to drift out of sync (see this
|
|
72
|
+
# project's own README, the 2026-09 incremental_vacuum incident, for
|
|
73
|
+
# exactly that failure shape once).
|
|
74
|
+
def with_manifest_quiesced(targets:, holder:, lease_duration_secs:, lease_wait_timeout:, lease_poll_interval:, renewal_interval: nil)
|
|
75
|
+
holder = holder.to_s
|
|
76
|
+
targets.each { |manifest_path, table_name| claim_lease!(manifest_path, table_name, holder: holder, lease_duration_secs: lease_duration_secs, lease_wait_timeout: lease_wait_timeout, lease_poll_interval: lease_poll_interval) }
|
|
77
|
+
|
|
78
|
+
if renewal_interval
|
|
79
|
+
with_lease_renewal(targets, holder: holder, lease_duration_secs: lease_duration_secs, renewal_interval: renewal_interval) { yield }
|
|
80
|
+
else
|
|
81
|
+
yield
|
|
82
|
+
end
|
|
83
|
+
ensure
|
|
84
|
+
targets.each { |manifest_path, table_name| release_maintenance_lease(manifest_path, table_name, holder) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def claim_lease!(manifest_path, table_name, holder:, lease_duration_secs:, lease_wait_timeout:, lease_poll_interval:)
|
|
90
|
+
deadline = Time.now + lease_wait_timeout
|
|
91
|
+
until claim_maintenance_lease(manifest_path, table_name, holder, lease_duration_secs)
|
|
92
|
+
if Time.now > deadline
|
|
93
|
+
raise LeaseUnavailable, "could not claim #{table_name}'s maintenance lease within #{lease_wait_timeout}s (still held by real maintenance)"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
sleep lease_poll_interval
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def with_lease_renewal(targets, holder:, lease_duration_secs:, renewal_interval:)
|
|
101
|
+
renewal_thread = Thread.new do
|
|
102
|
+
loop do
|
|
103
|
+
sleep renewal_interval
|
|
104
|
+
targets.each { |manifest_path, table_name| renew_maintenance_lease(manifest_path, table_name, holder, lease_duration_secs) }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
yield
|
|
108
|
+
ensure
|
|
109
|
+
renewal_thread&.kill
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/virgodb/table.rb
CHANGED
|
@@ -120,6 +120,18 @@ module Virgodb
|
|
|
120
120
|
Virgodb.integrity_check(@manifest_path)
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
# Writes a complete, consistent snapshot of the manifest to dest_path
|
|
124
|
+
# via VACUUM INTO, through virgodb's own connection -- same "don't use
|
|
125
|
+
# a second connection type against this shared file" reasoning as
|
|
126
|
+
# integrity_check above, just for backup tooling instead of health
|
|
127
|
+
# checks. Manifest-scoped, not table-scoped. See
|
|
128
|
+
# Manifest::snapshot_into's own doc comment (crates/manifest/src/lib.rs)
|
|
129
|
+
# for what VACUUM INTO itself guarantees and its one real caller
|
|
130
|
+
# constraint (refuses to overwrite an existing dest_path).
|
|
131
|
+
def snapshot(dest_path)
|
|
132
|
+
Virgodb.snapshot(@manifest_path, dest_path)
|
|
133
|
+
end
|
|
134
|
+
|
|
123
135
|
# Claims this table's maintenance lease as `holder` for the next
|
|
124
136
|
# `lease_duration_secs` seconds, locking out the real maintenance
|
|
125
137
|
# thread (its own claim attempts fail against an unexpired lease the
|
data/lib/virgodb/version.rb
CHANGED
data/lib/virgodb/virgodb_ruby.so
CHANGED
|
Binary file
|
data/lib/virgodb.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: virgodb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: x86_64-linux-gnu
|
|
6
6
|
authors:
|
|
7
7
|
- virgodb
|
|
@@ -21,6 +21,7 @@ extra_rdoc_files: []
|
|
|
21
21
|
files:
|
|
22
22
|
- LICENSE
|
|
23
23
|
- lib/virgodb.rb
|
|
24
|
+
- lib/virgodb/manifest_quiesce.rb
|
|
24
25
|
- lib/virgodb/rows.rb
|
|
25
26
|
- lib/virgodb/table.rb
|
|
26
27
|
- lib/virgodb/time_helpers.rb
|