1use crate::errors::FxfsError;
8use crate::filesystem::{FlushReason, ForceMajor};
9use crate::log::*;
10use crate::lsm_tree::types::{ItemRef, LayerIterator, MaybeContainsKey};
11use crate::lsm_tree::{LSMTree, LockedLayer, layer_from_handle};
12use crate::object_handle::{INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle};
13use crate::object_store::extent_record::ExtentValue;
14use crate::object_store::object_manager::{ObjectManager, ReservationUpdate};
15use crate::object_store::object_record::{ObjectKey, ObjectValue};
16use crate::object_store::transaction::{
17 AssociatedObject, LockKey, Mutation, ReservationOptions, lock_keys,
18};
19use crate::object_store::{
20 AssocObj, DirectWriter, EncryptedMutations, HandleOptions, LastObjectId, LastObjectIdInfo,
21 LockState, MAX_ENCRYPTED_MUTATIONS_SIZE, ObjectStore, Options, ReservedId, StoreInfo,
22 layer_size_from_encrypted_mutations_size, tree,
23};
24use crate::serialized_types::{LATEST_VERSION, Version, VersionedLatest};
25use anyhow::{Context, Error, anyhow, bail};
26use fxfs_crypto::UnwrappedKey;
27use std::sync::OnceLock;
28use std::sync::atomic::Ordering;
29
30#[fxfs_trace::trace]
31impl ObjectStore {
32 pub async fn flush(&self) -> Result<Version, Error> {
35 self.flush_with_reason(FlushReason::Journal(ForceMajor::False)).await
36 }
37
38 pub async fn flush_with_reason(&self, reason: FlushReason) -> Result<Version, Error> {
40 let filesystem = self.filesystem();
41
42 let keys = lock_keys![LockKey::flush(self.store_object_id())];
43 let _guard = filesystem.lock_manager().write_lock(keys).await;
44
45 self.flush_guarded_with_reason(reason).await
46 }
47
48 #[trace("store_object_id" => self.store_object_id)]
50 pub async fn flush_guarded_with_reason(&self, reason: FlushReason) -> Result<Version, Error> {
51 if self.parent_store.is_none() {
52 return Ok(self.tree.get_earliest_version());
54 }
55
56 let flush_as_locked = match *self.lock_state.lock() {
60 LockState::Deleted => {
61 return Ok(LATEST_VERSION);
64 }
65 LockState::Invalid => {
66 bail!(
70 anyhow!(FxfsError::Internal)
71 .context(format!("Cannot flush invalid store {}", self.store_object_id))
72 );
73 }
74 LockState::Locked | LockState::UnlockedReadOnly(..) => true,
75 _ => false,
76 };
77
78 let filesystem = self.filesystem();
79 let object_manager = filesystem.ensure_local().object_manager();
80 let earliest_version = self.tree.get_earliest_version();
81 let needs_flush = object_manager.needs_flush(self.store_object_id);
82 if (reason == FlushReason::Journal(ForceMajor::False) && !needs_flush)
84 || (reason == FlushReason::Journal(ForceMajor::True)
85 && !needs_flush
86 && (flush_as_locked || self.tree.immutable_layer_count() <= 1))
87 || (reason == FlushReason::UpgradeVersion && earliest_version == LATEST_VERSION)
88 || (reason == FlushReason::EncryptedMutations
89 && self.store_info().unwrap().encrypted_mutations_object_id == INVALID_OBJECT_ID)
90 {
91 return Ok(earliest_version);
94 }
95
96 let trace = self.trace.load(Ordering::Relaxed);
97 if trace {
98 info!(store_id = self.store_object_id(); "OS: begin flush");
99 }
100
101 if flush_as_locked {
102 self.flush_locked().await.with_context(|| {
103 format!("Failed to flush object store {}", self.store_object_id)
104 })?;
105 } else {
106 self.flush_unlocked(reason).await.with_context(|| {
107 format!("Failed to flush object store {}", self.store_object_id)
108 })?;
109 }
110
111 if trace {
112 info!(store_id = self.store_object_id(); "OS: end flush");
113 }
114 if let Some(callback) = &*self.flush_callback.lock() {
115 callback(self);
116 }
117
118 let mut counters = self.counters.lock();
121 counters.num_flushes += 1;
122 counters.last_flush_time = Some(std::time::SystemTime::now());
123 Ok(self.tree.get_earliest_version())
125 }
126
127 async fn flush_unlocked(&self, reason: FlushReason) -> Result<Vec<u64>, Error> {
129 struct StoreInfoSnapshot<'a> {
130 store: &'a ObjectStore,
131 store_info: OnceLock<StoreInfo>,
132 }
133 impl AssociatedObject for StoreInfoSnapshot<'_> {
134 fn will_apply_mutation(
135 &self,
136 _mutation: &Mutation,
137 _object_id: u64,
138 _manager: &ObjectManager,
139 ) {
140 let mut store_info = self.store.store_info().unwrap();
141
142 let lock_state = self.store.lock_state.lock();
143 if let LockState::Unlocked { mutations_cipher, .. } = &*lock_state {
144 store_info.mutations_cipher_offset = mutations_cipher.sequence_number();
145 }
146
147 self.store_info.set(store_info).unwrap();
148 }
149 }
150
151 let store_info_snapshot = StoreInfoSnapshot { store: self, store_info: OnceLock::new() };
152
153 let filesystem = self.filesystem();
154 let txn_options = Options {
155 skip_journal_checks: true,
156 reservation: ReservationOptions::Borrowed,
157 ..Default::default()
158 };
159
160 let mut transaction = self.new_transaction(lock_keys![], txn_options).await?;
163 transaction.add_with_object(
164 self.store_object_id(),
165 Mutation::BeginFlush,
166 AssocObj::Borrowed(&store_info_snapshot),
167 );
168 transaction.commit().await.context("Failed to commit BeginFlush transaction")?;
169
170 let mut new_store_info = store_info_snapshot.store_info.into_inner().unwrap();
171
172 let mut transaction = self.new_transaction(lock_keys![], txn_options).await?;
177
178 let parent_store = self.parent_store.as_ref().unwrap();
180 let handle_options = HandleOptions { skip_journal_checks: true, ..Default::default() };
181 let id_and_key = {
182 let mut lock_state = self.lock_state.lock();
183 match &mut *lock_state {
184 LockState::Unlocked { cached_keys, .. } => {
185 if let Some(item) = cached_keys.pop() {
186 Ok(Some(item))
187 } else {
188 log::warn!("No cached keys for flush for store {}", self.store_object_id());
189 Err(anyhow!(FxfsError::Internal).context("No cached keys for flush"))
190 }
191 }
192 LockState::UnlockedReadOnly(..) => {
193 Err(anyhow!(FxfsError::Internal).context("Flush on read-only store"))
194 }
195 LockState::Unencrypted => Ok(None),
196 _ => Err(anyhow!(FxfsError::Internal))
197 .with_context(|| format!("Invalid lock state ({:?}) for flush", *lock_state)),
198 }
199 }?;
200
201 let (new_object_tree_layer, unwrapped_key) = if let Some((raw_id, key, unwrapped_key)) =
202 id_and_key
203 {
204 let object_id = ReservedId::new(parent_store, raw_id);
205 let handle = ObjectStore::create_object_with_key(
206 parent_store,
207 &mut transaction,
208 object_id,
209 handle_options,
210 key,
211 UnwrappedKey::new(unwrapped_key.clone()),
212 )
213 .await?;
214 (handle, Some(unwrapped_key))
215 } else {
216 let handle =
217 ObjectStore::create_object(parent_store, &mut transaction, handle_options, None)
218 .await?;
219 (handle, None)
220 };
221 let writer = DirectWriter::new(&new_object_tree_layer, txn_options).await;
222 let new_object_tree_layer_object_id = new_object_tree_layer.object_id();
223 parent_store.add_to_graveyard(&mut transaction, new_object_tree_layer_object_id);
224
225 transaction.commit().await.context("Failed to commit create layer transaction")?;
226
227 let full_compaction = match reason {
229 FlushReason::UpgradeVersion | FlushReason::Journal(ForceMajor::True) => true,
230 _ => false,
231 };
232 let (layers_to_keep, old_layers) = tree::flush(
233 &self.tree,
234 writer,
235 matches!(reason, FlushReason::Journal(_))
236 .then(|| filesystem.ensure_local().journal().get_compaction_yielder()),
237 full_compaction,
238 )
239 .await
240 .context("Failed to flush tree")?;
241
242 let mut new_layers = vec![layer_from_handle(new_object_tree_layer, unwrapped_key).await?];
244 new_layers.extend(layers_to_keep.iter().map(|l| (*l).clone()));
245
246 new_store_info.layers = Vec::new();
247 for layer in &new_layers {
248 if let Some(handle) = layer.handle() {
249 new_store_info.layers.push(handle.object_id());
250 }
251 }
252
253 let reservation_update: ReservationUpdate; let mut end_transaction = parent_store
255 .new_transaction(
256 lock_keys![LockKey::object(
257 self.parent_store.as_ref().unwrap().store_object_id(),
258 self.store_info_handle_object_id().unwrap(),
259 )],
260 txn_options,
261 )
262 .await?;
263
264 parent_store.remove_from_graveyard(&mut end_transaction, new_object_tree_layer_object_id);
265
266 for layer in &old_layers {
268 if let Some(handle) = layer.handle() {
269 parent_store.add_to_graveyard(&mut end_transaction, handle.object_id());
270 }
271 }
272
273 let old_encrypted_mutations_object_id =
274 std::mem::replace(&mut new_store_info.encrypted_mutations_object_id, INVALID_OBJECT_ID);
275 if old_encrypted_mutations_object_id != INVALID_OBJECT_ID {
276 parent_store.add_to_graveyard(&mut end_transaction, old_encrypted_mutations_object_id);
277 }
278
279 match &mut new_store_info.last_object_id {
287 LastObjectIdInfo::Unencrypted { id } => {
288 let LastObjectId::Unencrypted { id: in_memory_value } =
289 &*self.last_object_id.lock()
290 else {
291 unreachable!()
292 };
293 *id = *in_memory_value;
294 }
295 LastObjectIdInfo::Encrypted { id, key } => {
296 let LastObjectId::Encrypted { id: in_memory_value, .. } =
297 &*self.last_object_id.lock()
298 else {
299 unreachable!()
300 };
301 *id = *in_memory_value;
302 let guard = self.store_info.lock();
303 let current_store_info = guard.as_ref().unwrap();
304 let LastObjectIdInfo::Encrypted { key: in_memory_value, .. } =
305 ¤t_store_info.last_object_id
306 else {
307 unreachable!()
308 };
309 *key = in_memory_value.clone();
310 }
311 LastObjectIdInfo::Low32Bit => {}
312 }
313
314 self.write_store_info(&mut end_transaction, &new_store_info).await?;
315
316 let layer_file_sizes = new_layers
317 .iter()
318 .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
319 .collect::<Vec<u64>>();
320
321 let total_layer_size = layer_file_sizes.iter().sum();
322
323 reservation_update = ReservationUpdate::new(total_layer_size);
327 let new_borrowed = if layers_to_keep.is_empty() {
328 0
329 } else {
330 let old_merged_size = old_layers
331 .iter()
332 .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
333 .sum();
334 filesystem.ensure_local().object_manager().store_borrowed_for_flush(
335 self.store_object_id(),
336 old_merged_size,
337 layer_file_sizes[0],
338 total_layer_size,
339 )
340 };
341
342 end_transaction.add_with_object(
343 self.store_object_id(),
344 Mutation::EndFlush(new_borrowed),
345 AssocObj::Borrowed(&reservation_update),
346 );
347
348 if self.trace.load(Ordering::Relaxed) {
349 info!(
350 store_id = self.store_object_id(),
351 old_layer_count = old_layers.len(),
352 new_layer_count = new_layers.len(),
353 total_layer_size,
354 new_store_info:?;
355 "OS: compacting"
356 );
357 }
358
359 end_transaction
360 .commit_with_callback(|_| {
361 let mut store_info = self.store_info.lock();
362 let info = store_info.as_mut().unwrap();
363 info.layers = new_store_info.layers;
364 info.encrypted_mutations_object_id = new_store_info.encrypted_mutations_object_id;
365 info.mutations_cipher_offset = new_store_info.mutations_cipher_offset;
366 self.tree.set_layers(new_layers);
367 })
368 .await
369 .context("Failed to commit EndFlush transaction")?;
370
371 for layer in old_layers {
373 let object_id = layer.handle().map(|h| h.object_id());
374 layer.close_layer().await;
375 if let Some(object_id) = object_id {
376 parent_store
377 .tombstone_object(object_id, txn_options, None)
378 .await
379 .context("Failed to tombstone old layer")?;
380 }
381 }
382
383 if old_encrypted_mutations_object_id != INVALID_OBJECT_ID {
384 parent_store
385 .tombstone_object(old_encrypted_mutations_object_id, txn_options, None)
386 .await
387 .context("Failed to tombstone old encrypted mutations")?;
388 }
389
390 Ok(layer_file_sizes)
391 }
392
393 async fn flush_locked(&self) -> Result<(), Error> {
395 let filesystem = self.filesystem();
396 let txn_options = Options {
397 skip_journal_checks: true,
398 reservation: ReservationOptions::Borrowed,
399 ..Default::default()
400 };
401
402 let mut transaction = self.new_transaction(lock_keys![], txn_options).await?;
403 transaction.add(self.store_object_id(), Mutation::BeginFlush);
404 transaction.commit().await.context("Failed to commit BeginFlush transaction")?;
405
406 let mut new_store_info = self.load_store_info().await?;
407
408 let mut transaction = self.new_transaction(lock_keys![], txn_options).await?;
413
414 let reservation_update: ReservationUpdate; let handle; let mut end_transaction;
417
418 let parent_store = self.parent_store.as_ref().unwrap();
421 handle = if new_store_info.encrypted_mutations_object_id == INVALID_OBJECT_ID {
422 let handle = ObjectStore::create_object(
423 parent_store,
424 &mut transaction,
425 HandleOptions { skip_journal_checks: true, ..Default::default() },
426 None,
427 )
428 .await?;
429 let oid = handle.object_id();
430 end_transaction = parent_store
431 .new_transaction(
432 lock_keys![
433 LockKey::object(parent_store.store_object_id(), oid),
434 LockKey::object(
435 parent_store.store_object_id(),
436 self.store_info_handle_object_id().unwrap(),
437 ),
438 ],
439 txn_options,
440 )
441 .await?;
442 new_store_info.encrypted_mutations_object_id = oid;
443 parent_store.add_to_graveyard(&mut transaction, oid);
444 parent_store.remove_from_graveyard(&mut end_transaction, oid);
445 handle
446 } else {
447 end_transaction = parent_store
448 .new_transaction(
449 lock_keys![
450 LockKey::object(
451 parent_store.store_object_id(),
452 new_store_info.encrypted_mutations_object_id,
453 ),
454 LockKey::object(
455 parent_store.store_object_id(),
456 self.store_info_handle_object_id().unwrap(),
457 ),
458 ],
459 txn_options,
460 )
461 .await?;
462 ObjectStore::open_object(
463 parent_store,
464 new_store_info.encrypted_mutations_object_id,
465 HandleOptions { skip_journal_checks: true, ..Default::default() },
466 None,
467 )
468 .await?
469 };
470 transaction
471 .commit()
472 .await
473 .context("Failed to commit create encrypted mutations transaction")?;
474
475 let journaled = filesystem
478 .ensure_local()
479 .journal()
480 .read_transactions_for_object(self.store_object_id)
481 .await
482 .context("Failed to read encrypted mutations from journal")?;
483 let mut buffer = handle.allocate_buffer(MAX_ENCRYPTED_MUTATIONS_SIZE).await;
484 let mut writer = buffer.writer();
485 EncryptedMutations::from_replayed_mutations(self.store_object_id, journaled)
486 .serialize_with_version(&mut writer)?;
487 let len = writer.position();
488 handle
489 .txn_write(&mut end_transaction, handle.get_size(), buffer.subslice(..len))
490 .await
491 .context("Failed to write encrypted mutations")?;
492
493 self.write_store_info(&mut end_transaction, &new_store_info)
494 .await
495 .context("Failed to write store info")?;
496
497 let mut total_layer_size = 0;
498 for &oid in &new_store_info.layers {
499 total_layer_size += parent_store.get_file_size(oid).await?;
500 }
501 let old_encrypted_size = layer_size_from_encrypted_mutations_size(handle.get_size());
502 let new_encrypted_size =
503 layer_size_from_encrypted_mutations_size(handle.get_size() + len as u64);
504 total_layer_size += new_encrypted_size;
505
506 reservation_update = ReservationUpdate::new(total_layer_size);
510 let new_borrowed = filesystem.ensure_local().object_manager().store_borrowed_for_flush(
511 self.store_object_id(),
512 old_encrypted_size,
513 new_encrypted_size,
514 total_layer_size,
515 );
516
517 end_transaction.add_with_object(
518 self.store_object_id(),
519 Mutation::EndFlush(new_borrowed),
520 AssocObj::Borrowed(&reservation_update),
521 );
522
523 end_transaction
524 .commit_with_callback(|_| {
525 if let Some(info) = &mut *self.store_info.lock() {
528 info.encrypted_mutations_object_id =
529 new_store_info.encrypted_mutations_object_id;
530 }
531 })
532 .await
533 .context("Failed to commit EndFlush transaction")?;
534
535 Ok(())
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::{FlushReason, ForceMajor};
542 use crate::filesystem::{FxFilesystem, FxFilesystemBuilder};
543 use crate::lsm_tree::types::{LayerIterator, MaybeContainsKey};
544 use crate::object_handle::{
545 INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle, WriteObjectHandle,
546 };
547 use crate::object_store::allocator::max_extent_size_for_block_size;
548 use crate::object_store::directory::{Directory, replace_child};
549 use crate::object_store::extent_record::ExtentValue;
550 use crate::object_store::journal::JournalOptions;
551 use crate::object_store::transaction::{Options, lock_keys};
552 use crate::object_store::volume::root_volume;
553 use crate::object_store::{
554 DirType, HandleOptions, LockKey, NewChildStoreOptions, ObjectKey, ObjectStore, ObjectValue,
555 StoreOptions, layer_size_from_encrypted_mutations_size,
556 };
557 use fxfs_insecure_crypto::new_insecure_crypt;
558 use std::collections::HashSet;
559 use std::ops::Bound;
560 use std::sync::Arc;
561 use storage_device::DeviceHolder;
562 use storage_device::fake_device::FakeDevice;
563
564 #[fuchsia::test]
565 async fn test_flush_when_locked() {
566 let device = DeviceHolder::new(FakeDevice::new(8192, 1024));
567 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
568 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
569 let crypt = Arc::new(new_insecure_crypt());
570 let store = root_volume
571 .new_volume(
572 "test",
573 NewChildStoreOptions {
574 options: StoreOptions { crypt: Some(crypt.clone()), ..StoreOptions::default() },
575 ..NewChildStoreOptions::default()
576 },
577 )
578 .await
579 .expect("new_volume failed");
580 let root_dir =
581 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
582 let mut transaction = fs
583 .root_store()
584 .new_transaction(
585 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
586 Options::default(),
587 )
588 .await
589 .expect("new_transaction failed");
590 let foo = root_dir
591 .create_child_file(&mut transaction, "foo")
592 .await
593 .expect("create_child_file failed");
594 transaction.commit().await.expect("commit failed");
595
596 store.flush().await.expect("flush failed");
600
601 let mut transaction = fs
602 .root_store()
603 .new_transaction(
604 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
605 Options::default(),
606 )
607 .await
608 .expect("new_transaction failed");
609 let bar = root_dir
610 .create_child_file(&mut transaction, "bar")
611 .await
612 .expect("create_child_file failed");
613 transaction.commit().await.expect("commit failed");
614
615 store.lock().await.expect("lock failed");
616
617 store.flush().await.expect("flush failed");
619
620 let info = store.load_store_info().await.unwrap();
622 let parent_store = store.parent_store().unwrap();
623 let mut total_layer_size = 0;
624 for &oid in &info.layers {
625 total_layer_size +=
626 parent_store.get_file_size(oid).await.expect("get_file_size failed");
627 }
628 assert_ne!(info.encrypted_mutations_object_id, INVALID_OBJECT_ID);
629 total_layer_size += layer_size_from_encrypted_mutations_size(
630 parent_store
631 .get_file_size(info.encrypted_mutations_object_id)
632 .await
633 .expect("get_file_size failed"),
634 );
635 assert_eq!(
636 fs.object_manager().reservation(store.store_object_id()),
637 Some(fs.object_manager().tree_reservation(store.store_object_id(), total_layer_size))
638 );
639
640 store.unlock(crypt).await.expect("unlock failed");
642
643 ObjectStore::open_object(&store, foo.object_id(), HandleOptions::default(), None)
644 .await
645 .expect("open_object failed");
646
647 ObjectStore::open_object(&store, bar.object_id(), HandleOptions::default(), None)
648 .await
649 .expect("open_object failed");
650
651 fs.close().await.expect("close failed");
652 }
653
654 #[fuchsia::test]
655 async fn test_major_compaction_frees_reservation_and_merges_layers() {
656 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
657 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
658 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
659 let store = root_volume
660 .new_volume("test", NewChildStoreOptions::default())
661 .await
662 .expect("new_volume failed");
663 let root_dir =
664 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
665
666 let num_files = 2000;
669 let mut file_ids = Vec::with_capacity(num_files);
670 for i in 0..num_files {
671 let filename = format!("file_{:04}_{:<200}", i, i);
672 let mut transaction = store
673 .new_transaction(
674 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
675 Options::default(),
676 )
677 .await
678 .expect("new_transaction failed");
679 let file = root_dir
680 .create_child_file(&mut transaction, &filename)
681 .await
682 .expect("create_child_file failed");
683 transaction.commit().await.expect("commit failed");
684 file_ids.push((file.object_id(), filename));
685 }
686
687 store.flush().await.expect("flush failed");
689
690 let info_initial = store.load_store_info().await.expect("load_store_info failed");
691 assert_eq!(info_initial.layers.len(), 1);
692 let initial_reservation = fs
693 .object_manager()
694 .reservation(store.store_object_id())
695 .expect("reservation not found");
696
697 for (oid, filename) in &file_ids[0..1000] {
699 let mut transaction = store
700 .new_transaction(
701 lock_keys![
702 LockKey::object(store.store_object_id(), root_dir.object_id()),
703 LockKey::object(store.store_object_id(), *oid),
704 ],
705 Options::default(),
706 )
707 .await
708 .expect("new_transaction failed");
709 let replaced = crate::object_store::directory::replace_child(
710 &mut transaction,
711 None,
712 (&root_dir, filename.as_str()),
713 )
714 .await
715 .expect("replace_child failed");
716 assert_matches::assert_matches!(
717 replaced,
718 crate::object_store::directory::ReplacedChild::Object(id) if id == *oid
719 );
720 transaction.commit().await.expect("commit failed");
721 }
722
723 store
726 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
727 .await
728 .expect("minor flush failed");
729
730 let info_after_minor = store.load_store_info().await.expect("load_store_info failed");
731 assert_eq!(
732 info_after_minor.layers.len(),
733 2,
734 "Minor compaction should keep the base layer, creating 2 layers"
735 );
736 let res_after_minor = fs
737 .object_manager()
738 .reservation(store.store_object_id())
739 .expect("reservation not found");
740 assert!(
741 res_after_minor >= initial_reservation,
742 "Minor compaction should not decrease reservation because base layer is kept"
743 );
744
745 store
748 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
749 .await
750 .expect("major flush failed");
751
752 let info_after_major = store.load_store_info().await.expect("load_store_info failed");
753 assert_eq!(
754 info_after_major.layers.len(),
755 1,
756 "Major compaction should merge all layers into 1"
757 );
758 let res_after_major = fs
759 .object_manager()
760 .reservation(store.store_object_id())
761 .expect("reservation not found");
762 assert!(
763 res_after_major < initial_reservation,
764 "Major compaction should decrease reservation (was {}, initial was {})",
765 res_after_major,
766 initial_reservation
767 );
768
769 for (oid, filename) in &file_ids[0..1000] {
772 assert!(
773 root_dir.lookup(filename).await.expect("lookup failed").is_none(),
774 "Deleted file {} should not exist",
775 oid
776 );
777 }
778 for (oid, filename) in &file_ids[1000..num_files] {
779 let res = root_dir.lookup(filename).await.expect("lookup failed");
780 assert!(res.is_some(), "Surviving file {} should exist", oid);
781 assert_eq!(res.unwrap().0, *oid);
782 }
783
784 fs.close().await.expect("close failed");
785 }
786
787 #[fuchsia::test]
788 async fn test_major_compaction_without_journal_mutations() {
789 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
790 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
791 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
792 let crypt = Arc::new(new_insecure_crypt());
793 let store = root_volume
794 .new_volume(
795 "test",
796 NewChildStoreOptions {
797 options: StoreOptions { crypt: Some(crypt.clone()), ..StoreOptions::default() },
798 ..Default::default()
799 },
800 )
801 .await
802 .expect("new_volume failed");
803 let root_dir =
804 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
805
806 for i in 0..2000 {
808 let filename = format!("file_{:04}_{:<200}", i, i);
809 let mut transaction = store
810 .new_transaction(
811 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
812 Options::default(),
813 )
814 .await
815 .expect("new_transaction failed");
816 root_dir.create_child_file(&mut transaction, &filename).await.expect("create failed");
817 transaction.commit().await.expect("commit failed");
818 }
819 store.flush().await.expect("flush failed");
820
821 let mut transaction = store
823 .new_transaction(
824 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
825 Options::default(),
826 )
827 .await
828 .expect("new_transaction failed");
829 root_dir.create_child_file(&mut transaction, "extra_file").await.expect("create failed");
830 transaction.commit().await.expect("commit failed");
831 store
832 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
833 .await
834 .expect("flush failed");
835
836 let info = store.load_store_info().await.expect("load_store_info failed");
837 assert_eq!(info.layers.len(), 2, "Should have 2 layers before major flush");
838
839 store.lock().await.expect("lock failed");
843 store.unlock_read_only(crypt.clone()).await.expect("unlock_read_only failed");
844 assert!(!fs.object_manager().needs_flush(store.store_object_id()));
845 fs.object_manager()
846 .flush(FlushReason::Journal(ForceMajor::True))
847 .await
848 .expect("read-only major flush failed");
849 assert_eq!(
850 store
851 .load_store_info()
852 .await
853 .expect("load_store_info failed")
854 .encrypted_mutations_object_id,
855 INVALID_OBJECT_ID
856 );
857 store.lock_read_only();
858 store.unlock(crypt).await.expect("unlock failed");
859
860 assert!(!fs.object_manager().needs_flush(store.store_object_id()));
862 store
863 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
864 .await
865 .expect("major flush failed");
866
867 let info_after_major = store.load_store_info().await.expect("load_store_info failed");
868 assert_eq!(
869 info_after_major.layers.len(),
870 1,
871 "Major flush should merge 2 layers into 1 even without journal mutations"
872 );
873
874 assert!(!fs.object_manager().needs_flush(store.store_object_id()));
877 store
878 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
879 .await
880 .expect("second major flush failed");
881 let info_after_second = store.load_store_info().await.expect("load_store_info failed");
882 assert_eq!(
883 info_after_second.layers, info_after_major.layers,
884 "Major flush when at 1 layer with no mutations should be a no-op"
885 );
886
887 store.lock().await.expect("lock failed");
890 assert!(!fs.object_manager().needs_flush(store.store_object_id()));
891 store
892 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
893 .await
894 .expect("locked major flush failed");
895 let info_after_locked = store.load_store_info().await.expect("load_store_info failed");
896 assert_eq!(info_after_locked.encrypted_mutations_object_id, INVALID_OBJECT_ID);
897
898 fs.close().await.expect("close failed");
899 }
900
901 #[fuchsia::test]
902 async fn test_major_compaction_purges_deleted_extents() {
903 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
904 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
905 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
906 let store = root_volume
907 .new_volume("test", NewChildStoreOptions::default())
908 .await
909 .expect("new_volume failed");
910 let root_dir =
911 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
912
913 for i in 0..2000 {
915 let filename = format!("file_{:04}_{:<200}", i, i);
916 let mut transaction = store
917 .new_transaction(
918 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
919 Options::default(),
920 )
921 .await
922 .expect("new_transaction failed");
923 root_dir.create_child_file(&mut transaction, &filename).await.expect("create failed");
924 transaction.commit().await.expect("commit failed");
925 }
926
927 let data_oid = {
929 let mut transaction = store
930 .new_transaction(
931 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
932 Options::default(),
933 )
934 .await
935 .expect("new_transaction failed");
936 let file = root_dir
937 .create_child_file(&mut transaction, "data_file")
938 .await
939 .expect("create_child_file failed");
940 let oid = file.object_id();
941 transaction.commit().await.expect("commit failed");
942 oid
943 };
944
945 let data_file = ObjectStore::open_object(&store, data_oid, HandleOptions::default(), None)
946 .await
947 .expect("open_object failed");
948 {
949 let mut transaction = store
950 .new_transaction(
951 lock_keys![LockKey::object(store.store_object_id(), data_oid)],
952 Options::default(),
953 )
954 .await
955 .expect("new_transaction failed");
956 let mut buffer = data_file.allocate_buffer(131072).await;
957 buffer.fill(0xAB);
958 data_file
959 .txn_write(&mut transaction, 0, buffer.as_ref())
960 .await
961 .expect("txn_write failed");
962 transaction.commit().await.expect("commit failed");
963 }
964
965 store.flush().await.expect("flush failed");
967
968 let info0 = store.load_store_info().await.expect("load_store_info failed");
969 assert_eq!(info0.layers.len(), 1);
970
971 data_file.truncate(0).await.expect("truncate failed");
973
974 store
976 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
977 .await
978 .expect("minor flush failed");
979 let info1 = store.load_store_info().await.expect("load_store_info failed");
980 assert_eq!(info1.layers.len(), 2, "Minor flush should keep Layer 0 and create Layer 1");
981
982 store
984 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
985 .await
986 .expect("major flush failed");
987 let info2 = store.load_store_info().await.expect("load_store_info failed");
988 assert_eq!(info2.layers.len(), 1, "Major flush should merge into 1 layer");
989
990 assert_eq!(data_file.get_size(), 0);
992
993 fs.close().await.expect("close failed");
994 }
995
996 #[fuchsia::test]
1002 async fn test_borrow_metadata_space_fails_without_major_compaction() {
1003 let reclaim_size = 65536;
1004 let device = DeviceHolder::new(FakeDevice::new(102400, 512));
1006 let fs = FxFilesystemBuilder::new()
1007 .journal_options(JournalOptions { reclaim_size, ..Default::default() })
1008 .format(true)
1009 .open(device)
1010 .await
1011 .expect("open failed");
1012
1013 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
1018 let num_stores = 3;
1019 let files_per_store = 3500;
1020 let mut stores_and_files = Vec::new();
1021
1022 for s in 0..num_stores {
1023 let store = root_volume
1024 .new_volume(&format!("test_{s}"), NewChildStoreOptions::default())
1025 .await
1026 .expect("new_volume failed");
1027 let root_dir = Directory::open(&store, store.root_directory_object_id())
1028 .await
1029 .expect("open failed");
1030 let mut file_ids = Vec::with_capacity(files_per_store);
1031 for i in 0..files_per_store {
1032 let filename = format!("file_{:04}_{:<200}", i, i);
1033 let mut transaction = store
1034 .new_transaction(
1035 lock_keys![LockKey::object(store.store_object_id(), root_dir.object_id())],
1036 Options::default(),
1037 )
1038 .await
1039 .expect("new_transaction failed");
1040 let file = root_dir
1041 .create_child_file(&mut transaction, &filename)
1042 .await
1043 .expect("create_child_file failed");
1044 transaction.commit().await.expect("commit failed");
1045 file_ids.push((file.object_id(), filename));
1046 }
1047 store
1048 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
1049 .await
1050 .expect("flush failed");
1051 stores_and_files.push((store, root_dir, file_ids));
1052 }
1053
1054 for (_, root_dir, file_ids) in stores_and_files {
1055 for (oid, filename) in &file_ids[0..2500] {
1056 let mut context = root_dir
1057 .acquire_context_for_replace(None, filename.as_str(), true)
1058 .await
1059 .expect("acquire_context failed");
1060 let replaced = crate::object_store::directory::replace_child(
1061 &mut context.transaction,
1062 None,
1063 (&root_dir, filename.as_str()),
1064 )
1065 .await
1066 .expect("replace_child failed");
1067 assert_matches::assert_matches!(
1068 replaced,
1069 crate::object_store::directory::ReplacedChild::Object(id) if id == *oid
1070 );
1071 context.transaction.commit().await.expect("commit failed");
1072 }
1073 }
1074
1075 fs.close().await.expect("close failed");
1076 }
1077
1078 #[fuchsia::test]
1079 async fn test_file_truncate_and_extend_does_not_force_major_compaction() {
1080 let device = DeviceHolder::new(FakeDevice::new(65536, 512));
1081 let fs = FxFilesystemBuilder::new()
1082 .journal_options(JournalOptions { reclaim_size: 32768, ..Default::default() })
1083 .format(true)
1084 .open(device)
1085 .await
1086 .expect("open failed");
1087 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
1088 let store = root_volume
1089 .new_volume("test", NewChildStoreOptions::default())
1090 .await
1091 .expect("new_volume failed");
1092 let store_id = store.store_object_id();
1093 let root_dir =
1094 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
1095
1096 for i in 0..2000 {
1099 let filename = format!("base_{i:04}_{:<200}", i);
1100 let mut transaction = store
1101 .new_transaction(
1102 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1103 Options::default(),
1104 )
1105 .await
1106 .expect("new_transaction failed");
1107 root_dir
1108 .create_child_file(&mut transaction, &filename)
1109 .await
1110 .expect("create_child_file failed");
1111 transaction.commit().await.expect("commit failed");
1112 }
1113
1114 let file = {
1116 let mut transaction = store
1117 .new_transaction(
1118 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1119 Options::default(),
1120 )
1121 .await
1122 .expect("new_transaction failed");
1123 let file = root_dir
1124 .create_child_file(&mut transaction, "work_file")
1125 .await
1126 .expect("create_child_file failed");
1127 transaction.commit().await.expect("commit failed");
1128 file
1129 };
1130 let mut buf = file.allocate_buffer(4096).await;
1131 buf.fill(0xaa);
1132 file.write_or_append(Some(0), buf.as_ref()).await.expect("write_or_append failed");
1133 fs.journal().force_compact().await.expect("force_compact failed");
1134
1135 let bytes_before_loop = fs.object_manager().compaction_bytes_written();
1136
1137 for _ in 0..450 {
1143 file.truncate_with_options(
1144 Options {
1145 reservation: crate::object_store::transaction::ReservationOptions::Borrowed,
1146 ..Default::default()
1147 },
1148 0,
1149 )
1150 .await
1151 .expect("truncate failed");
1152
1153 file.write_or_append(Some(0), buf.as_ref()).await.expect("write_or_append failed");
1154 }
1155 fs.journal().force_compact().await.expect("force_compact failed");
1156
1157 let compaction_bytes = fs.object_manager().compaction_bytes_written() - bytes_before_loop;
1161 assert!(
1162 compaction_bytes < 600 * 1024,
1163 "Truncate and extend churn caused unnecessary major compaction: \
1164 {compaction_bytes} bytes written"
1165 );
1166
1167 fs.close().await.expect("close failed");
1168 }
1169
1170 #[fuchsia::test]
1171 async fn test_temporary_file_create_and_delete_does_not_force_major_compaction() {
1172 let device = DeviceHolder::new(FakeDevice::new(65536, 512));
1173 let fs = FxFilesystemBuilder::new()
1174 .journal_options(JournalOptions { reclaim_size: 32768, ..Default::default() })
1175 .format(true)
1176 .open(device)
1177 .await
1178 .expect("open failed");
1179 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
1180 let store = root_volume
1181 .new_volume("test", NewChildStoreOptions::default())
1182 .await
1183 .expect("new_volume failed");
1184 let store_id = store.store_object_id();
1185 let root_dir =
1186 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
1187
1188 for i in 0..2000 {
1191 let filename = format!("base_{i:04}_{:<200}", i);
1192 let mut transaction = store
1193 .new_transaction(
1194 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1195 Options::default(),
1196 )
1197 .await
1198 .expect("new_transaction failed");
1199 root_dir
1200 .create_child_file(&mut transaction, &filename)
1201 .await
1202 .expect("create_child_file failed");
1203 transaction.commit().await.expect("commit failed");
1204 }
1205 fs.journal().force_compact().await.expect("force_compact failed");
1206 store
1207 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
1208 .await
1209 .expect("major flush failed");
1210 fs.journal().force_compact().await.expect("force_compact failed");
1211
1212 let (base_layer_object_id, base_layer_len) = {
1213 let layers = store.tree().immutable_layer_set().layers;
1214 assert_eq!(layers.len(), 1);
1215 assert!(layers[0].handle().unwrap().get_size() > 512 * 1024);
1216 (layers[0].handle().unwrap().object_id(), layers[0].len())
1217 };
1218
1219 const NUM_TEMP_FILES: usize = 200;
1223 const MINOR_FLUSH_INTERVAL: usize = 25;
1224 let graveyard_dir_id = store.graveyard_directory_object_id();
1225 let mut tombstone_keys = Vec::with_capacity(NUM_TEMP_FILES * 3);
1226 for i in 0..NUM_TEMP_FILES {
1227 let filename = format!("tmp_{i:04}");
1228 let oid = {
1229 let mut transaction = store
1230 .new_transaction(
1231 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1232 Options::default(),
1233 )
1234 .await
1235 .expect("new_transaction failed");
1236 let file = root_dir
1237 .create_child_file(&mut transaction, &filename)
1238 .await
1239 .expect("create_child_file failed");
1240 transaction.commit().await.expect("commit failed");
1241 file.object_id()
1242 };
1243
1244 if i % MINOR_FLUSH_INTERVAL == 0 {
1250 store
1251 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
1252 .await
1253 .expect("minor flush failed");
1254 }
1255
1256 {
1257 let mut context = root_dir
1258 .acquire_context_for_replace(None, filename.as_str(), true)
1259 .await
1260 .expect("acquire_context failed");
1261 replace_child(&mut context.transaction, None, (&root_dir, filename.as_str()))
1262 .await
1263 .expect("replace_child failed");
1264 context.transaction.commit().await.expect("commit failed");
1265 }
1266 fs.graveyard().queue_tombstone_object(store_id, oid);
1267 fs.graveyard().flush().await;
1268
1269 tombstone_keys.push(ObjectKey::object(oid));
1270 tombstone_keys.push(ObjectKey::child(root_dir.object_id(), &filename, DirType::Normal));
1271 tombstone_keys.push(ObjectKey::graveyard_entry(graveyard_dir_id, oid));
1272 }
1273 fs.journal().force_compact().await.expect("force_compact failed");
1274
1275 {
1279 let layers = store.tree().immutable_layer_set().layers;
1280 assert_eq!(layers.len(), 2);
1281
1282 assert_eq!(layers[1].handle().unwrap().object_id(), base_layer_object_id);
1285 assert_eq!(layers[1].len(), base_layer_len);
1286
1287 let expected_false_positives = tombstone_keys
1293 .iter()
1294 .filter(|key| layers[1].maybe_contains_key(key) != MaybeContainsKey::False)
1295 .count();
1296 assert_eq!(layers[0].len(), 1 + expected_false_positives);
1297
1298 assert!(
1302 layers[0].len() <= 16,
1303 "unexpectedly many items in minor layer: {}",
1304 layers[0].len()
1305 );
1306
1307 let mut iter = layers[0].seek(Bound::Unbounded).await.expect("seek failed");
1308 let mut saw_root_dir = false;
1309 let mut count = 0;
1310 while let Some(item) = iter.get() {
1311 count += 1;
1312 if item.key == &ObjectKey::object(root_dir.object_id()) {
1313 saw_root_dir = true;
1314 } else {
1315 assert!(
1318 matches!(
1319 item.value,
1320 ObjectValue::None | ObjectValue::Extent(ExtentValue::None)
1321 ),
1322 "unexpected non-tombstone item in minor layer: {item:?}"
1323 );
1324 assert_ne!(
1325 layers[1].maybe_contains_key(item.key),
1326 MaybeContainsKey::False,
1327 "tombstone survived despite base layer bloom filter returning False: \
1328 {item:?}"
1329 );
1330 }
1331 iter.advance().await.expect("advance failed");
1332 }
1333 assert!(saw_root_dir, "expected root_dir item in minor layer");
1334 assert_eq!(count, layers[0].len());
1335 }
1336
1337 fs.close().await.expect("close failed");
1338 }
1339
1340 #[fuchsia::test]
1341 async fn test_minor_compaction_extent_tombstones() {
1342 const MIB: u64 = 1024 * 1024;
1343 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
1344 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
1345 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
1346 let store = root_volume
1347 .new_volume("test", NewChildStoreOptions::default())
1348 .await
1349 .expect("new_volume failed");
1350 let store_id = store.store_object_id();
1351 let root_dir =
1352 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
1353
1354 for i in 0..2000 {
1363 let filename = format!("base_{i:04}_{:<200}", i);
1364 let mut transaction = store
1365 .new_transaction(
1366 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1367 Options::default(),
1368 )
1369 .await
1370 .expect("new_transaction failed");
1371 root_dir
1372 .create_child_file(&mut transaction, &filename)
1373 .await
1374 .expect("create_child_file failed");
1375 transaction.commit().await.expect("commit failed");
1376 }
1377
1378 let create_file = |name: &'static str| {
1379 let store = &store;
1380 let root_dir = &root_dir;
1381 async move {
1382 let mut transaction = store
1383 .new_transaction(
1384 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1385 Options::default(),
1386 )
1387 .await
1388 .expect("new_transaction failed");
1389 let file = root_dir
1390 .create_child_file(&mut transaction, name)
1391 .await
1392 .expect("create_child_file failed");
1393 transaction.commit().await.expect("commit failed");
1394 file
1395 }
1396 };
1397
1398 let large_base_len = max_extent_size_for_block_size(fs.block_size());
1399 assert_eq!(large_base_len, 1792 * 1024);
1400 let large_base_start = 512 * 1024;
1401 let large_base_end = large_base_start + large_base_len;
1402 let large_base_file = create_file("large_base_file").await;
1403 {
1404 let mut buf = large_base_file.allocate_buffer(large_base_len as usize).await;
1405 buf.fill(0xaa);
1406 large_base_file
1407 .write_or_append(Some(large_base_start), buf.as_ref())
1408 .await
1409 .expect("write_or_append failed");
1410 }
1411
1412 let tail_4m_offset = 3 * MIB + 512 * 1024;
1413 let tail_4m_file = create_file("tail_4m_file").await;
1414 {
1415 let mut buf = tail_4m_file.allocate_buffer(4096).await;
1416 buf.fill(0xbb);
1417 tail_4m_file
1418 .write_or_append(Some(tail_4m_offset), buf.as_ref())
1419 .await
1420 .expect("write_or_append failed");
1421 }
1422
1423 let tail_8m_offset = 7 * MIB + 512 * 1024;
1424 let tail_8m_file = create_file("tail_8m_file").await;
1425 {
1426 let mut buf = tail_8m_file.allocate_buffer(4096).await;
1427 buf.fill(0xcc);
1428 tail_8m_file
1429 .write_or_append(Some(tail_8m_offset), buf.as_ref())
1430 .await
1431 .expect("write_or_append failed");
1432 }
1433
1434 let prefix_base_file = create_file("prefix_base_file").await;
1435 {
1436 let mut buf = prefix_base_file.allocate_buffer(64 * 1024).await;
1437 buf.fill(0xdd);
1438 prefix_base_file
1439 .write_or_append(Some(0), buf.as_ref())
1440 .await
1441 .expect("write_or_append failed");
1442 }
1443
1444 store
1445 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
1446 .await
1447 .expect("major flush failed");
1448 let base_layer_object_id = {
1449 let layers = store.tree().immutable_layer_set().layers;
1450 assert_eq!(layers.len(), 1);
1451 assert!(layers[0].handle().unwrap().get_size() > 512 * 1024);
1452
1453 let expected_key = ObjectKey::extent(
1457 large_base_file.object_id(),
1458 large_base_file.attribute_id(),
1459 large_base_start..large_base_end,
1460 );
1461 let iter =
1462 layers[0].seek(Bound::Included(&expected_key)).await.expect("base seek failed");
1463 assert_eq!(iter.get().map(|item| item.key), Some(&expected_key));
1464
1465 layers[0].handle().unwrap().object_id()
1466 };
1467
1468 {
1472 let mut buf = prefix_base_file.allocate_buffer((3 * MIB) as usize).await;
1473 buf.fill(0xee);
1474 prefix_base_file
1475 .write_or_append(Some(MIB), buf.as_ref())
1476 .await
1477 .expect("write_or_append failed");
1478 }
1479 let temp_extent_file = create_file("temp_extent_file").await;
1480 {
1481 let mut buf = temp_extent_file.allocate_buffer((4 * MIB) as usize).await;
1482 buf.fill(0xff);
1483 temp_extent_file
1484 .write_or_append(Some(0), buf.as_ref())
1485 .await
1486 .expect("write_or_append failed");
1487 }
1488 store
1489 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
1490 .await
1491 .expect("first minor flush failed");
1492
1493 let hole_start = large_base_start..(large_base_start + 4096);
1507 let hole_interior = (MIB + 512 * 1024)..(MIB + 512 * 1024 + 4096);
1508 let hole_boundary = (2 * MIB - 4096)..(2 * MIB + 4096);
1509 let hole_end = (large_base_end - 4096)..large_base_end;
1510 for hole in
1511 [hole_start.clone(), hole_interior.clone(), hole_boundary.clone(), hole_end.clone()]
1512 {
1513 let mut transaction =
1514 large_base_file.new_transaction().await.expect("new_transaction failed");
1515 large_base_file.zero(&mut transaction, hole).await.expect("zero failed");
1516 transaction.commit().await.expect("commit failed");
1517 }
1518 {
1519 let mut transaction =
1520 tail_4m_file.new_transaction().await.expect("new_transaction failed");
1521 tail_4m_file.zero(&mut transaction, 0..4 * MIB).await.expect("zero failed");
1522 transaction.commit().await.expect("commit failed");
1523 }
1524 {
1525 let mut transaction =
1526 tail_8m_file.new_transaction().await.expect("new_transaction failed");
1527 tail_8m_file.zero(&mut transaction, 0..8 * MIB).await.expect("zero failed");
1528 transaction.commit().await.expect("commit failed");
1529 }
1530 {
1531 let mut transaction =
1532 prefix_base_file.new_transaction().await.expect("new_transaction failed");
1533 prefix_base_file.zero(&mut transaction, MIB..4 * MIB).await.expect("zero failed");
1534 transaction.commit().await.expect("commit failed");
1535 }
1536 temp_extent_file.truncate(0).await.expect("truncate failed");
1537
1538 store
1539 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
1540 .await
1541 .expect("second minor flush failed");
1542
1543 let must_keep_keys = [
1546 ObjectKey::extent(
1547 large_base_file.object_id(),
1548 large_base_file.attribute_id(),
1549 hole_start.clone(),
1550 ),
1551 ObjectKey::extent(
1552 large_base_file.object_id(),
1553 large_base_file.attribute_id(),
1554 hole_interior.clone(),
1555 ),
1556 ObjectKey::extent(
1557 large_base_file.object_id(),
1558 large_base_file.attribute_id(),
1559 hole_boundary.clone(),
1560 ),
1561 ObjectKey::extent(
1562 large_base_file.object_id(),
1563 large_base_file.attribute_id(),
1564 hole_end.clone(),
1565 ),
1566 ObjectKey::extent(tail_4m_file.object_id(), tail_4m_file.attribute_id(), 0..4 * MIB),
1567 ObjectKey::extent(tail_8m_file.object_id(), tail_8m_file.attribute_id(), 0..8 * MIB),
1568 ];
1569 let elidable_keys = [
1570 ObjectKey::extent(
1571 prefix_base_file.object_id(),
1572 prefix_base_file.attribute_id(),
1573 MIB..4 * MIB,
1574 ),
1575 ObjectKey::extent(
1576 temp_extent_file.object_id(),
1577 temp_extent_file.attribute_id(),
1578 0..4 * MIB,
1579 ),
1580 ];
1581
1582 {
1583 let layers = store.tree().immutable_layer_set().layers;
1584 assert_eq!(layers.len(), 2);
1585 assert_eq!(layers[1].handle().unwrap().object_id(), base_layer_object_id);
1586
1587 for key in &must_keep_keys[..5] {
1591 assert_eq!(layers[1].maybe_contains_key(key), MaybeContainsKey::Maybe, "{key:?}");
1592 }
1593
1594 assert_eq!(
1597 layers[1].maybe_contains_key(&must_keep_keys[5]),
1598 MaybeContainsKey::RangeKeyTooLarge
1599 );
1600
1601 let mut minor_deleted_extents = HashSet::new();
1602 let mut iter = layers[0].seek(Bound::Unbounded).await.expect("seek failed");
1603 while let Some(item) = iter.get() {
1604 if matches!(item.value, ObjectValue::Extent(ExtentValue::None)) {
1605 minor_deleted_extents.insert(item.key.clone());
1606 }
1607 iter.advance().await.expect("advance failed");
1608 }
1609
1610 for key in &must_keep_keys {
1611 assert!(
1612 minor_deleted_extents.contains(key),
1613 "expected overlapping/large extent tombstone to be kept: {key:?}"
1614 );
1615 }
1616 for key in &elidable_keys {
1617 let expected_kept = layers[1].maybe_contains_key(key) != MaybeContainsKey::False;
1618 assert_eq!(
1619 minor_deleted_extents.contains(key),
1620 expected_kept,
1621 "unexpected retention state for non-overlapping extent tombstone: {key:?}"
1622 );
1623 }
1624 }
1625
1626 let mut buf_4k = large_base_file.allocate_buffer(4096).await;
1629 let mut buf_8k = large_base_file.allocate_buffer(8192).await;
1630
1631 assert_eq!(
1632 large_base_file
1633 .read_aligned(hole_start.start, buf_4k.as_mut())
1634 .await
1635 .expect("read_aligned failed"),
1636 4096
1637 );
1638 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0u8; 4096]);
1639 assert_eq!(
1640 large_base_file
1641 .read_aligned(hole_start.end, buf_4k.as_mut())
1642 .await
1643 .expect("read_aligned failed"),
1644 4096
1645 );
1646 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0xaa; 4096]);
1647
1648 assert_eq!(
1649 large_base_file
1650 .read_aligned(hole_interior.start - 4096, buf_4k.as_mut())
1651 .await
1652 .expect("read_aligned failed"),
1653 4096
1654 );
1655 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0xaa; 4096]);
1656 assert_eq!(
1657 large_base_file
1658 .read_aligned(hole_interior.start, buf_4k.as_mut())
1659 .await
1660 .expect("read_aligned failed"),
1661 4096
1662 );
1663 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0u8; 4096]);
1664 assert_eq!(
1665 large_base_file
1666 .read_aligned(hole_interior.end, buf_4k.as_mut())
1667 .await
1668 .expect("read_aligned failed"),
1669 4096
1670 );
1671 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0xaa; 4096]);
1672
1673 assert_eq!(
1674 large_base_file
1675 .read_aligned(hole_boundary.start - 4096, buf_4k.as_mut())
1676 .await
1677 .expect("read_aligned failed"),
1678 4096
1679 );
1680 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0xaa; 4096]);
1681 assert_eq!(
1682 large_base_file
1683 .read_aligned(hole_boundary.start, buf_8k.as_mut())
1684 .await
1685 .expect("read_aligned failed"),
1686 8192
1687 );
1688 assert_eq!(buf_8k.as_ptr_slice().to_vec(), vec![0u8; 8192]);
1689 assert_eq!(
1690 large_base_file
1691 .read_aligned(hole_boundary.end, buf_4k.as_mut())
1692 .await
1693 .expect("read_aligned failed"),
1694 4096
1695 );
1696 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0xaa; 4096]);
1697
1698 assert_eq!(
1699 large_base_file
1700 .read_aligned(hole_end.start - 4096, buf_4k.as_mut())
1701 .await
1702 .expect("read_aligned failed"),
1703 4096
1704 );
1705 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0xaa; 4096]);
1706 assert_eq!(
1707 large_base_file
1708 .read_aligned(hole_end.start, buf_4k.as_mut())
1709 .await
1710 .expect("read_aligned failed"),
1711 4096
1712 );
1713 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0u8; 4096]);
1714
1715 assert_eq!(
1716 tail_4m_file
1717 .read_aligned(tail_4m_offset, buf_4k.as_mut())
1718 .await
1719 .expect("read_aligned failed"),
1720 4096
1721 );
1722 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0u8; 4096]);
1723
1724 assert_eq!(
1725 tail_8m_file
1726 .read_aligned(tail_8m_offset, buf_4k.as_mut())
1727 .await
1728 .expect("read_aligned failed"),
1729 4096
1730 );
1731 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0u8; 4096]);
1732
1733 let mut buf_64k = prefix_base_file.allocate_buffer(64 * 1024).await;
1734 assert_eq!(
1735 prefix_base_file.read_aligned(0, buf_64k.as_mut()).await.expect("read_aligned failed"),
1736 64 * 1024
1737 );
1738 assert_eq!(buf_64k.as_ptr_slice().to_vec(), vec![0xdd; 64 * 1024]);
1739 assert_eq!(
1740 prefix_base_file.read_aligned(MIB, buf_4k.as_mut()).await.expect("read_aligned failed"),
1741 4096
1742 );
1743 assert_eq!(buf_4k.as_ptr_slice().to_vec(), vec![0u8; 4096]);
1744 assert_eq!(temp_extent_file.get_size(), 0);
1745
1746 fs.close().await.expect("close failed");
1747 }
1748
1749 #[fuchsia::test]
1750 async fn test_mixed_deletes_and_creates_reclaim_space_via_major_compaction() {
1751 let device = DeviceHolder::new(FakeDevice::new(16384, 512));
1752 let fs = FxFilesystemBuilder::new()
1753 .journal_options(JournalOptions { reclaim_size: 32768, ..Default::default() })
1754 .format(true)
1755 .open(device)
1756 .await
1757 .expect("open failed");
1758 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
1759 let store = root_volume
1760 .new_volume("test", NewChildStoreOptions::default())
1761 .await
1762 .expect("new_volume failed");
1763 let store_id = store.store_object_id();
1764 let root_dir =
1765 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
1766
1767 let num_files = 2000;
1769 let mut file_ids = Vec::with_capacity(num_files);
1770 for i in 0..num_files {
1771 let filename = format!("file_{i:04}_{:<200}", i);
1772 let mut transaction = store
1773 .new_transaction(
1774 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1775 Options::default(),
1776 )
1777 .await
1778 .expect("new_transaction failed");
1779 let file = root_dir
1780 .create_child_file(&mut transaction, &filename)
1781 .await
1782 .expect("create_child_file failed");
1783 transaction.commit().await.expect("commit failed");
1784 file_ids.push((file.object_id(), filename));
1785 }
1786 fs.journal().force_compact().await.expect("force_compact failed");
1787 fs.journal().force_compact().await.expect("force_compact failed");
1788
1789 let filler = {
1792 let mut transaction = store
1793 .new_transaction(
1794 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1795 Options::default(),
1796 )
1797 .await
1798 .expect("new_transaction failed");
1799 let file = root_dir
1800 .create_child_file(&mut transaction, "filler")
1801 .await
1802 .expect("create_child_file failed");
1803 transaction.commit().await.expect("commit failed");
1804 file
1805 };
1806 let chunk = filler.allocate_buffer(65536).await;
1807 let mut offset = 0;
1808 while filler.write_or_append(Some(offset), chunk.as_ref()).await.is_ok() {
1809 offset += 65536;
1810 }
1811 filler.truncate(offset - 512 * 1024).await.expect("truncate filler failed");
1812 fs.journal().force_compact().await.expect("force_compact failed");
1813
1814 for (i, (oid, filename)) in file_ids[0..1500].iter().enumerate() {
1820 {
1821 let mut context = root_dir
1822 .acquire_context_for_replace(None, filename.as_str(), true)
1823 .await
1824 .expect("acquire_context failed");
1825 replace_child(&mut context.transaction, None, (&root_dir, filename.as_str()))
1826 .await
1827 .expect("replace_child failed");
1828 context.transaction.commit().await.expect("commit failed");
1829 }
1830 fs.graveyard().queue_tombstone_object(store_id, *oid);
1831 fs.graveyard().flush().await;
1832
1833 let new_filename = format!("new_{i:04}_{:<200}", i);
1834 let mut transaction = store
1835 .new_transaction(
1836 lock_keys![LockKey::object(store_id, root_dir.object_id())],
1837 Options::default(),
1838 )
1839 .await
1840 .expect("new_transaction failed");
1841 root_dir
1842 .create_child_file(&mut transaction, &new_filename)
1843 .await
1844 .expect("create_child_file failed");
1845 transaction.commit().await.expect("commit failed");
1846 }
1847
1848 fs.close().await.expect("close failed");
1849 }
1850}
1851
1852impl tree::MajorCompactable<ObjectKey, ObjectValue> for LSMTree<ObjectKey, ObjectValue> {
1853 async fn major_iter(
1854 iter: impl LayerIterator<ObjectKey, ObjectValue>,
1855 ) -> Result<impl LayerIterator<ObjectKey, ObjectValue>, Error> {
1856 iter.filter(|item: ItemRef<'_, _, _>| match item {
1857 ItemRef { value: ObjectValue::None, .. } => false,
1859 ItemRef { value: ObjectValue::Extent(ExtentValue::None), .. } => false,
1861 _ => true,
1862 })
1863 .await
1864 }
1865
1866 async fn minor_iter(
1867 iter: impl LayerIterator<ObjectKey, ObjectValue>,
1868 layers_to_keep: impl AsRef<[LockedLayer<ObjectKey, ObjectValue>]> + Send + Sync,
1869 ) -> Result<impl LayerIterator<ObjectKey, ObjectValue>, Error> {
1870 iter.filter(move |item: ItemRef<'_, _, _>| match item {
1871 ItemRef { key, value: ObjectValue::None | ObjectValue::Extent(ExtentValue::None) } => {
1873 layers_to_keep
1874 .as_ref()
1875 .iter()
1876 .any(|layer| layer.maybe_contains_key(key) != MaybeContainsKey::False)
1877 }
1878 _ => true,
1879 })
1880 .await
1881 }
1882}