merge_machines 0.2.0
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 +7 -0
- data/CHANGELOG.md +57 -0
- data/Cargo.lock +294 -0
- data/Cargo.toml +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -0
- data/ext/merge_machines_native/core/Cargo.toml +24 -0
- data/ext/merge_machines_native/core/src/gcounter.rs +242 -0
- data/ext/merge_machines_native/core/src/gset.rs +181 -0
- data/ext/merge_machines_native/core/src/hybrid.rs +222 -0
- data/ext/merge_machines_native/core/src/lib.rs +221 -0
- data/ext/merge_machines_native/core/src/logical.rs +139 -0
- data/ext/merge_machines_native/core/src/lww.rs +213 -0
- data/ext/merge_machines_native/core/src/orset.rs +334 -0
- data/ext/merge_machines_native/core/src/pncounter.rs +165 -0
- data/ext/merge_machines_native/extconf.rb +3 -0
- data/ext/merge_machines_native/ffi/Cargo.toml +20 -0
- data/ext/merge_machines_native/ffi/extconf.rb +21 -0
- data/ext/merge_machines_native/ffi/src/data.rs +44 -0
- data/ext/merge_machines_native/ffi/src/gcounter.rs +138 -0
- data/ext/merge_machines_native/ffi/src/gset.rs +187 -0
- data/ext/merge_machines_native/ffi/src/hash.rs +33 -0
- data/ext/merge_machines_native/ffi/src/hybrid.rs +110 -0
- data/ext/merge_machines_native/ffi/src/lib.rs +221 -0
- data/ext/merge_machines_native/ffi/src/logical.rs +89 -0
- data/ext/merge_machines_native/ffi/src/lww.rs +360 -0
- data/ext/merge_machines_native/ffi/src/names.rs +82 -0
- data/ext/merge_machines_native/ffi/src/orset.rs +240 -0
- data/ext/merge_machines_native/ffi/src/pncounter.rs +143 -0
- data/ext/merge_machines_native/ffi/src/shape.rs +116 -0
- data/ext/merge_machines_native/ffi/src/wall.rs +37 -0
- data/lib/merge_machines/version.rb +5 -0
- data/lib/merge_machines.rb +8 -0
- data/sig/merge_machines.rbs +238 -0
- metadata +98 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Conflict-free replicated data types and the clocks that stamp them. Every
|
|
2
|
+
# class here is defined by the native extension, which binds the
|
|
3
|
+
# `merge-machines` crate.
|
|
4
|
+
#
|
|
5
|
+
# Every state merges commutatively, associatively and idempotently, so replicas
|
|
6
|
+
# converge without a coordinator and without ordering their messages. Nothing
|
|
7
|
+
# here talks to a network: the caller owns the transport and the replica ids.
|
|
8
|
+
module MergeMachines
|
|
9
|
+
VERSION: String
|
|
10
|
+
|
|
11
|
+
# Nanoseconds in a second. Wall-clock readings cross this boundary as seconds
|
|
12
|
+
# and move through the clocks as nanoseconds.
|
|
13
|
+
NS_PER_SECOND: Integer
|
|
14
|
+
|
|
15
|
+
class Error < StandardError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Raised when a state needs a slot and every one of them is taken.
|
|
19
|
+
class Full < Error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Raised when a clock has issued the last stamp its counter can hold, by
|
|
23
|
+
# either clock.
|
|
24
|
+
class Exhausted < Error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A wall-clock reading in nanoseconds and the counter that breaks its ties.
|
|
28
|
+
class Timestamp < ::Data
|
|
29
|
+
attr_reader wall_ns: Integer
|
|
30
|
+
attr_reader counter: Integer
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Which clock stamps a register's writes.
|
|
34
|
+
type stamp_kind = :logical | :hybrid
|
|
35
|
+
|
|
36
|
+
# A counter that only grows, counted separately by each replica.
|
|
37
|
+
class GCounter
|
|
38
|
+
# `replicas` is 4, 8, 16 or 32.
|
|
39
|
+
def self.new: (replicas: Integer) -> instance
|
|
40
|
+
|
|
41
|
+
def self.load: (String blob) -> instance
|
|
42
|
+
|
|
43
|
+
def replicas: () -> Integer
|
|
44
|
+
|
|
45
|
+
# Adds `by` to `replica`'s own count. Raises `Full` when `replica` has no
|
|
46
|
+
# slot yet and every one of them is taken.
|
|
47
|
+
def increment: (replica: Integer, ?by: Integer) -> nil
|
|
48
|
+
|
|
49
|
+
# What `replica` has added, zero if it has no slot here.
|
|
50
|
+
def count: (replica: Integer) -> Integer
|
|
51
|
+
|
|
52
|
+
# Every replica's count.
|
|
53
|
+
def value: () -> Integer
|
|
54
|
+
|
|
55
|
+
# Folds `other` in. Raises `ArgumentError` on a different replica count.
|
|
56
|
+
def merge: (GCounter other) -> self
|
|
57
|
+
|
|
58
|
+
def dump: () -> String
|
|
59
|
+
def inspect: () -> String
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# A counter replicas can both raise and lower, from two that only grow.
|
|
63
|
+
class PNCounter
|
|
64
|
+
# `replicas` is 4, 8, 16 or 32.
|
|
65
|
+
def self.new: (replicas: Integer) -> instance
|
|
66
|
+
|
|
67
|
+
def self.load: (String blob) -> instance
|
|
68
|
+
|
|
69
|
+
def replicas: () -> Integer
|
|
70
|
+
|
|
71
|
+
def increment: (replica: Integer, ?by: Integer) -> nil
|
|
72
|
+
|
|
73
|
+
def decrement: (replica: Integer, ?by: Integer) -> nil
|
|
74
|
+
|
|
75
|
+
# Everything added, minus everything taken away, which may be negative.
|
|
76
|
+
def value: () -> Integer
|
|
77
|
+
|
|
78
|
+
def added: () -> Integer
|
|
79
|
+
def removed: () -> Integer
|
|
80
|
+
|
|
81
|
+
# Folds `other` in. Raises `ArgumentError` on a different replica count.
|
|
82
|
+
def merge: (PNCounter other) -> self
|
|
83
|
+
|
|
84
|
+
def dump: () -> String
|
|
85
|
+
def inspect: () -> String
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# One value, held by whichever write was stamped last, with the replica id as
|
|
89
|
+
# the tie-break.
|
|
90
|
+
class LwwRegister
|
|
91
|
+
# `stamp` picks which clock stamps the writes, defaulting to `:logical`.
|
|
92
|
+
def self.new: (?stamp: stamp_kind) -> instance
|
|
93
|
+
|
|
94
|
+
def self.load: (String blob) -> instance
|
|
95
|
+
|
|
96
|
+
def stamp_kind: () -> stamp_kind
|
|
97
|
+
|
|
98
|
+
# Writes `value` if `stamp` and `replica` beat the write already held.
|
|
99
|
+
# `stamp` is an Integer on a logical register and a `Timestamp` on a hybrid
|
|
100
|
+
# one. `value` must be UTF-8.
|
|
101
|
+
def set: (String value, replica: Integer, stamp: Integer | Timestamp) -> nil
|
|
102
|
+
|
|
103
|
+
# The value being held, `nil` until the first write.
|
|
104
|
+
def value: () -> String?
|
|
105
|
+
|
|
106
|
+
# The stamp of the write being held.
|
|
107
|
+
def stamp: () -> (Integer | Timestamp)
|
|
108
|
+
|
|
109
|
+
# The replica that wrote what is being held.
|
|
110
|
+
def replica: () -> Integer
|
|
111
|
+
|
|
112
|
+
# Folds `other` in. Raises `ArgumentError` on a register stamped by the
|
|
113
|
+
# other clock.
|
|
114
|
+
def merge: (LwwRegister other) -> self
|
|
115
|
+
|
|
116
|
+
def dump: () -> String
|
|
117
|
+
def inspect: () -> String
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# A set nothing is ever taken out of.
|
|
121
|
+
class GSet
|
|
122
|
+
# `capacity` is 64, 256 or 1024.
|
|
123
|
+
def self.new: (capacity: Integer) -> instance
|
|
124
|
+
|
|
125
|
+
def self.load: (String blob) -> instance
|
|
126
|
+
|
|
127
|
+
def capacity: () -> Integer
|
|
128
|
+
|
|
129
|
+
# Adds `key`, which is a no-op if it is already in. Raises `Full` when
|
|
130
|
+
# `key` is new and every slot is taken, `EncodingError` when it is not
|
|
131
|
+
# UTF-8.
|
|
132
|
+
def add: (String key) -> nil
|
|
133
|
+
|
|
134
|
+
def include?: (String key) -> bool
|
|
135
|
+
|
|
136
|
+
# The keys in the set, in no particular order.
|
|
137
|
+
def members: () -> Array[String]
|
|
138
|
+
|
|
139
|
+
def size: () -> Integer
|
|
140
|
+
def empty?: () -> bool
|
|
141
|
+
|
|
142
|
+
# Unions `other` in. Raises `ArgumentError` on a different capacity.
|
|
143
|
+
def merge: (GSet other) -> self
|
|
144
|
+
|
|
145
|
+
def dump: () -> String
|
|
146
|
+
def inspect: () -> String
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# A set keys can leave, where a concurrent add survives a remove.
|
|
150
|
+
class OrSet
|
|
151
|
+
# `capacity` is 64, 256 or 1024, and bounds the adds and the tombstones
|
|
152
|
+
# separately.
|
|
153
|
+
def self.new: (capacity: Integer) -> instance
|
|
154
|
+
|
|
155
|
+
def self.load: (String blob) -> instance
|
|
156
|
+
|
|
157
|
+
def capacity: () -> Integer
|
|
158
|
+
|
|
159
|
+
# Adds `key` under a tag of `replica` and the next counter that replica has
|
|
160
|
+
# not used here. Raises `Full` when every add slot is taken, `Exhausted`
|
|
161
|
+
# when `replica` has no counter left, `EncodingError` when `key` is not
|
|
162
|
+
# UTF-8.
|
|
163
|
+
def add: (String key, replica: Integer) -> nil
|
|
164
|
+
|
|
165
|
+
# Tombstones every tag this set has seen for `key`. Raises `Full` when the
|
|
166
|
+
# tombstones do not fit.
|
|
167
|
+
def remove: (String key) -> nil
|
|
168
|
+
|
|
169
|
+
def include?: (String key) -> bool
|
|
170
|
+
|
|
171
|
+
# The keys still in the set, each once, in no particular order.
|
|
172
|
+
def members: () -> Array[String]
|
|
173
|
+
|
|
174
|
+
def size: () -> Integer
|
|
175
|
+
def empty?: () -> bool
|
|
176
|
+
|
|
177
|
+
# Folds `other` in. Raises `ArgumentError` on a different capacity.
|
|
178
|
+
def merge: (OrSet other) -> self
|
|
179
|
+
|
|
180
|
+
def dump: () -> String
|
|
181
|
+
def inspect: () -> String
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Lamport's counter: ordering events without reading a clock.
|
|
185
|
+
class LogicalClock
|
|
186
|
+
def self.new: (replica: Integer) -> instance
|
|
187
|
+
|
|
188
|
+
def self.load: (String blob) -> instance
|
|
189
|
+
|
|
190
|
+
# The replica whose events this clock stamps.
|
|
191
|
+
def replica: () -> Integer
|
|
192
|
+
|
|
193
|
+
# The last stamp this clock issued.
|
|
194
|
+
def now: () -> Integer
|
|
195
|
+
|
|
196
|
+
# Stamps a local event. Raises `Exhausted` when the counter holds its
|
|
197
|
+
# highest value, which leaves the clock where it was.
|
|
198
|
+
def tick: () -> Integer
|
|
199
|
+
|
|
200
|
+
# Stamps the receipt of `observed`, a stamp from another replica. Raises
|
|
201
|
+
# `Exhausted` when it cannot be stamped after `observed`.
|
|
202
|
+
def observe: (Integer observed) -> Integer
|
|
203
|
+
|
|
204
|
+
def dump: () -> String
|
|
205
|
+
def inspect: () -> String
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# A wall-clock reading paired with a counter that breaks its ties, which never
|
|
209
|
+
# goes backwards.
|
|
210
|
+
#
|
|
211
|
+
# The reading comes from `Process::CLOCK_REALTIME`, which may step backwards.
|
|
212
|
+
# No deadline may be derived from it.
|
|
213
|
+
class HybridClock
|
|
214
|
+
def self.new: (replica: Integer) -> instance
|
|
215
|
+
|
|
216
|
+
def self.load: (String blob) -> instance
|
|
217
|
+
|
|
218
|
+
# The replica whose events this clock stamps.
|
|
219
|
+
def replica: () -> Integer
|
|
220
|
+
|
|
221
|
+
# The last timestamp this clock issued.
|
|
222
|
+
def now: () -> Timestamp
|
|
223
|
+
|
|
224
|
+
# Stamps a local event at `wall_ns`, defaulting to the realtime clock's
|
|
225
|
+
# reading. Raises `Exhausted` when the reading has not advanced and the
|
|
226
|
+
# counter that separates two events at one reading holds its highest value,
|
|
227
|
+
# which leaves the clock where it was.
|
|
228
|
+
def tick: (?wall_ns: Integer) -> Timestamp
|
|
229
|
+
|
|
230
|
+
# Stamps the receipt of the timestamp `wall_ns` and `counter` name, at the
|
|
231
|
+
# realtime clock's reading. Raises `Exhausted` when it cannot be stamped
|
|
232
|
+
# after the timestamp observed.
|
|
233
|
+
def observe: (wall_ns: Integer, counter: Integer) -> Timestamp
|
|
234
|
+
|
|
235
|
+
def dump: () -> String
|
|
236
|
+
def inspect: () -> String
|
|
237
|
+
end
|
|
238
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: merge_machines
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Abdelkader Boudih
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rb_sys
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.9'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.9'
|
|
26
|
+
description: Counters, a last-writer-wins register, grow-only and observed-remove
|
|
27
|
+
sets, and the Lamport and hybrid logical clocks that stamp them, bound to Ruby from
|
|
28
|
+
the no_std merge-machines crate. Every state merges commutatively, associatively
|
|
29
|
+
and idempotently, and encodes to a byte string other languages reading the same
|
|
30
|
+
crate can decode.
|
|
31
|
+
email:
|
|
32
|
+
- oss@seuros.com
|
|
33
|
+
executables: []
|
|
34
|
+
extensions:
|
|
35
|
+
- ext/merge_machines_native/extconf.rb
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- CHANGELOG.md
|
|
39
|
+
- Cargo.lock
|
|
40
|
+
- Cargo.toml
|
|
41
|
+
- LICENSE.txt
|
|
42
|
+
- README.md
|
|
43
|
+
- ext/merge_machines_native/core/Cargo.toml
|
|
44
|
+
- ext/merge_machines_native/core/src/gcounter.rs
|
|
45
|
+
- ext/merge_machines_native/core/src/gset.rs
|
|
46
|
+
- ext/merge_machines_native/core/src/hybrid.rs
|
|
47
|
+
- ext/merge_machines_native/core/src/lib.rs
|
|
48
|
+
- ext/merge_machines_native/core/src/logical.rs
|
|
49
|
+
- ext/merge_machines_native/core/src/lww.rs
|
|
50
|
+
- ext/merge_machines_native/core/src/orset.rs
|
|
51
|
+
- ext/merge_machines_native/core/src/pncounter.rs
|
|
52
|
+
- ext/merge_machines_native/extconf.rb
|
|
53
|
+
- ext/merge_machines_native/ffi/Cargo.toml
|
|
54
|
+
- ext/merge_machines_native/ffi/extconf.rb
|
|
55
|
+
- ext/merge_machines_native/ffi/src/data.rs
|
|
56
|
+
- ext/merge_machines_native/ffi/src/gcounter.rs
|
|
57
|
+
- ext/merge_machines_native/ffi/src/gset.rs
|
|
58
|
+
- ext/merge_machines_native/ffi/src/hash.rs
|
|
59
|
+
- ext/merge_machines_native/ffi/src/hybrid.rs
|
|
60
|
+
- ext/merge_machines_native/ffi/src/lib.rs
|
|
61
|
+
- ext/merge_machines_native/ffi/src/logical.rs
|
|
62
|
+
- ext/merge_machines_native/ffi/src/lww.rs
|
|
63
|
+
- ext/merge_machines_native/ffi/src/names.rs
|
|
64
|
+
- ext/merge_machines_native/ffi/src/orset.rs
|
|
65
|
+
- ext/merge_machines_native/ffi/src/pncounter.rs
|
|
66
|
+
- ext/merge_machines_native/ffi/src/shape.rs
|
|
67
|
+
- ext/merge_machines_native/ffi/src/wall.rs
|
|
68
|
+
- lib/merge_machines.rb
|
|
69
|
+
- lib/merge_machines/version.rb
|
|
70
|
+
- sig/merge_machines.rbs
|
|
71
|
+
homepage: https://github.com/seuros/merge_machines
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
source_code_uri: https://github.com/seuros/merge_machines
|
|
76
|
+
changelog_uri: https://github.com/seuros/merge_machines/blob/main/CHANGELOG.md
|
|
77
|
+
bug_tracker_uri: https://github.com/seuros/merge_machines/issues
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
cargo_crate_name: merge_machines_native
|
|
80
|
+
cargo_manifest_path: ext/merge_machines_native/ffi/Cargo.toml
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 4.0.0
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 4.0.20
|
|
96
|
+
specification_version: 4
|
|
97
|
+
summary: Conflict-free replicated data types for Ruby
|
|
98
|
+
test_files: []
|