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,221 @@
|
|
|
1
|
+
#![cfg_attr(not(feature = "std"), no_std)]
|
|
2
|
+
|
|
3
|
+
//! Conflict-free replicated data types and the clocks they need.
|
|
4
|
+
//!
|
|
5
|
+
//! Every type here has a [`Merge::merge`] that is commutative, associative and
|
|
6
|
+
//! idempotent, so replicas that have seen the same updates hold the same state
|
|
7
|
+
//! no matter what order the updates arrived in, or how many times.
|
|
8
|
+
//!
|
|
9
|
+
//! Nothing here talks to a network and nothing here reads a clock. The caller
|
|
10
|
+
//! owns the transport, the replica ids, the tags, and every clock reading.
|
|
11
|
+
//!
|
|
12
|
+
//! # Which clock
|
|
13
|
+
//!
|
|
14
|
+
//! [`LogicalClock`] counts events. [`HybridClock`] pairs a wall-clock reading
|
|
15
|
+
//! with a counter, where a wall-clock reading means physical time that two
|
|
16
|
+
//! machines can compare: nanoseconds since the Unix epoch, which jumps
|
|
17
|
+
//! backwards when the host's clock is corrected. That is not the monotonic
|
|
18
|
+
//! reading you measure elapsed time with, and no deadline, timeout or budget
|
|
19
|
+
//! may be derived from it. Those belong to a monotonic clock, which this crate
|
|
20
|
+
//! does not have and does not want.
|
|
21
|
+
//!
|
|
22
|
+
//! State is fixed-size: capacities are const generics, and a state with no free
|
|
23
|
+
//! slot left reports [`CapacityError`] rather than allocating. A clock whose
|
|
24
|
+
//! counter has run out reports [`ClockExhausted`] rather than reissuing the
|
|
25
|
+
//! timestamp it last gave away.
|
|
26
|
+
//!
|
|
27
|
+
//! # Putting state on a wire
|
|
28
|
+
//!
|
|
29
|
+
//! Every state encodes to a fixed-length frame of its own
|
|
30
|
+
//! `ENCODED_LEN` bytes, led by a tag naming the structure and, where there is
|
|
31
|
+
//! one, a field naming the const generic. `decode` and `decode_into` reject a
|
|
32
|
+
//! short buffer, another structure's tag, and a frame written at another shape.
|
|
33
|
+
|
|
34
|
+
#[cfg(feature = "alloc")]
|
|
35
|
+
extern crate alloc;
|
|
36
|
+
|
|
37
|
+
/// Compiles and runs the README's examples as doctests.
|
|
38
|
+
#[cfg(doctest)]
|
|
39
|
+
#[doc = include_str!("../README.md")]
|
|
40
|
+
struct ReadmeDoctests;
|
|
41
|
+
|
|
42
|
+
pub mod gcounter;
|
|
43
|
+
pub mod gset;
|
|
44
|
+
pub mod hybrid;
|
|
45
|
+
pub mod logical;
|
|
46
|
+
pub mod lww;
|
|
47
|
+
pub mod orset;
|
|
48
|
+
pub mod pncounter;
|
|
49
|
+
|
|
50
|
+
pub use gcounter::GCounter;
|
|
51
|
+
pub use gset::GSet;
|
|
52
|
+
pub use hybrid::{HybridClock, HybridTimestamp};
|
|
53
|
+
pub use logical::LogicalClock;
|
|
54
|
+
pub use lww::{LwwRegister, STAMP_HYBRID, STAMP_LOGICAL, Stamp};
|
|
55
|
+
pub use orset::{OrSet, Tag, Tagged};
|
|
56
|
+
pub use pncounter::PNCounter;
|
|
57
|
+
|
|
58
|
+
/// Which replica an update came from.
|
|
59
|
+
pub type ReplicaId = u64;
|
|
60
|
+
|
|
61
|
+
/// A state that can absorb another replica's state.
|
|
62
|
+
pub trait Merge {
|
|
63
|
+
/// Why this state could not absorb the other one.
|
|
64
|
+
type Error;
|
|
65
|
+
|
|
66
|
+
/// Folds `other` into this state.
|
|
67
|
+
///
|
|
68
|
+
/// # Errors
|
|
69
|
+
///
|
|
70
|
+
/// Returns [`CapacityError`] when the union needs more slots than this
|
|
71
|
+
/// state has. Nothing is rolled back: whatever fit stayed, and merging
|
|
72
|
+
/// again after raising the capacity completes the job.
|
|
73
|
+
fn merge(&mut self, other: &Self) -> Result<(), Self::Error>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// The union needed a slot and there was none left.
|
|
77
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
78
|
+
pub struct CapacityError;
|
|
79
|
+
|
|
80
|
+
/// A clock has issued the last timestamp its counter can hold.
|
|
81
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
82
|
+
pub struct ClockExhausted;
|
|
83
|
+
|
|
84
|
+
impl core::fmt::Display for ClockExhausted {
|
|
85
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
86
|
+
f.write_str("no timestamp left")
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
impl core::error::Error for ClockExhausted {}
|
|
91
|
+
|
|
92
|
+
impl core::fmt::Display for CapacityError {
|
|
93
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
94
|
+
f.write_str("no free slot")
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl core::error::Error for CapacityError {}
|
|
99
|
+
|
|
100
|
+
/// Why a state could not be read from or written to a byte buffer.
|
|
101
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
102
|
+
pub enum CodecError {
|
|
103
|
+
/// The buffer is shorter than the encoded form needs.
|
|
104
|
+
Truncated,
|
|
105
|
+
/// The leading byte names a different structure.
|
|
106
|
+
Tag,
|
|
107
|
+
/// The encoded shape does not match the target type's.
|
|
108
|
+
Parameters,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
impl core::fmt::Display for CodecError {
|
|
112
|
+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
113
|
+
f.write_str(match self {
|
|
114
|
+
Self::Truncated => "buffer too short",
|
|
115
|
+
Self::Tag => "wrong structure tag",
|
|
116
|
+
Self::Parameters => "shape mismatch",
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
impl core::error::Error for CodecError {}
|
|
122
|
+
|
|
123
|
+
pub(crate) const TAG_LOGICAL_CLOCK: u8 = 0x51;
|
|
124
|
+
pub(crate) const TAG_HYBRID_CLOCK: u8 = 0x52;
|
|
125
|
+
pub(crate) const TAG_GCOUNTER: u8 = 0x61;
|
|
126
|
+
pub(crate) const TAG_PNCOUNTER: u8 = 0x62;
|
|
127
|
+
pub(crate) const TAG_LWW_REGISTER: u8 = 0x71;
|
|
128
|
+
pub(crate) const TAG_GSET: u8 = 0x81;
|
|
129
|
+
pub(crate) const TAG_ORSET: u8 = 0x82;
|
|
130
|
+
|
|
131
|
+
/// Where a frame's leading structure tag sits.
|
|
132
|
+
pub(crate) const TAG: usize = 0;
|
|
133
|
+
|
|
134
|
+
/// Bytes a frame spends on its leading structure tag.
|
|
135
|
+
pub(crate) const TAG_LEN: usize = 1;
|
|
136
|
+
|
|
137
|
+
/// Bytes a frame spends naming the shape it was written at.
|
|
138
|
+
pub(crate) const SHAPE_LEN: usize = 2;
|
|
139
|
+
|
|
140
|
+
/// Bytes a frame spends saying how many of its slot records carry a slot.
|
|
141
|
+
pub(crate) const COUNT_LEN: usize = 2;
|
|
142
|
+
|
|
143
|
+
/// Bytes a frame spends on one 64-bit field.
|
|
144
|
+
pub(crate) const FIELD_LEN: usize = 8;
|
|
145
|
+
|
|
146
|
+
/// A frame's 64-bit field, read from `field`, which is [`FIELD_LEN`] long.
|
|
147
|
+
pub(crate) const fn read_u64(field: &[u8]) -> u64 {
|
|
148
|
+
let mut bytes = [0_u8; FIELD_LEN];
|
|
149
|
+
bytes.copy_from_slice(field);
|
|
150
|
+
u64::from_le_bytes(bytes)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// The low two bytes of `value`, as a frame's shape or count field.
|
|
154
|
+
pub(crate) const fn small_field(value: usize) -> [u8; SHAPE_LEN] {
|
|
155
|
+
let [low, high, ..] = value.to_le_bytes();
|
|
156
|
+
[low, high]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// A frame's shape or count field, read from `field`, which is [`SHAPE_LEN`]
|
|
160
|
+
/// long.
|
|
161
|
+
pub(crate) fn read_small(field: &[u8]) -> usize {
|
|
162
|
+
let mut bytes = [0_u8; SHAPE_LEN];
|
|
163
|
+
bytes.copy_from_slice(field);
|
|
164
|
+
usize::from(u16::from_le_bytes(bytes))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Writes a frame of `len` bytes at the front of `out` through `fill`,
|
|
168
|
+
/// returning the number of bytes written.
|
|
169
|
+
pub(crate) fn write_into(
|
|
170
|
+
out: &mut [u8],
|
|
171
|
+
len: usize,
|
|
172
|
+
fill: impl FnOnce(&mut [u8]),
|
|
173
|
+
) -> Result<usize, CodecError> {
|
|
174
|
+
let Some(frame) = out.get_mut(..len) else {
|
|
175
|
+
return Err(CodecError::Truncated);
|
|
176
|
+
};
|
|
177
|
+
fill(frame);
|
|
178
|
+
Ok(len)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// A frame of exactly `len` bytes, filled by `fill`.
|
|
182
|
+
#[cfg(feature = "alloc")]
|
|
183
|
+
pub(crate) fn boxed_frame(len: usize, fill: impl FnOnce(&mut [u8])) -> alloc::boxed::Box<[u8]> {
|
|
184
|
+
let mut frame = alloc::vec![0_u8; len].into_boxed_slice();
|
|
185
|
+
fill(&mut frame);
|
|
186
|
+
frame
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// The `len`-byte frame `bytes` leads with, which `tag` has to name.
|
|
190
|
+
pub(crate) fn tagged_frame(bytes: &[u8], len: usize, tag: u8) -> Result<&[u8], CodecError> {
|
|
191
|
+
let Some(frame) = bytes.get(..len) else {
|
|
192
|
+
return Err(CodecError::Truncated);
|
|
193
|
+
};
|
|
194
|
+
if frame[TAG] != tag {
|
|
195
|
+
return Err(CodecError::Tag);
|
|
196
|
+
}
|
|
197
|
+
Ok(frame)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Whether two slot arrays hold the same elements, ignoring where in the array
|
|
201
|
+
/// each one sits.
|
|
202
|
+
pub(crate) fn same_elements<T: PartialEq>(left: &[Option<T>], right: &[Option<T>]) -> bool {
|
|
203
|
+
let contained =
|
|
204
|
+
|haystack: &[Option<T>], needle: &T| haystack.iter().flatten().any(|held| held == needle);
|
|
205
|
+
|
|
206
|
+
left.iter()
|
|
207
|
+
.flatten()
|
|
208
|
+
.all(|element| contained(right, element))
|
|
209
|
+
&& right
|
|
210
|
+
.iter()
|
|
211
|
+
.flatten()
|
|
212
|
+
.all(|element| contained(left, element))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Index of the first slot nothing has claimed yet.
|
|
216
|
+
pub(crate) fn free_slot<T>(slots: &[Option<T>]) -> Option<usize> {
|
|
217
|
+
slots.iter().position(Option::is_none)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#[cfg(test)]
|
|
221
|
+
mod tests;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//! Lamport's logical clock: ordering events without reading a clock.
|
|
2
|
+
|
|
3
|
+
#[cfg(feature = "alloc")]
|
|
4
|
+
use crate::boxed_frame;
|
|
5
|
+
use crate::{
|
|
6
|
+
ClockExhausted, CodecError, FIELD_LEN, TAG, TAG_LEN, TAG_LOGICAL_CLOCK, read_u64, tagged_frame,
|
|
7
|
+
write_into,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/// Where the counter sits in a frame.
|
|
11
|
+
const COUNTER: core::ops::Range<usize> = TAG_LEN..TAG_LEN + FIELD_LEN;
|
|
12
|
+
|
|
13
|
+
/// A counter that orders events across replicas, with no clock involved.
|
|
14
|
+
///
|
|
15
|
+
/// ```
|
|
16
|
+
/// use merge_machines::{ClockExhausted, LogicalClock};
|
|
17
|
+
///
|
|
18
|
+
/// let mut sender = LogicalClock::new();
|
|
19
|
+
/// let mut receiver = LogicalClock::new();
|
|
20
|
+
///
|
|
21
|
+
/// let sent_at = sender.tick()?;
|
|
22
|
+
/// assert_eq!(sent_at, 1);
|
|
23
|
+
///
|
|
24
|
+
/// receiver.tick()?;
|
|
25
|
+
/// receiver.tick()?;
|
|
26
|
+
///
|
|
27
|
+
/// // Receiving is stamped after both the local history and the send.
|
|
28
|
+
/// assert_eq!(receiver.observe(sent_at), Ok(3));
|
|
29
|
+
/// assert_eq!(receiver.observe(99), Ok(100));
|
|
30
|
+
///
|
|
31
|
+
/// // A counter with nothing left reports it instead of repeating itself.
|
|
32
|
+
/// assert_eq!(receiver.observe(u64::MAX), Err(ClockExhausted));
|
|
33
|
+
/// assert_eq!(receiver.now(), 100);
|
|
34
|
+
/// # Ok::<(), ClockExhausted>(())
|
|
35
|
+
/// ```
|
|
36
|
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
37
|
+
pub struct LogicalClock {
|
|
38
|
+
counter: u64,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl LogicalClock {
|
|
42
|
+
#[must_use]
|
|
43
|
+
pub const fn new() -> Self {
|
|
44
|
+
Self { counter: 0 }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// The last timestamp this clock issued.
|
|
48
|
+
#[must_use]
|
|
49
|
+
pub const fn now(&self) -> u64 {
|
|
50
|
+
self.counter
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Stamps a local event.
|
|
54
|
+
///
|
|
55
|
+
/// # Errors
|
|
56
|
+
///
|
|
57
|
+
/// [`ClockExhausted`] when the counter holds its highest value, which
|
|
58
|
+
/// leaves the clock where it was.
|
|
59
|
+
pub const fn tick(&mut self) -> Result<u64, ClockExhausted> {
|
|
60
|
+
self.issue(self.counter)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Stamps the receipt of `observed`, a timestamp from another replica.
|
|
64
|
+
///
|
|
65
|
+
/// # Errors
|
|
66
|
+
///
|
|
67
|
+
/// The same as [`tick`](Self::tick), and the observed timestamp is not
|
|
68
|
+
/// adopted either.
|
|
69
|
+
pub const fn observe(&mut self, observed: u64) -> Result<u64, ClockExhausted> {
|
|
70
|
+
self.issue(if observed > self.counter {
|
|
71
|
+
observed
|
|
72
|
+
} else {
|
|
73
|
+
self.counter
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Issues the timestamp that follows `base`.
|
|
78
|
+
const fn issue(&mut self, base: u64) -> Result<u64, ClockExhausted> {
|
|
79
|
+
let Some(next) = base.checked_add(1) else {
|
|
80
|
+
return Err(ClockExhausted);
|
|
81
|
+
};
|
|
82
|
+
self.counter = next;
|
|
83
|
+
Ok(next)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Length of the [`encode`](Self::encode) frame.
|
|
87
|
+
pub const ENCODED_LEN: usize = TAG_LEN + FIELD_LEN;
|
|
88
|
+
|
|
89
|
+
/// Writes the clock into `out`, returning the number of bytes written.
|
|
90
|
+
///
|
|
91
|
+
/// # Errors
|
|
92
|
+
///
|
|
93
|
+
/// [`CodecError::Truncated`] when `out` is shorter than
|
|
94
|
+
/// [`ENCODED_LEN`](Self::ENCODED_LEN).
|
|
95
|
+
pub fn encode(&self, out: &mut [u8]) -> Result<usize, CodecError> {
|
|
96
|
+
write_into(out, Self::ENCODED_LEN, |frame| self.write_frame(frame))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// The clock's frame, exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
|
|
100
|
+
#[cfg(feature = "alloc")]
|
|
101
|
+
#[must_use]
|
|
102
|
+
pub fn encoded(&self) -> alloc::boxed::Box<[u8]> {
|
|
103
|
+
boxed_frame(Self::ENCODED_LEN, |frame| self.write_frame(frame))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Fills a frame of exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
|
|
107
|
+
fn write_frame(self, frame: &mut [u8]) {
|
|
108
|
+
frame[TAG] = TAG_LOGICAL_CLOCK;
|
|
109
|
+
frame[COUNTER].copy_from_slice(&self.counter.to_le_bytes());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Reads a clock written by [`encode`](Self::encode).
|
|
113
|
+
///
|
|
114
|
+
/// # Errors
|
|
115
|
+
///
|
|
116
|
+
/// [`CodecError::Truncated`] when `bytes` is too short, [`CodecError::Tag`]
|
|
117
|
+
/// when it holds another structure.
|
|
118
|
+
pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
|
|
119
|
+
let mut clock = Self::new();
|
|
120
|
+
clock.decode_into(bytes)?;
|
|
121
|
+
Ok(clock)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Reads a frame over a clock the caller already holds.
|
|
125
|
+
///
|
|
126
|
+
/// The clock is untouched when the frame is rejected.
|
|
127
|
+
///
|
|
128
|
+
/// # Errors
|
|
129
|
+
///
|
|
130
|
+
/// The same as [`decode`](Self::decode).
|
|
131
|
+
pub fn decode_into(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
|
|
132
|
+
let frame = tagged_frame(bytes, Self::ENCODED_LEN, TAG_LOGICAL_CLOCK)?;
|
|
133
|
+
self.counter = read_u64(&frame[COUNTER]);
|
|
134
|
+
Ok(())
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[cfg(test)]
|
|
139
|
+
mod tests;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
//! Last writer wins, with the replica id as the tie-break.
|
|
2
|
+
|
|
3
|
+
#[cfg(feature = "alloc")]
|
|
4
|
+
use crate::boxed_frame;
|
|
5
|
+
use crate::{
|
|
6
|
+
CodecError, FIELD_LEN, Merge, ReplicaId, SHAPE_LEN, TAG, TAG_LEN, TAG_LWW_REGISTER, read_small,
|
|
7
|
+
read_u64, small_field, tagged_frame, write_into,
|
|
8
|
+
};
|
|
9
|
+
use core::convert::Infallible;
|
|
10
|
+
|
|
11
|
+
/// A stamp naming one 64-bit counter, as [`LogicalClock`](crate::LogicalClock)
|
|
12
|
+
/// issues.
|
|
13
|
+
pub const STAMP_LOGICAL: u8 = 0x01;
|
|
14
|
+
|
|
15
|
+
/// A stamp naming a wall-clock reading and its counter, as
|
|
16
|
+
/// [`HybridClock`](crate::HybridClock) issues.
|
|
17
|
+
pub const STAMP_HYBRID: u8 = 0x02;
|
|
18
|
+
|
|
19
|
+
/// A stamp a register's frame can carry.
|
|
20
|
+
pub trait Stamp: Copy + Default + Ord {
|
|
21
|
+
/// Byte naming this stamp in a register frame.
|
|
22
|
+
const KIND: u8;
|
|
23
|
+
|
|
24
|
+
/// Bytes this stamp occupies in a register frame.
|
|
25
|
+
const ENCODED_LEN: usize;
|
|
26
|
+
|
|
27
|
+
/// Writes this stamp into `field`, which is
|
|
28
|
+
/// [`ENCODED_LEN`](Self::ENCODED_LEN) long.
|
|
29
|
+
fn write(self, field: &mut [u8]);
|
|
30
|
+
|
|
31
|
+
/// Reads a stamp from `field`, which is
|
|
32
|
+
/// [`ENCODED_LEN`](Self::ENCODED_LEN) long.
|
|
33
|
+
fn read(field: &[u8]) -> Self;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl Stamp for u64 {
|
|
37
|
+
const KIND: u8 = STAMP_LOGICAL;
|
|
38
|
+
const ENCODED_LEN: usize = FIELD_LEN;
|
|
39
|
+
|
|
40
|
+
fn write(self, field: &mut [u8]) {
|
|
41
|
+
field.copy_from_slice(&self.to_le_bytes());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn read(field: &[u8]) -> Self {
|
|
45
|
+
read_u64(field)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Where the stamp kind sits in a frame.
|
|
50
|
+
const STAMP_KIND: core::ops::Range<usize> = TAG_LEN..TAG_LEN + SHAPE_LEN;
|
|
51
|
+
|
|
52
|
+
/// Where the value sits in a frame.
|
|
53
|
+
const VALUE: core::ops::Range<usize> = STAMP_KIND.end..STAMP_KIND.end + FIELD_LEN;
|
|
54
|
+
|
|
55
|
+
/// Where the writing replica sits in a frame.
|
|
56
|
+
const REPLICA: core::ops::Range<usize> = VALUE.end..VALUE.end + FIELD_LEN;
|
|
57
|
+
|
|
58
|
+
/// One value, held by whichever write was stamped last.
|
|
59
|
+
///
|
|
60
|
+
/// ```
|
|
61
|
+
/// use merge_machines::{LwwRegister, Merge};
|
|
62
|
+
///
|
|
63
|
+
/// let mut left = LwwRegister::new(10_u32, 4_u64, 1);
|
|
64
|
+
/// let right = LwwRegister::new(20, 9, 2);
|
|
65
|
+
///
|
|
66
|
+
/// assert_eq!(left.merge(&right), Ok(()));
|
|
67
|
+
/// assert_eq!(*left.value(), 20);
|
|
68
|
+
///
|
|
69
|
+
/// // A stamp from before the current one changes nothing.
|
|
70
|
+
/// left.set(30, 1, 1);
|
|
71
|
+
/// assert_eq!(*left.value(), 20);
|
|
72
|
+
///
|
|
73
|
+
/// // Equal stamps go to the higher replica id, from either side.
|
|
74
|
+
/// let mut low = LwwRegister::new(1_u32, 5_u64, 1);
|
|
75
|
+
/// let high = LwwRegister::new(2, 5, 2);
|
|
76
|
+
/// assert_eq!(low.merge(&high), Ok(()));
|
|
77
|
+
/// assert_eq!(*low.value(), 2);
|
|
78
|
+
/// ```
|
|
79
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
80
|
+
pub struct LwwRegister<T, S> {
|
|
81
|
+
value: T,
|
|
82
|
+
stamp: S,
|
|
83
|
+
replica: ReplicaId,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl<T: Clone, S: Ord + Copy> LwwRegister<T, S> {
|
|
87
|
+
#[must_use]
|
|
88
|
+
pub const fn new(value: T, stamp: S, replica: ReplicaId) -> Self {
|
|
89
|
+
Self {
|
|
90
|
+
value,
|
|
91
|
+
stamp,
|
|
92
|
+
replica,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Writes `value` if `(stamp, replica)` beats the write already held.
|
|
97
|
+
pub fn set(&mut self, value: T, stamp: S, replica: ReplicaId) {
|
|
98
|
+
if (stamp, replica) > (self.stamp, self.replica) {
|
|
99
|
+
self.value = value;
|
|
100
|
+
self.stamp = stamp;
|
|
101
|
+
self.replica = replica;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[must_use]
|
|
106
|
+
pub const fn value(&self) -> &T {
|
|
107
|
+
&self.value
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// The stamp of the write being held.
|
|
111
|
+
#[must_use]
|
|
112
|
+
pub const fn stamp(&self) -> &S {
|
|
113
|
+
&self.stamp
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// The replica that wrote what is being held.
|
|
117
|
+
#[must_use]
|
|
118
|
+
pub const fn replica(&self) -> ReplicaId {
|
|
119
|
+
self.replica
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
impl<S: Stamp> LwwRegister<u64, S> {
|
|
124
|
+
/// Length of the [`encode`](Self::encode) frame.
|
|
125
|
+
pub const ENCODED_LEN: usize = REPLICA.end + S::ENCODED_LEN;
|
|
126
|
+
|
|
127
|
+
/// Where the stamp sits in a frame.
|
|
128
|
+
const STAMP: core::ops::Range<usize> = REPLICA.end..Self::ENCODED_LEN;
|
|
129
|
+
|
|
130
|
+
/// A register holding nothing, for [`decode`](Self::decode) to read a frame
|
|
131
|
+
/// over.
|
|
132
|
+
fn empty() -> Self {
|
|
133
|
+
Self {
|
|
134
|
+
value: 0,
|
|
135
|
+
stamp: S::default(),
|
|
136
|
+
replica: 0,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Writes the register into `out`, returning the number of bytes written.
|
|
141
|
+
///
|
|
142
|
+
/// # Errors
|
|
143
|
+
///
|
|
144
|
+
/// [`CodecError::Truncated`] when `out` is shorter than
|
|
145
|
+
/// [`ENCODED_LEN`](Self::ENCODED_LEN).
|
|
146
|
+
pub fn encode(&self, out: &mut [u8]) -> Result<usize, CodecError> {
|
|
147
|
+
write_into(out, Self::ENCODED_LEN, |frame| self.write_frame(frame))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// The register's frame, exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
|
|
151
|
+
#[cfg(feature = "alloc")]
|
|
152
|
+
#[must_use]
|
|
153
|
+
pub fn encoded(&self) -> alloc::boxed::Box<[u8]> {
|
|
154
|
+
boxed_frame(Self::ENCODED_LEN, |frame| self.write_frame(frame))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Fills a frame of exactly [`ENCODED_LEN`](Self::ENCODED_LEN) bytes.
|
|
158
|
+
fn write_frame(&self, frame: &mut [u8]) {
|
|
159
|
+
frame[TAG] = TAG_LWW_REGISTER;
|
|
160
|
+
frame[STAMP_KIND].copy_from_slice(&small_field(usize::from(S::KIND)));
|
|
161
|
+
frame[VALUE].copy_from_slice(&self.value.to_le_bytes());
|
|
162
|
+
frame[REPLICA].copy_from_slice(&self.replica.to_le_bytes());
|
|
163
|
+
self.stamp.write(&mut frame[Self::STAMP]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Reads a register written by [`encode`](Self::encode).
|
|
167
|
+
///
|
|
168
|
+
/// # Errors
|
|
169
|
+
///
|
|
170
|
+
/// [`CodecError::Truncated`] when `bytes` is too short, [`CodecError::Tag`]
|
|
171
|
+
/// when it holds another structure, [`CodecError::Parameters`] when it was
|
|
172
|
+
/// written with another kind of stamp.
|
|
173
|
+
pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
|
|
174
|
+
let mut register = Self::empty();
|
|
175
|
+
register.decode_into(bytes)?;
|
|
176
|
+
Ok(register)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Reads a frame over a register the caller already holds.
|
|
180
|
+
///
|
|
181
|
+
/// The register is untouched when the frame is rejected.
|
|
182
|
+
///
|
|
183
|
+
/// # Errors
|
|
184
|
+
///
|
|
185
|
+
/// The same as [`decode`](Self::decode).
|
|
186
|
+
pub fn decode_into(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
|
|
187
|
+
let frame = tagged_frame(bytes, Self::ENCODED_LEN, TAG_LWW_REGISTER)?;
|
|
188
|
+
if read_small(&frame[STAMP_KIND]) != usize::from(S::KIND) {
|
|
189
|
+
return Err(CodecError::Parameters);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
self.value = read_u64(&frame[VALUE]);
|
|
193
|
+
self.replica = read_u64(&frame[REPLICA]);
|
|
194
|
+
self.stamp = S::read(&frame[Self::STAMP]);
|
|
195
|
+
Ok(())
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
impl<T: Clone, S: Ord + Copy> Merge for LwwRegister<T, S> {
|
|
200
|
+
type Error = Infallible;
|
|
201
|
+
|
|
202
|
+
fn merge(&mut self, other: &Self) -> Result<(), Self::Error> {
|
|
203
|
+
if (other.stamp, other.replica) > (self.stamp, self.replica) {
|
|
204
|
+
self.value.clone_from(&other.value);
|
|
205
|
+
self.stamp = other.stamp;
|
|
206
|
+
self.replica = other.replica;
|
|
207
|
+
}
|
|
208
|
+
Ok(())
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[cfg(test)]
|
|
213
|
+
mod tests;
|