1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
use std::sync::atomic::{AtomicU64, Ordering};
use wdev::Device;
use wrecord::{HEADER_SIZE, MAX_FILLER_BYTES, RecordHeader, RecordMut};
use super::HybridLog;
use crate::error::{Error, Result};
impl<D: Device> HybridLog<D> {
/// 尝试在可变区原位更新记录的值(基于 FillerWords 与动态松弛,严格对标 Tsavorite TrySetPinnedValueSpan & InternalRMW 原位更新语义)
///
/// 若记录处于内存可变区且新值长度在物理容量容纳范围内(val_len + filler_bytes),
/// 校验 expected_key 匹配后直接原位覆写并调整松弛填充,零追加、零换页、零 I/O。
pub fn try_update_in_place(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if rec_mut.is_tombstone() || !rec_mut.can_update_with_slack(new_val.len()) {
return Ok(false);
}
rec_mut.update_value_with_slack(new_val)?;
Ok(true)
})
}
/// 尝试在可变区原位设置墓碑标记(严格对标 libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/Implementation/InternalDelete.cs:InPlaceDeleter)
///
/// 若记录处于内存可变区且非墓碑记录,校验 expected_key 匹配后直接原位标记墓碑并置位 MODIFIED,
/// 零追加、零换页、零哈希表 CAS。
pub fn try_set_tombstone_in_place(&self, addr: u64, expected_key: &[u8]) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if rec_mut.is_tombstone() {
return Ok(false);
}
rec_mut.set_tombstone(true);
rec_mut.set_modified(true);
Ok(true)
})
}
/// 尝试在可变区原位读-改-写记录的值(严格对标 libs/server/Storage/Functions/MainStore/RMWMethods.cs:InPlaceUpdaterWorker & InternalRMW.cs)
///
/// - 若记录处于内存可变区且非墓碑记录,并校验键与 `expected_key` 匹配(使用 SIMD 高效比对);
/// - 在持有页面写锁期间,向闭包 `f` 暴露底层物理内存可变切片 `&mut [u8]` 执行就地读-改-写;
/// - 若闭包返回 `Some(r)` 说明原位修改成功,返回 `Ok(Some(r))`;
/// - 若记录为墓碑、键不匹配、处于只读区或闭包返回 `None`(如因长度变更),安全返回
/// `Ok(None)` 供调用方降级走 RCU 追加写。
pub fn try_modify_record_in_place<R>(
&self,
addr: u64,
expected_key: &[u8],
f: impl FnOnce(&mut [u8]) -> Option<R>,
) -> Result<Option<R>> {
self.with_mutable_record(addr, expected_key, None, |rec_mut| {
if rec_mut.is_tombstone() {
return Ok(None);
}
let r = f(rec_mut.value_mut());
if r.is_some() {
rec_mut.set_modified(true);
}
Ok(r)
})
}
/// 尝试在可变区链内原地复活墓碑记录(严格对照 C# Garnet InternalUpsert.cs:127 TryRevivifyInChain & RecordDataHeader.cs:FillerWords)
///
/// 若记录处于内存可变区且为墓碑记录,校验 expected_key 匹配且物理容量(val_capacity)足以容纳新值的前提下,
/// 原子覆写值并清除墓碑标记,富余空间自动吸纳转换为高精度松弛填充,无需分配新 Tail 槽位、无需换页、无需修改哈希索引指针。
pub fn try_revivify_in_chain(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
// 必须为墓碑记录,且富余空间可以松弛填充形式吸纳(val_capacity - new_val <= MAX_FILLER_BYTES)
if !rec_mut.is_tombstone()
|| new_val.len() > rec_mut.val_capacity()
|| (rec_mut.val_capacity() - new_val.len()) > MAX_FILLER_BYTES
{
return Ok(false);
}
rec_mut.revivify_with_slack(new_val)?;
Ok(true)
})
}
/// 在复活池回收的物理槽位上就地覆写记录(严格对照 C# Garnet BlockAllocate.cs:77-80 TryTakeFreeRecord)
///
/// 若槽位大小超出记录对齐逻辑尺寸,且富余空间足以容纳新的记录头,自动填充 PadRecord;
/// 富余不足一个头时吸纳为当前记录的松弛填充(filler_bytes,词粒度),使整条记录物理尺寸
/// 精确覆盖整个 `slot_size`,消除中间无法解码的残片。返回 `Ok(())` 表示复活写入成功。
///
/// # 原子发布协议(对标 C# RecordDataHeader.Initialize 单 8 字节原子字发布)
/// 槽位旧记录可能仍被无锁读者解析(prev 链在途引用),发布顺序严格保证任意中间态
/// 布局一致且链条一致:
/// 1. RecordInfo 字原子 store(新前驱地址 + 墓碑标记)——旧布局读者经旧 RDH 解析旧键值
/// (物理尺寸恒为槽位大小,越界不可能),未匹配则沿新前驱继续回溯,链条一致;
/// 2. 键/值/填充字节落笔;
/// 3. RDH 原子字单次 store(filler + key_len + val_len 同字发布完整新布局)——
/// 无锁读者经 `RecordHeader::from_ptr_atomic` Acquire 载入只会观察到前态或后态。
///
/// 新头为整体覆写,天然不携带 SEALED 等易失标记(解除 wedb_reviv 复活池的槽位密封约定)。
pub fn revivify_record_at(
&self,
addr: u64,
slot_size: usize,
key: &[u8],
val: &[u8],
prev_addr: u64,
is_tombstone: bool,
) -> Result<()> {
if !self.addresses.is_mutable(addr) {
return Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
});
}
let p = super::RecParams {
prev_addr,
key,
val,
is_tombstone,
};
let rec_size = self.validate_append_args(&p)?;
if slot_size < rec_size {
return Err(Error::RecordTooLarge {
size: rec_size,
page_size: slot_size,
});
}
let page_id = self.config.page_id(addr);
let offset = self.config.page_offset(addr);
if !self.buffer.is_page_loaded(page_id) {
return Err(Error::PageNotReady(page_id));
}
let mut guard = self.buffer.write_page(page_id);
// Double-check 防止加锁期间地址状态发生滑动或页面槽位被重用
if !self.addresses.is_mutable(addr) || !self.buffer.is_page_loaded(page_id) {
return Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
});
}
let Some(slot_buf) = guard.get_mut(offset..offset + slot_size) else {
return Err(Error::RecordTooLarge {
size: slot_size,
page_size: self.config.page_size,
});
};
let mut header =
RecordHeader::new(prev_addr, key.len() as u32, val.len() as u32, is_tombstone)?;
let kv_end = HEADER_SIZE + key.len() + val.len();
let remaining = slot_size - rec_size;
// 1. RecordInfo 字原子发布(新前驱地址 + 墓碑标记,链条一致性先行)
// SAFETY: 记录 8 字节对齐不变式保证槽位头两字对齐
unsafe { &*(slot_buf.as_ptr() as *const AtomicU64) }
.store(header.prev_address, Ordering::Release);
// 2. 键值字节落笔 + 隐式对齐填充清零
slot_buf[HEADER_SIZE..HEADER_SIZE + key.len()].copy_from_slice(key);
slot_buf[HEADER_SIZE + key.len()..kv_end].copy_from_slice(val);
slot_buf[kv_end..rec_size].fill(0);
if remaining >= HEADER_SIZE {
// 富余容纳一个完整头:写 Pad 填充头,逻辑尺寸精确覆盖至槽位末尾
let pad_header = RecordHeader::pad(remaining);
slot_buf[rec_size..rec_size + HEADER_SIZE].copy_from_slice(&pad_header.to_bytes());
} else if remaining > 0 {
// 富余不足一个 Pad 头(对齐不变式下恒为 8 字节整词):吸纳为记录松弛填充,
// 物理尺寸精确覆盖整个槽位(填充区清零,绝不解释)
header.set_filler_bytes(remaining);
slot_buf[rec_size..].fill(0);
}
// 3. RDH 原子字单次发布完整新布局(filler + key_len + val_len 同字)
// SAFETY: 记录 8 字节对齐不变式保证槽位头两字对齐
unsafe { &*(slot_buf.as_ptr().wrapping_add(8) as *const AtomicU64) }
.store(header.rdh_word, Ordering::Release);
Ok(())
}
/// 可变区原位操作的统一内核:加页写锁 → Double-check(可变区 + 页就绪)→ 解析 → 键匹配后执行闭包
///
/// 任一前置条件不满足即返回 `Ok(degraded)`,供调用方降级为 RCU 追加写(对标 C#
/// InPlaceUpdater 失败路径);`degraded` 由调用方给定(bool 路径为 false,Option 路径为 None)。
///
/// 降级即 C# RMW 拷贝更新判定的物理准入:三 store 的同名钩子语义在此一处承接
/// (对象信封记录恒走整值读改写,天然覆盖 ObjectStore 恒 copy 口径)。
///
/// 在 garnet 中的相对路径:libs/server/Storage/Functions/MainStore/RMWMethods.cs:NeedCopyUpdate
/// 在 garnet 中的相对路径:libs/server/Storage/Functions/ObjectStore/RMWMethods.cs:NeedCopyUpdate
/// 在 garnet 中的相对路径:libs/server/Storage/Functions/UnifiedStore/RMWMethods.cs:NeedCopyUpdate
fn with_mutable_record<T>(
&self,
addr: u64,
expected_key: &[u8],
degraded: T,
f: impl FnOnce(&mut RecordMut<'_>) -> Result<T>,
) -> Result<T> {
let ro = self.addresses.read_only();
if addr < ro || addr >= self.addresses.tail() {
return Ok(degraded);
}
let page_id = self.config.page_id(addr);
let offset = self.config.page_offset(addr);
if offset + HEADER_SIZE > self.config.page_size || !self.buffer.is_page_loaded(page_id) {
return Ok(degraded);
}
let mut guard = self.buffer.write_page(page_id);
// Double-check 防止加锁期间地址状态发生滑动或页面槽位被重用(严格终止于 read_only_address)
if addr < self.addresses.read_only() || !self.buffer.is_page_loaded(page_id) {
return Ok(degraded);
}
let mut rec_mut = match RecordMut::from_slice_mut(&mut guard[offset..]) {
Ok(r) => r,
Err(_) => return Ok(degraded),
};
if !rec_mut.matches_key(expected_key) {
return Ok(degraded);
}
f(&mut rec_mut)
}
}