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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +57 -0
  3. data/Cargo.lock +294 -0
  4. data/Cargo.toml +24 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +252 -0
  7. data/ext/merge_machines_native/core/Cargo.toml +24 -0
  8. data/ext/merge_machines_native/core/src/gcounter.rs +242 -0
  9. data/ext/merge_machines_native/core/src/gset.rs +181 -0
  10. data/ext/merge_machines_native/core/src/hybrid.rs +222 -0
  11. data/ext/merge_machines_native/core/src/lib.rs +221 -0
  12. data/ext/merge_machines_native/core/src/logical.rs +139 -0
  13. data/ext/merge_machines_native/core/src/lww.rs +213 -0
  14. data/ext/merge_machines_native/core/src/orset.rs +334 -0
  15. data/ext/merge_machines_native/core/src/pncounter.rs +165 -0
  16. data/ext/merge_machines_native/extconf.rb +3 -0
  17. data/ext/merge_machines_native/ffi/Cargo.toml +20 -0
  18. data/ext/merge_machines_native/ffi/extconf.rb +21 -0
  19. data/ext/merge_machines_native/ffi/src/data.rs +44 -0
  20. data/ext/merge_machines_native/ffi/src/gcounter.rs +138 -0
  21. data/ext/merge_machines_native/ffi/src/gset.rs +187 -0
  22. data/ext/merge_machines_native/ffi/src/hash.rs +33 -0
  23. data/ext/merge_machines_native/ffi/src/hybrid.rs +110 -0
  24. data/ext/merge_machines_native/ffi/src/lib.rs +221 -0
  25. data/ext/merge_machines_native/ffi/src/logical.rs +89 -0
  26. data/ext/merge_machines_native/ffi/src/lww.rs +360 -0
  27. data/ext/merge_machines_native/ffi/src/names.rs +82 -0
  28. data/ext/merge_machines_native/ffi/src/orset.rs +240 -0
  29. data/ext/merge_machines_native/ffi/src/pncounter.rs +143 -0
  30. data/ext/merge_machines_native/ffi/src/shape.rs +116 -0
  31. data/ext/merge_machines_native/ffi/src/wall.rs +37 -0
  32. data/lib/merge_machines/version.rb +5 -0
  33. data/lib/merge_machines.rb +8 -0
  34. data/sig/merge_machines.rbs +238 -0
  35. 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: []