luci/storage/
allocator.rs1use crate::core::{LuciError, Result};
2
3use crate::storage::block::{BlockId, Extent};
4
5#[derive(Debug)]
15pub struct BlockAllocator {
16 total_blocks: u64,
19 free_list: Vec<Extent>,
21}
22
23impl BlockAllocator {
24 pub fn new() -> Self {
29 Self {
30 total_blocks: 0,
31 free_list: Vec::new(),
32 }
33 }
34
35 pub fn from_state(total_blocks: u64, free_list: Vec<Extent>) -> Self {
40 let mut alloc = Self {
41 total_blocks,
42 free_list,
43 };
44 alloc.free_list.sort_by_key(|e| e.start);
45 alloc
46 }
47
48 pub fn allocate(&mut self, count: u32) -> Result<Extent> {
57 if count == 0 {
58 return Err(LuciError::InvalidQuery(
59 "cannot allocate zero blocks".into(),
60 ));
61 }
62
63 if let Some(idx) = self.free_list.iter().position(|e| e.count >= count) {
65 let extent = self.free_list[idx];
66 if extent.count == count {
67 self.free_list.remove(idx);
69 } else {
70 self.free_list[idx] =
72 Extent::new(BlockId(extent.start.0 + count as u64), extent.count - count);
73 }
74 return Ok(Extent::new(extent.start, count));
75 }
76
77 let start = BlockId(self.total_blocks);
79 self.total_blocks += count as u64;
80 Ok(Extent::new(start, count))
81 }
82
83 pub fn free(&mut self, extent: Extent) {
93 debug_assert!(
94 extent.end().0 <= self.total_blocks,
95 "extent {extent} exceeds file bounds (total_blocks = {})",
96 self.total_blocks
97 );
98
99 let pos = self
101 .free_list
102 .binary_search_by_key(&extent.start, |e| e.start)
103 .unwrap_or_else(|i| i);
104
105 debug_assert!(
106 !self.overlaps_free_list(&extent),
107 "double-free: {extent} overlaps an existing free extent"
108 );
109
110 self.free_list.insert(pos, extent);
111 self.coalesce_at(pos);
112 }
113
114 pub fn total_blocks(&self) -> u64 {
116 self.total_blocks
117 }
118
119 pub fn free_block_count(&self) -> u64 {
121 self.free_list.iter().map(|e| e.count as u64).sum()
122 }
123
124 pub fn free_list(&self) -> &[Extent] {
126 &self.free_list
127 }
128
129 fn coalesce_at(&mut self, pos: usize) {
133 if pos + 1 < self.free_list.len()
136 && self.free_list[pos].is_adjacent_to(&self.free_list[pos + 1])
137 {
138 self.free_list[pos] = Extent::new(
139 self.free_list[pos].start,
140 self.free_list[pos].count + self.free_list[pos + 1].count,
141 );
142 self.free_list.remove(pos + 1);
143 }
144
145 if pos > 0 && self.free_list[pos - 1].is_adjacent_to(&self.free_list[pos]) {
147 self.free_list[pos - 1] = Extent::new(
148 self.free_list[pos - 1].start,
149 self.free_list[pos - 1].count + self.free_list[pos].count,
150 );
151 self.free_list.remove(pos);
152 }
153 }
154
155 fn overlaps_free_list(&self, extent: &Extent) -> bool {
157 self.free_list
158 .iter()
159 .any(|free| extent.start.0 < free.end().0 && free.start.0 < extent.end().0)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn new_allocator_is_empty() {
169 let alloc = BlockAllocator::new();
170 assert_eq!(alloc.total_blocks(), 0);
171 assert_eq!(alloc.free_block_count(), 0);
172 }
173
174 #[test]
175 fn allocate_extends_file() {
176 let mut alloc = BlockAllocator::new();
177 let ext = alloc.allocate(4).unwrap();
178 assert_eq!(ext, Extent::new(BlockId(0), 4));
179 assert_eq!(alloc.total_blocks(), 4);
180 }
181
182 #[test]
183 fn sequential_allocations_are_contiguous() {
184 let mut alloc = BlockAllocator::new();
185 let a = alloc.allocate(2).unwrap();
186 let b = alloc.allocate(3).unwrap();
187 assert_eq!(a, Extent::new(BlockId(0), 2));
188 assert_eq!(b, Extent::new(BlockId(2), 3));
189 assert_eq!(alloc.total_blocks(), 5);
190 }
191
192 #[test]
193 fn allocate_zero_is_error() {
194 let mut alloc = BlockAllocator::new();
195 assert!(alloc.allocate(0).is_err());
196 }
197
198 #[test]
199 fn free_and_reuse() {
200 let mut alloc = BlockAllocator::new();
201 let a = alloc.allocate(4).unwrap();
202 let _b = alloc.allocate(2).unwrap();
203
204 alloc.free(a);
206 assert_eq!(alloc.free_block_count(), 4);
207
208 let c = alloc.allocate(4).unwrap();
210 assert_eq!(c, a);
211 assert_eq!(alloc.free_block_count(), 0);
212 }
213
214 #[test]
215 fn free_and_split() {
216 let mut alloc = BlockAllocator::new();
217 let a = alloc.allocate(8).unwrap();
218 alloc.free(a);
219
220 let b = alloc.allocate(3).unwrap();
222 assert_eq!(b, Extent::new(BlockId(0), 3));
223 assert_eq!(alloc.free_block_count(), 5);
224
225 let c = alloc.allocate(5).unwrap();
227 assert_eq!(c, Extent::new(BlockId(3), 5));
228 assert_eq!(alloc.free_block_count(), 0);
229 }
230
231 #[test]
232 fn coalesce_adjacent_frees() {
233 let mut alloc = BlockAllocator::new();
234 let a = alloc.allocate(3).unwrap(); let b = alloc.allocate(3).unwrap(); let c = alloc.allocate(3).unwrap(); alloc.free(b);
240 alloc.free(a);
241 assert_eq!(alloc.free_list().len(), 1);
242 assert_eq!(alloc.free_list()[0], Extent::new(BlockId(0), 6));
243
244 alloc.free(c);
246 assert_eq!(alloc.free_list().len(), 1);
247 assert_eq!(alloc.free_list()[0], Extent::new(BlockId(0), 9));
248 }
249
250 #[test]
251 fn non_adjacent_frees_stay_separate() {
252 let mut alloc = BlockAllocator::new();
253 let a = alloc.allocate(2).unwrap(); let _b = alloc.allocate(2).unwrap(); let c = alloc.allocate(2).unwrap(); alloc.free(a);
258 alloc.free(c);
259 assert_eq!(alloc.free_list().len(), 2);
260 assert_eq!(alloc.free_list()[0], Extent::new(BlockId(0), 2));
261 assert_eq!(alloc.free_list()[1], Extent::new(BlockId(4), 2));
262 }
263
264 #[test]
265 fn first_fit_prefers_first_free_extent() {
266 let mut alloc = BlockAllocator::new();
267 let a = alloc.allocate(4).unwrap(); let _b = alloc.allocate(2).unwrap(); let c = alloc.allocate(6).unwrap(); alloc.free(a);
272 alloc.free(c);
273
274 let d = alloc.allocate(3).unwrap();
276 assert_eq!(d, Extent::new(BlockId(0), 3));
277 }
278
279 #[test]
280 fn allocate_falls_through_to_file_extension_when_free_list_too_small() {
281 let mut alloc = BlockAllocator::new();
282 let a = alloc.allocate(2).unwrap();
283 alloc.free(a);
284
285 let b = alloc.allocate(5).unwrap();
287 assert_eq!(b, Extent::new(BlockId(2), 5));
288 assert_eq!(alloc.free_block_count(), 2);
290 }
291
292 #[test]
293 fn from_state_reconstructs_correctly() {
294 let free_list = vec![Extent::new(BlockId(10), 3), Extent::new(BlockId(5), 2)];
295 let alloc = BlockAllocator::from_state(20, free_list);
296 assert_eq!(alloc.total_blocks(), 20);
297 assert_eq!(alloc.free_block_count(), 5);
298 assert_eq!(alloc.free_list()[0].start, BlockId(5));
300 assert_eq!(alloc.free_list()[1].start, BlockId(10));
301 }
302
303 #[test]
304 fn from_state_allocates_from_free_list() {
305 let free_list = vec![Extent::new(BlockId(5), 4)];
306 let mut alloc = BlockAllocator::from_state(20, free_list);
307
308 let ext = alloc.allocate(2).unwrap();
309 assert_eq!(ext, Extent::new(BlockId(5), 2));
310 assert_eq!(alloc.free_block_count(), 2);
311 assert_eq!(alloc.total_blocks(), 20);
313 }
314}