1use crate::fuchsia::file::FxFile;
6use crate::fuchsia::fxblob::BlobDirectory;
7use crate::fuchsia::fxblob::blob::FxBlob;
8use crate::fuchsia::node::{FxNode, OpenedNode};
9use crate::fuchsia::pager::PagerBacked;
10use crate::fuchsia::volume::FxVolume;
11use anyhow::{Context as _, Error, anyhow, ensure};
12use arrayref::{array_refs, mut_array_refs};
13use async_trait::async_trait;
14use fuchsia_async as fasync;
15use fuchsia_hash::Hash;
16use futures::future::{self, BoxFuture, join_all};
17use futures::lock::Mutex;
18use futures::{FutureExt, select};
19use fxfs::errors::FxfsError;
20use fxfs::log::*;
21use fxfs::object_handle::{INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle, WriteObjectHandle};
22use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
23use fxfs::object_store::{
24 DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectStore, Timestamp, VOLUME_DATA_KEY_ID,
25 directory,
26};
27use linked_hash_map::LinkedHashMap;
28use scopeguard::ScopeGuard;
29use std::cmp::{Eq, PartialEq};
30use std::collections::btree_map::{BTreeMap, Entry};
31use std::marker::PhantomData;
32use std::mem::size_of;
33use std::pin::pin;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicU64, Ordering};
36use vfs::execution_scope::ActiveGuard;
37
38const FILE_OPEN_MARKER: u64 = u64::MAX;
39const REPLAY_THREADS: usize = 2;
40const MESSAGE_CHUNK_SIZE: usize = 64;
43const IO_SIZE: usize = 1 << 17; pub static RECORDED: AtomicU64 = AtomicU64::new(0);
46
47pub trait RecordingHandle: Send + Sync {
49 fn append<'a>(
51 &'a self,
52 buf: storage_device::buffer::BufferRef<'a>,
53 ) -> futures::future::BoxFuture<'a, Result<u64, Error>>;
54
55 fn allocate_buffer(
56 &self,
57 size: usize,
58 ) -> futures::future::BoxFuture<'_, storage_device::buffer::Buffer<'_>>;
59
60 fn block_size(&self) -> usize;
61
62 fn commit(self: Box<Self>) -> futures::future::BoxFuture<'static, Result<(), Error>>;
64
65 fn abort_cleanup(self: Box<Self>);
68}
69
70pub struct FileRecordingHandle {
72 name: String,
73 volume: Arc<FxVolume>,
74 handle: DataObjectHandle<FxVolume>,
75}
76
77impl FileRecordingHandle {
78 pub async fn new(name: &str, volume: Arc<FxVolume>) -> Result<Self, Error> {
79 let store = volume.store();
80 let mut transaction = store.new_transaction(lock_keys![], Options::default()).await?;
81 let handle =
82 ObjectStore::create_object(&volume, &mut transaction, HandleOptions::default(), None)
83 .await?;
84 store.add_to_graveyard(&mut transaction, handle.object_id());
85 transaction.commit().await?;
86
87 Ok(Self { name: name.to_string(), volume, handle })
88 }
89
90 async fn commit_impl(&self) -> Result<(), Error> {
91 let store = self.volume.store();
92 let fs = store.filesystem();
93 let profile_dir = self.volume.get_profile_directory().await?;
94
95 let mut lock_keys =
96 lock_keys![LockKey::object(store.store_object_id(), profile_dir.object_id())];
97 let mut old_id = INVALID_OBJECT_ID;
98 let mut transaction = loop {
99 let transaction = store.new_transaction(lock_keys, Options::default()).await?;
100 if let Some((id, descriptor, _)) = profile_dir.lookup(&self.name).await? {
101 ensure!(matches!(descriptor, ObjectDescriptor::File), FxfsError::Inconsistent);
102 if id == old_id {
103 break transaction;
104 }
105 lock_keys = lock_keys![
106 LockKey::object(store.store_object_id(), profile_dir.object_id()),
107 LockKey::object(store.store_object_id(), id)
108 ];
109 old_id = id;
110 } else {
111 old_id = INVALID_OBJECT_ID;
112 break transaction;
113 }
114 };
115
116 store.remove_from_graveyard(&mut transaction, self.handle.object_id());
117 directory::replace_child_with_object(
118 &mut transaction,
119 Some((self.handle.object_id(), ObjectDescriptor::File)),
120 (&profile_dir, &self.name),
121 0,
122 false,
123 Timestamp::now(),
124 )
125 .await?;
126 transaction.commit().await?;
127
128 if old_id != INVALID_OBJECT_ID {
129 fs.graveyard().queue_tombstone_object(store.store_object_id(), old_id);
130 }
131
132 Ok(())
133 }
134}
135
136impl RecordingHandle for FileRecordingHandle {
137 fn append<'a>(
138 &'a self,
139 buf: storage_device::buffer::BufferRef<'a>,
140 ) -> futures::future::BoxFuture<'a, Result<u64, Error>> {
141 async move { self.handle.write_or_append(None, buf).await.map_err(Into::into) }.boxed()
142 }
143
144 fn allocate_buffer(
145 &self,
146 size: usize,
147 ) -> futures::future::BoxFuture<'_, storage_device::buffer::Buffer<'_>> {
148 self.handle.allocate_buffer(size).boxed()
149 }
150
151 fn block_size(&self) -> usize {
152 self.handle.block_size() as usize
153 }
154
155 fn commit(self: Box<Self>) -> futures::future::BoxFuture<'static, Result<(), Error>> {
156 async move {
157 let store = self.volume.store();
158 self.commit_impl().await.inspect_err(|_| {
159 store
160 .filesystem()
161 .graveyard()
162 .queue_tombstone_object(store.store_object_id(), self.handle.object_id());
163 })
164 }
165 .boxed()
166 }
167
168 fn abort_cleanup(self: Box<Self>) {
169 let this = *self;
170 this.volume
171 .store()
172 .filesystem()
173 .graveyard()
174 .queue_tombstone_object(this.volume.store().store_object_id(), this.handle.object_id());
175 }
176}
177
178trait RecordedVolume: Send + Sync + Sized + Unpin {
179 type IdType: std::fmt::Display + Ord + Send + Sized;
180 type NodeType: PagerBacked;
181 type MessageType: Message<IdType = Self::IdType>;
182
183 fn new(volume: Arc<FxVolume>) -> Self;
184
185 fn open(
186 &self,
187 id: Self::IdType,
188 ) -> impl std::future::Future<Output = Result<OpenedNode<Self::NodeType>, Error>> + Send;
189
190 fn file_is_replayable(
192 &self,
193 id: &Self::IdType,
194 ) -> impl std::future::Future<Output = bool> + Send;
195
196 fn read_and_queue(
197 &self,
198 handle: Box<dyn ReadObjectHandle>,
199 sender: &async_channel::Sender<Request<Self::NodeType>>,
200 local_cache: &mut BTreeMap<Self::IdType, Option<OpenedNode<Self::NodeType>>>,
201 ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
202 async move {
203 let mut io_buf = handle.allocate_buffer(IO_SIZE).await;
204 let block_size = handle.block_size() as usize;
205 let file_size = handle.get_size() as usize;
206 let mut offset = 0;
207 while offset < file_size {
208 let actual = handle
209 .read(offset as u64, io_buf.as_mut())
210 .await
211 .map_err(|e| e.context(format!("Failed to read at offset: {}", offset)))?;
212 offset += actual;
213 let mut local_offset = 0;
214 let mut next_block = block_size;
215 let mut next_offset = size_of::<Self::MessageType>();
216 while next_offset <= actual {
217 let msg = Self::MessageType::decode_from(
218 &io_buf.as_slice()[local_offset..next_offset],
219 );
220
221 local_offset = next_offset;
222 next_offset = local_offset + size_of::<Self::MessageType>();
223 if next_offset > next_block {
225 local_offset = next_block;
226 next_offset = local_offset + size_of::<Self::MessageType>();
227 next_block += block_size;
228 }
229
230 if msg.is_zeroes() {
233 break;
234 }
235
236 let file = match local_cache.entry(msg.id()) {
237 Entry::Occupied(entry) => match entry.get() {
238 Some(opened_file) => (*opened_file).clone(),
239 None => continue,
241 },
242 Entry::Vacant(entry) => match self.open(msg.id()).await {
243 Err(e) => {
244 debug!("Failed to open object {} from profile: {:?}", msg.id(), e);
245 entry.insert(None);
247 continue;
248 }
249 Ok(opened_file) => {
250 let file_clone = opened_file.clone();
251 entry.insert(Some(opened_file));
252 file_clone
253 }
254 },
255 };
256
257 sender.send(Request { file, offset: msg.offset() }).await?;
258 }
259 }
260 Ok(())
261 }
262 }
263
264 fn record(
265 &self,
266 recording_handle: Box<dyn RecordingHandle>,
267 receiver: async_channel::Receiver<Vec<Self::MessageType>>,
268 ) -> impl std::future::Future<Output = Result<(), Error>> + Send {
269 let recording_handle = scopeguard::guard(recording_handle, |recording_handle| {
271 recording_handle.abort_cleanup();
272 });
273
274 async move {
275 let mut recorded_offsets = LinkedHashMap::<Self::MessageType, ()>::new();
276 let mut recorded_opens = BTreeMap::<Self::IdType, bool>::new();
277 while let Ok(buffer) = receiver.recv().await {
278 for message in buffer {
279 if message.is_open_marker() {
280 if let Entry::Vacant(entry) = recorded_opens.entry(message.id()) {
281 let usable = self.file_is_replayable(entry.key()).await;
282 entry.insert(usable);
283 }
284 } else {
285 recorded_offsets.insert(message, ());
286 }
287 }
288 }
289
290 let block_size = recording_handle.block_size();
291 let mut offset = 0;
292 let mut io_buf = recording_handle.allocate_buffer(IO_SIZE).await;
293 let mut next_block = block_size;
294 while let Some((message, _)) = recorded_offsets.pop_front() {
295 if !recorded_opens.get(&message.id()).copied().unwrap_or(false) {
297 continue;
298 }
299
300 let mut next_offset = offset + size_of::<Self::MessageType>();
301 if next_offset > next_block {
302 io_buf.as_mut_slice()[offset..next_block].fill(0);
305 if next_block >= IO_SIZE {
306 recording_handle
307 .append(io_buf.as_ref())
308 .await
309 .context("Failed to write profile block")?;
310 offset = 0;
311 next_offset = size_of::<Self::MessageType>();
312 next_block = block_size;
313 } else {
314 offset = next_block;
315 next_offset = offset + size_of::<Self::MessageType>();
316 next_block += block_size;
317 }
318 }
319 message.encode_to(&mut io_buf.as_mut_slice()[offset..next_offset]);
320 offset = next_offset;
321 }
322 if offset > 0 {
323 io_buf.as_mut_slice()[offset..next_block].fill(0);
324 recording_handle
325 .append(io_buf.subslice(0..next_block))
326 .await
327 .context("Failed to write profile block")?;
328 }
329
330 std::mem::drop(io_buf);
331 let recording_handle = ScopeGuard::into_inner(recording_handle);
333 recording_handle.commit().await?;
334
335 Ok(())
336 }
337 }
338}
339
340struct BlobVolume {
341 volume: Arc<FxVolume>,
342 root_dir: Mutex<Option<Arc<BlobDirectory>>>,
345}
346
347impl RecordedVolume for BlobVolume {
348 type IdType = Hash;
349 type NodeType = FxBlob;
350 type MessageType = BlobMessage;
351
352 fn new(volume: Arc<FxVolume>) -> Self {
353 Self { volume, root_dir: Mutex::new(None) }
354 }
355
356 async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
357 let mut root_dir = self.root_dir.lock().await;
358 if root_dir.is_none() {
359 *root_dir = Some(
360 self.volume
361 .get_or_load_node(
362 self.volume.store().root_directory_object_id(),
363 ObjectDescriptor::Directory,
364 None,
365 )
366 .await?
367 .into_any()
368 .downcast::<BlobDirectory>()
369 .map_err(|_| FxfsError::Inconsistent)?,
370 );
371 };
372 root_dir
373 .as_ref()
374 .unwrap()
375 .open_blob(&id.into())
376 .await?
377 .ok_or_else(|| FxfsError::NotFound.into())
378 }
379
380 async fn file_is_replayable(&self, _id: &Self::IdType) -> bool {
381 true
383 }
384}
385
386struct FileVolume {
387 volume: Arc<FxVolume>,
388}
389
390impl RecordedVolume for FileVolume {
391 type IdType = u64;
392 type NodeType = FxFile;
393 type MessageType = FileMessage;
394
395 fn new(volume: Arc<FxVolume>) -> Self {
396 Self { volume }
397 }
398
399 async fn open(&self, id: Self::IdType) -> Result<OpenedNode<Self::NodeType>, Error> {
400 self.volume
401 .get_or_load_node(id, ObjectDescriptor::File, None)
402 .await?
403 .into_any()
404 .downcast::<FxFile>()
405 .map_err(|_| anyhow!("Non-file opened"))?
406 .into_opened_node()
407 .ok_or_else(|| anyhow!("File being purged"))
408 }
409
410 async fn file_is_replayable(&self, id: &Self::IdType) -> bool {
411 match self.volume.store().get_keys(*id).await {
412 Ok(keys)
415 if keys.is_empty()
416 || (keys.len() == 1 && keys.first().unwrap().0 == VOLUME_DATA_KEY_ID) =>
417 {
418 true
419 }
420 _ => false,
421 }
422 }
423}
424
425trait Message: Eq + PartialEq + Sized + Send + Sync + std::hash::Hash + 'static {
426 type IdType: std::fmt::Display + Ord + Send + Sized;
427
428 fn id(&self) -> Self::IdType;
429 fn offset(&self) -> u64;
430 fn encode_to(&self, dest: &mut [u8]);
431 fn decode_from(src: &[u8]) -> Self;
432 fn is_zeroes(&self) -> bool;
433 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error>;
434 fn is_open_marker(&self) -> bool;
435}
436
437#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
438struct BlobMessage {
439 id: Hash,
440 offset: u64,
443}
444
445impl BlobMessage {
446 fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
447 let (first, second) = mut_array_refs![dest, size_of::<Hash>(), size_of::<u64>()];
448 *first = self.id.into();
449 *second = self.offset.to_le_bytes();
450 }
451
452 fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
453 let (first, second) = array_refs!(src, size_of::<Hash>(), size_of::<u64>());
454 Self { id: Hash::from_array(*first), offset: u64::from_le_bytes(*second) }
455 }
456}
457
458impl Message for BlobMessage {
459 type IdType = Hash;
460
461 fn id(&self) -> Self::IdType {
462 self.id
463 }
464
465 fn offset(&self) -> u64 {
466 self.offset
467 }
468
469 fn encode_to(&self, dest: &mut [u8]) {
470 self.encode_to_impl(dest.try_into().unwrap());
471 }
472
473 fn decode_from(src: &[u8]) -> Self {
474 Self::decode_from_impl(src.try_into().unwrap())
475 }
476
477 fn is_zeroes(&self) -> bool {
478 self.id == Hash::from_array([0u8; size_of::<Hash>()]) && self.offset == 0
479 }
480
481 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
482 match node.into_any().downcast::<FxBlob>() {
483 Ok(blob) => Ok(Self { id: blob.root(), offset }),
484 Err(_) => Err(anyhow!("Cannot record non-blob entry.")),
485 }
486 }
487
488 fn is_open_marker(&self) -> bool {
489 self.offset == FILE_OPEN_MARKER
490 }
491}
492
493#[derive(Debug, Eq, std::hash::Hash, PartialEq)]
494struct FileMessage {
495 id: u64,
496 offset: u64,
499}
500
501impl FileMessage {
502 fn encode_to_impl(&self, dest: &mut [u8; size_of::<Self>()]) {
503 let (first, second) = mut_array_refs![dest, size_of::<u64>(), size_of::<u64>()];
504 *first = self.id.to_le_bytes();
505 *second = self.offset.to_le_bytes();
506 }
507
508 fn decode_from_impl(src: &[u8; size_of::<Self>()]) -> Self {
509 let (first, second) = array_refs!(src, size_of::<u64>(), size_of::<u64>());
510 Self { id: u64::from_le_bytes(*first), offset: u64::from_le_bytes(*second) }
511 }
512}
513
514impl Message for FileMessage {
515 type IdType = u64;
516
517 fn id(&self) -> Self::IdType {
518 self.id
519 }
520
521 fn offset(&self) -> u64 {
522 self.offset
523 }
524
525 fn encode_to(&self, dest: &mut [u8]) {
526 self.encode_to_impl(dest.try_into().unwrap())
527 }
528
529 fn decode_from(src: &[u8]) -> Self {
530 Self::decode_from_impl(src.try_into().unwrap())
531 }
532
533 fn is_zeroes(&self) -> bool {
534 self.id == 0 && self.offset == 0
535 }
536
537 fn from_node_request(node: Arc<dyn FxNode>, offset: u64) -> Result<Self, Error> {
538 match node.into_any().downcast::<FxFile>() {
539 Ok(file) => Ok(Self { id: file.object_id(), offset }),
540 Err(_) => Err(anyhow!("Cannot record non-file entry")),
541 }
542 }
543
544 fn is_open_marker(&self) -> bool {
545 self.offset == FILE_OPEN_MARKER
546 }
547}
548
549pub trait Recorder: Send + Sync {
552 fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error>;
554
555 fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error>;
557}
558
559struct RecorderImpl<T: Message> {
560 sender: async_channel::Sender<Vec<T>>,
561 buffer: Vec<T>,
562}
563
564impl<T: Message> RecorderImpl<T> {
565 fn new(sender: async_channel::Sender<Vec<T>>) -> Self {
566 Self { sender, buffer: Vec::with_capacity(MESSAGE_CHUNK_SIZE) }
567 }
568}
569
570impl<T: Message> Recorder for RecorderImpl<T> {
571 fn record(&mut self, node: Arc<dyn FxNode>, offset: u64) -> Result<(), Error> {
572 self.buffer.push(T::from_node_request(node, offset)?);
573 if self.buffer.len() >= MESSAGE_CHUNK_SIZE {
574 self.sender.try_send(std::mem::replace(
577 &mut self.buffer,
578 Vec::with_capacity(MESSAGE_CHUNK_SIZE),
579 ))?;
580 }
581 RECORDED.fetch_add(1, Ordering::Relaxed);
582 Ok(())
583 }
584
585 fn record_open(&mut self, node: Arc<dyn FxNode>) -> Result<(), Error> {
586 self.record(node, FILE_OPEN_MARKER)
587 }
588}
589
590impl<T: Message> Drop for RecorderImpl<T> {
591 fn drop(&mut self) {
592 if self.buffer.len() > 0 {
594 let buffer = std::mem::take(&mut self.buffer);
595 let _ = self.sender.try_send(buffer);
596 }
597 }
598}
599
600struct Request<P: PagerBacked> {
601 file: Arc<P>,
602 offset: u64,
603}
604
605struct ReplayState<T> {
606 replay_threads: future::Shared<BoxFuture<'static, ()>>,
607 _cache_task: fasync::Task<()>,
608 _phantom: PhantomData<T>,
609}
610
611impl<T: RecordedVolume> ReplayState<T> {
612 fn new(handle: Box<dyn ReadObjectHandle>, volume: Arc<FxVolume>, guard: ActiveGuard) -> Self {
613 let (sender, receiver) = async_channel::unbounded::<Request<T::NodeType>>();
614
615 let mut replay_threads = Vec::with_capacity(REPLAY_THREADS);
618 for _ in 0..REPLAY_THREADS {
619 let receiver = receiver.clone();
620 let guard = guard.clone();
623 replay_threads.push(fasync::unblock(move || {
624 let _guard = guard;
625 Self::page_in_thread(receiver);
626 }));
627 }
628 let replay_threads = (Box::pin(async {
629 join_all(replay_threads).await;
630 }) as BoxFuture<'static, ()>)
631 .shared();
632
633 let scope = volume.scope().clone();
634 let cache_task = scope
635 .spawn({
636 async move {
640 let mut task = pin!(
641 async {
642 let mut local_cache: BTreeMap<
646 T::IdType,
647 Option<OpenedNode<T::NodeType>>,
648 > = BTreeMap::new();
649
650 let volume_id = volume.id();
651
652 if let Err(error) = T::new(volume)
653 .read_and_queue(handle, &sender, &mut local_cache)
654 .await
655 {
656 error!(error:?; "Failed to read back profile");
657 }
658 sender.close();
659
660 info!(
661 "Replay for volume {} opened {} of {} objects.",
662 volume_id,
663 local_cache.iter().filter(|(_, e)| e.is_some()).count(),
664 local_cache.len()
665 );
666
667 let () = std::future::pending().await;
669 }
670 .fuse()
671 );
672
673 select! {
674 _ = task => {}
675 _ = guard.on_cancel().fuse() => {}
676 }
677 }
678 })
679 .into();
680
681 Self { replay_threads, _cache_task: cache_task, _phantom: PhantomData }
682 }
683
684 fn page_in_thread(queue: async_channel::Receiver<Request<T::NodeType>>) {
685 while let Ok(request) = queue.recv_blocking() {
686 let res = request.file.vmo().op_range(
687 zx::VmoOp::PREFETCH,
688 request.offset,
689 zx::system_get_page_size() as u64,
690 );
691 if let Err(e) = res {
692 warn!("Failed to prefetch page: {:?}", e);
693 }
694 if queue.sender_count() == 0 {
696 return;
697 }
698 }
699 }
700}
701
702#[async_trait]
705pub trait ProfileState: Send + Sync {
706 fn record_new(
710 &mut self,
711 volume: &Arc<FxVolume>,
712 recording_handle: Box<dyn RecordingHandle>,
713 ) -> Box<dyn Recorder>;
714
715 fn replay_profile(
718 &mut self,
719 handle: Box<dyn ReadObjectHandle>,
720 volume: Arc<FxVolume>,
721 guard: ActiveGuard,
722 );
723
724 async fn wait_for_replay_to_finish(&mut self);
727
728 async fn wait_for_recording_to_finish(&mut self);
730}
731
732pub fn new_profile_state(is_blob: bool) -> Box<dyn ProfileState> {
733 if is_blob {
734 Box::new(ProfileStateImpl::<BlobVolume>::new())
735 } else {
736 Box::new(ProfileStateImpl::<FileVolume>::new())
737 }
738}
739
740struct ProfileStateImpl<T> {
741 recording: Option<fasync::CancelableJoinHandle<()>>,
742 replay: Option<ReplayState<T>>,
743}
744
745impl<T> ProfileStateImpl<T> {
746 fn new() -> Self {
747 Self { recording: None, replay: None }
748 }
749}
750
751#[async_trait]
752impl<T: RecordedVolume> ProfileState for ProfileStateImpl<T> {
753 fn record_new(
754 &mut self,
755 volume: &Arc<FxVolume>,
756 recording_handle: Box<dyn RecordingHandle>,
757 ) -> Box<dyn Recorder> {
758 let (sender, receiver) = async_channel::unbounded();
759 let volume = volume.clone();
760 self.recording = None;
762 let scope = volume.scope().clone();
763 self.recording = Some(
764 scope
765 .spawn(async move {
766 let recording = T::new(volume);
767 if let Err(error) = recording.record(recording_handle, receiver).await {
768 warn!(error:?; "Profile recording failed");
769 }
770 })
771 .into(),
772 );
773 Box::new(RecorderImpl::new(sender))
774 }
775
776 fn replay_profile(
777 &mut self,
778 handle: Box<dyn ReadObjectHandle>,
779 volume: Arc<FxVolume>,
780 guard: ActiveGuard,
781 ) {
782 self.replay = Some(ReplayState::new(handle, volume, guard));
783 }
784
785 async fn wait_for_replay_to_finish(&mut self) {
786 if let Some(replay) = &mut self.replay {
787 replay.replay_threads.clone().await;
788 }
789 }
790
791 async fn wait_for_recording_to_finish(&mut self) {
792 if let Some(recording) = self.recording.take() {
793 let _ = recording.await;
794 }
795 }
796}
797
798#[cfg(test)]
799mod tests {
800 use super::{
801 BlobMessage, BlobVolume, FileMessage, FileRecordingHandle, FileVolume, IO_SIZE, Message,
802 RecordedVolume, Request, new_profile_state,
803 };
804 use crate::fuchsia::file::FxFile;
805 use crate::fuchsia::fxblob::blob::FxBlob;
806 use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture, open_blob_fixture};
807 use crate::fuchsia::node::{FxNode, OpenedNode};
808 use crate::fuchsia::pager::PagerBacked;
809 use crate::fuchsia::testing::{TestFixture, TestFixtureOptions, open_file_checked};
810 use crate::fuchsia::volume::FxVolume;
811 use anyhow::Error;
812 use async_trait::async_trait;
813 use delivery_blob::CompressionMode;
814 use event_listener::{Event, EventListener};
815 use fidl_fuchsia_io as fio;
816 use fuchsia_async as fasync;
817 use fuchsia_hash::Hash;
818 use fuchsia_sync::Mutex;
819 use fxfs::object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle};
820 use fxfs::object_store::transaction::{LockKey, Options, lock_keys};
821 use fxfs::object_store::{DataObjectHandle, HandleOptions, ObjectDescriptor, ObjectStore};
822 use std::collections::BTreeMap;
823 use std::mem::size_of;
824 use std::sync::Arc;
825 use std::time::Duration;
826 use storage_device::buffer::{BufferRef, MutableBufferRef};
827 use storage_device::buffer_allocator::{BufferAllocator, BufferFuture, BufferSource};
828
829 struct FakeReaderWriterInner {
830 data: Vec<u8>,
831 delays: Vec<EventListener>,
832 }
833
834 struct FakeReaderWriter {
835 allocator: BufferAllocator,
836 inner: Arc<Mutex<FakeReaderWriterInner>>,
837 }
838
839 const BLOCK_SIZE: usize = 4096;
840
841 impl FakeReaderWriter {
842 fn new() -> Self {
843 Self {
844 allocator: BufferAllocator::new(BLOCK_SIZE, BufferSource::new(IO_SIZE * 2)),
845 inner: Arc::new(Mutex::new(FakeReaderWriterInner {
846 data: Vec::new(),
847 delays: Vec::new(),
848 })),
849 }
850 }
851
852 fn push_delay(&self, delay: EventListener) {
853 self.inner.lock().delays.insert(0, delay);
854 }
855 }
856
857 impl ObjectHandle for FakeReaderWriter {
858 fn object_id(&self) -> u64 {
859 0
860 }
861
862 fn block_size(&self) -> u64 {
863 self.allocator.block_size() as u64
864 }
865
866 fn allocate_buffer(&self, size: usize) -> BufferFuture<'_> {
867 self.allocator.allocate_buffer(size)
868 }
869 }
870
871 impl WriteObjectHandle for FakeReaderWriter {
872 async fn write_or_append(
873 &self,
874 offset: Option<u64>,
875 buf: BufferRef<'_>,
876 ) -> Result<u64, Error> {
877 assert!(offset.is_none());
879 let delay = self.inner.lock().delays.pop();
880 if let Some(delay) = delay {
881 delay.await;
882 }
883 self.inner.lock().data.extend_from_slice(buf.as_slice());
885 Ok(buf.len() as u64)
886 }
887
888 async fn truncate(&self, _size: u64) -> Result<(), Error> {
889 unreachable!();
890 }
891
892 async fn flush(&self) -> Result<(), Error> {
893 unreachable!();
894 }
895 }
896
897 async fn write_file(fixture: &TestFixture, name: &str, data: &[u8]) -> u64 {
898 let root_dir = fixture.volume().root_dir();
899 let mut transaction = fixture
900 .volume()
901 .volume()
902 .store()
903 .new_transaction(
904 lock_keys![LockKey::object(
905 fixture.volume().volume().store().store_object_id(),
906 root_dir.object_id()
907 )],
908 Options::default(),
909 )
910 .await
911 .expect("Creating transaction for new file");
912 let id = root_dir
913 .directory()
914 .create_child_file(&mut transaction, name)
915 .await
916 .expect("Creating new_file")
917 .object_id();
918 transaction.commit().await.unwrap();
919 let file = open_file_checked(
920 fixture.root(),
921 name,
922 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::Flags::PROTOCOL_FILE,
923 &Default::default(),
924 )
925 .await;
926 file.write(data).await.unwrap().expect("Writing file");
927 id
928 }
929
930 #[async_trait]
931 impl ReadObjectHandle for FakeReaderWriter {
932 async fn read(&self, offset: u64, mut buf: MutableBufferRef<'_>) -> Result<usize, Error> {
933 let delay = self.inner.lock().delays.pop();
934 if let Some(delay) = delay {
935 delay.await;
936 }
937 let inner = self.inner.lock();
939 assert!(offset as usize <= inner.data.len());
940 let offset_end = std::cmp::min(offset as usize + buf.len(), inner.data.len());
941 let size = offset_end - offset as usize;
942 buf.as_mut_slice()[..size].clone_from_slice(&inner.data[offset as usize..offset_end]);
943 Ok(size)
944 }
945
946 fn get_size(&self) -> u64 {
947 self.inner.lock().data.len() as u64
948 }
949 }
950
951 #[fuchsia::test]
952 async fn test_encode_decode_blob() {
953 let mut buf = [0u8; size_of::<BlobMessage>()];
954 let m = BlobMessage { id: [88u8; 32].into(), offset: 77 };
955 m.encode_to(&mut buf.as_mut_slice());
956 let m2 = BlobMessage::decode_from(&buf);
957 assert_eq!(m, m2);
958 }
959
960 #[fuchsia::test]
961 async fn test_encode_decode_file() {
962 let mut buf = [0u8; size_of::<FileMessage>()];
963 let m = FileMessage { id: 88, offset: 77 };
964 m.encode_to(&mut buf.as_mut_slice());
965 let m2 = FileMessage::decode_from(&buf);
966 assert!(!m2.is_zeroes());
967 assert_eq!(m, m2);
968 }
969
970 const TEST_PROFILE_NAME: &str = "test_profile";
971
972 async fn get_test_profile_handle(volume: &Arc<FxVolume>) -> DataObjectHandle<FxVolume> {
973 let profile_dir = volume.get_profile_directory().await.unwrap();
974 ObjectStore::open_object(
975 volume,
976 profile_dir
977 .lookup(TEST_PROFILE_NAME)
978 .await
979 .expect("lookup failed")
980 .expect("not found")
981 .0,
982 HandleOptions::default(),
983 None,
984 )
985 .await
986 .unwrap()
987 }
988
989 async fn get_test_profile_contents(volume: &Arc<FxVolume>) -> Vec<u8> {
990 get_test_profile_handle(volume).await.contents(1024 * 1024).await.unwrap().to_vec()
991 }
992
993 #[fuchsia::test]
994 async fn test_recording_basic_blob() {
995 let fixture = new_blob_fixture().await;
996 {
997 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
998 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
999
1000 let mut state = new_profile_state(true);
1001 let volume = fixture.volume().volume();
1002
1003 {
1004 let handle =
1006 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1007 let mut recorder = state.record_new(volume, Box::new(handle));
1008 recorder.record(blob.clone(), 0).unwrap();
1009 recorder.record_open(blob).unwrap();
1010 }
1011
1012 state.wait_for_recording_to_finish().await;
1013
1014 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1015 }
1016 fixture.close().await;
1017 }
1018
1019 #[fuchsia::test]
1020 async fn test_recording_basic_file() {
1021 let fixture = TestFixture::new().await;
1022 {
1023 let id = write_file(&fixture, "foo", &[88u8]).await;
1024 let node = fixture
1025 .volume()
1026 .volume()
1027 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1028 .await
1029 .unwrap();
1030
1031 let mut state = new_profile_state(false);
1032 let volume = fixture.volume().volume();
1033
1034 {
1035 let handle =
1037 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1038 let mut recorder = state.record_new(volume, Box::new(handle));
1039 recorder.record(node.clone(), 0).unwrap();
1040 recorder.record_open(node).unwrap();
1041 }
1042 state.wait_for_recording_to_finish().await;
1043
1044 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE);
1045 }
1046 fixture.close().await;
1047 }
1048
1049 #[fuchsia::test]
1050 async fn test_recording_filtered_without_open() {
1051 let fixture = new_blob_fixture().await;
1052 {
1053 let hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1054 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1055
1056 let mut state = new_profile_state(true);
1057 let volume = fixture.volume().volume();
1058
1059 {
1060 let handle =
1062 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1063 let mut recorder = state.record_new(volume, Box::new(handle));
1064 recorder.record(blob.clone(), 0).unwrap();
1065 }
1066 state.wait_for_recording_to_finish().await;
1067
1068 assert_eq!(get_test_profile_contents(volume).await.len(), 0);
1069 }
1070 fixture.close().await;
1071 }
1072
1073 #[fuchsia::test]
1074 async fn test_recording_blob_more_than_block() {
1075 let mut state = new_profile_state(true);
1076
1077 let fixture = new_blob_fixture().await;
1078 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1079 let message_count = (fixture.fs().block_size() as usize / size_of::<BlobMessage>()) + 1;
1080 let hash;
1081 let volume = fixture.volume().volume();
1082
1083 {
1084 hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1085 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1086 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1088 let mut recorder = state.record_new(volume, Box::new(handle));
1089 recorder.record_open(blob.clone()).unwrap();
1090 for i in 0..message_count {
1091 recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1092 }
1093 }
1094 state.wait_for_recording_to_finish().await;
1095
1096 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1097
1098 let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1099 let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1100
1101 let volume = fixture.volume().volume().clone();
1102 let task = fasync::Task::spawn(async move {
1103 let handle = Box::new(get_test_profile_handle(&volume).await);
1104 let blob = BlobVolume::new(volume);
1105 blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1106 });
1107
1108 let mut recv_count = 0;
1109 while let Ok(msg) = receiver.recv().await {
1110 assert_eq!(msg.file.root(), hash);
1111 assert_eq!(msg.offset, 4096 * recv_count);
1112 recv_count += 1;
1113 }
1114 task.await;
1115 assert_eq!(recv_count, message_count as u64);
1116
1117 fixture.close().await;
1118 }
1119
1120 #[fuchsia::test]
1121 async fn test_recording_file_more_than_block() {
1122 let mut state = new_profile_state(false);
1123
1124 let fixture = TestFixture::new().await;
1125 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1126 let message_count = (fixture.fs().block_size() as usize / size_of::<FileMessage>()) + 1;
1127 let id;
1128 let volume = fixture.volume().volume();
1129 {
1130 id = write_file(&fixture, "foo", &[88u8]).await;
1131 let node = volume
1132 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1133 .await
1134 .unwrap();
1135 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1137 let mut recorder = state.record_new(volume, Box::new(handle));
1138 recorder.record_open(node.clone()).unwrap();
1139 for i in 0..message_count {
1140 recorder.record(node.clone(), 4096 * i as u64).unwrap();
1141 }
1142 }
1143 state.wait_for_recording_to_finish().await;
1144
1145 assert_eq!(get_test_profile_contents(volume).await.len(), BLOCK_SIZE * 2);
1146
1147 let mut local_cache: BTreeMap<u64, Option<OpenedNode<FxFile>>> = BTreeMap::new();
1148 let (sender, receiver) = async_channel::unbounded::<Request<FxFile>>();
1149
1150 let volume = fixture.volume().volume().clone();
1151 let task = fasync::Task::spawn(async move {
1152 let handle = Box::new(get_test_profile_handle(&volume).await);
1153 let file = FileVolume::new(volume);
1154 file.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1155 });
1156
1157 let mut recv_count = 0;
1158 while let Ok(msg) = receiver.recv().await {
1159 assert_eq!(msg.file.object_id(), id);
1160 assert_eq!(msg.offset, 4096 * recv_count);
1161 recv_count += 1;
1162 }
1163 task.await;
1164 assert_eq!(recv_count, message_count as u64);
1165
1166 fixture.close().await;
1167 }
1168
1169 #[fuchsia::test]
1170 async fn test_recording_more_than_io_size() {
1171 let fixture = new_blob_fixture().await;
1172
1173 {
1174 let mut state = new_profile_state(true);
1175 let message_count = (IO_SIZE as usize / size_of::<BlobMessage>()) + 1;
1176 let hash;
1177 let volume = fixture.volume().volume();
1178 {
1179 hash = fixture.write_blob(&[88u8], CompressionMode::Never).await;
1180 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1181 let handle =
1183 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1184 let mut recorder = state.record_new(volume, Box::new(handle));
1185 recorder.record_open(blob.clone()).unwrap();
1186 for i in 0..message_count {
1187 recorder.record(blob.clone(), 4096 * i as u64).unwrap();
1188 }
1189 }
1190 state.wait_for_recording_to_finish().await;
1191 assert_eq!(get_test_profile_contents(volume).await.len(), IO_SIZE + BLOCK_SIZE);
1192
1193 let mut local_cache: BTreeMap<Hash, Option<OpenedNode<FxBlob>>> = BTreeMap::new();
1194 let (sender, receiver) = async_channel::unbounded::<Request<FxBlob>>();
1195
1196 let volume = volume.clone();
1197 let task = fasync::Task::spawn(async move {
1198 let handle = Box::new(get_test_profile_handle(&volume).await);
1199 let blob = BlobVolume::new(volume);
1200 blob.read_and_queue(handle, &sender, &mut local_cache).await.unwrap();
1201 });
1202
1203 let mut recv_count = 0;
1204 while let Ok(msg) = receiver.recv().await {
1205 assert_eq!(msg.file.root(), hash);
1206 assert_eq!(msg.offset, 4096 * recv_count);
1207 recv_count += 1;
1208 }
1209 task.await;
1210 assert_eq!(recv_count, message_count as u64);
1211 }
1212
1213 fixture.close().await;
1214 }
1215
1216 #[fuchsia::test]
1217 async fn test_replay_profile_blob() {
1218 let mut state = new_profile_state(true);
1220
1221 let mut hashes = Vec::new();
1222
1223 let fixture = new_blob_fixture().await;
1224 {
1225 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1226 let message_count = (fixture.fs().block_size() as usize / size_of::<BlobMessage>()) + 1;
1227
1228 let volume = fixture.volume().volume();
1229 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1230 let mut recorder = state.record_new(volume, Box::new(handle));
1231 for i in 0..message_count {
1233 let hash =
1234 fixture.write_blob(i.to_string().as_bytes(), CompressionMode::Never).await;
1235 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1236 recorder.record_open(blob.clone()).unwrap();
1237 hashes.push(hash);
1238 recorder.record(blob.clone(), 0).unwrap();
1239 }
1240 };
1241 let device = fixture.close().await;
1242 device.ensure_unique();
1243 state.wait_for_recording_to_finish().await;
1244
1245 device.reopen(false);
1246 let fixture = open_blob_fixture(device).await;
1247 {
1248 for hash in &hashes {
1251 let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1252 assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1253 }
1254
1255 let volume = fixture.volume().volume();
1256 state.replay_profile(
1257 Box::new(get_test_profile_handle(volume).await),
1258 volume.clone(),
1259 volume.scope().try_active_guard().unwrap(),
1260 );
1261
1262 for hash in &hashes {
1264 let blob = fixture.get_blob(*hash).await.expect("Opening blob");
1265 while blob.vmo().info().unwrap().committed_bytes == 0 {
1266 fasync::Timer::new(Duration::from_millis(25)).await;
1267 }
1268 }
1269 }
1270 fixture.close().await;
1271 }
1272
1273 #[fuchsia::test]
1274 async fn test_replay_profile_file() {
1275 let mut state = new_profile_state(false);
1277
1278 let mut ids = Vec::new();
1279
1280 let fixture = TestFixture::new().await;
1281 {
1282 assert_eq!(BLOCK_SIZE as u64, fixture.fs().block_size());
1283 let message_count = (fixture.fs().block_size() as usize / size_of::<FileMessage>()) + 1;
1284
1285 let volume = fixture.volume().volume();
1286 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1287 let mut recorder = state.record_new(volume, Box::new(handle));
1288 for i in 0..message_count {
1290 let id = write_file(&fixture, &i.to_string(), &[88u8]).await;
1291 let node = fixture
1292 .volume()
1293 .volume()
1294 .get_or_load_node(id, ObjectDescriptor::File, Some(fixture.volume().root_dir()))
1295 .await
1296 .unwrap();
1297 recorder.record_open(node.clone()).unwrap();
1298 ids.push(id);
1299 recorder.record(node.clone(), 0).unwrap();
1300 }
1301 };
1302 let device = fixture.close().await;
1303 device.ensure_unique();
1304 state.wait_for_recording_to_finish().await;
1305
1306 device.reopen(false);
1307 let fixture = TestFixture::open(
1308 device,
1309 TestFixtureOptions { encrypted: true, format: false, ..Default::default() },
1310 )
1311 .await;
1312 {
1313 for id in &ids {
1315 let file = fixture
1316 .volume()
1317 .volume()
1318 .get_or_load_node(
1319 *id,
1320 ObjectDescriptor::File,
1321 Some(fixture.volume().root_dir()),
1322 )
1323 .await
1324 .unwrap()
1325 .into_any()
1326 .downcast::<FxFile>()
1327 .unwrap();
1328 assert_eq!(file.vmo().info().unwrap().committed_bytes, 0);
1329 }
1330
1331 let volume = fixture.volume().volume();
1332 state.replay_profile(
1333 Box::new(get_test_profile_handle(volume).await),
1334 volume.clone(),
1335 volume.scope().try_active_guard().unwrap(),
1336 );
1337
1338 for id in &ids {
1340 let file = fixture
1341 .volume()
1342 .volume()
1343 .get_or_load_node(
1344 *id,
1345 ObjectDescriptor::File,
1346 Some(fixture.volume().root_dir()),
1347 )
1348 .await
1349 .unwrap()
1350 .into_any()
1351 .downcast::<FxFile>()
1352 .unwrap();
1353 while file.vmo().info().unwrap().committed_bytes == 0 {
1354 fasync::Timer::new(Duration::from_millis(25)).await;
1355 }
1356 }
1357 state.wait_for_recording_to_finish().await;
1358 }
1359 fixture.close().await;
1360 }
1361
1362 #[fuchsia::test]
1363 async fn test_recording_during_replay() {
1364 let mut state = new_profile_state(true);
1365
1366 let hash;
1367 let first_recording;
1368 let fixture = new_blob_fixture().await;
1369 let volume = fixture.volume().volume();
1370
1371 {
1373 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1374 let mut recorder = state.record_new(volume, Box::new(handle));
1375 hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1376 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1377 recorder.record_open(blob.clone()).unwrap();
1378 recorder.record(blob.clone(), 0).unwrap();
1379 }
1380
1381 state.wait_for_recording_to_finish().await;
1382 first_recording = get_test_profile_contents(volume).await;
1383 assert_ne!(first_recording.len(), 0);
1384 let device = fixture.close().await;
1385 device.ensure_unique();
1386
1387 device.reopen(false);
1388 let fixture = open_blob_fixture(device).await;
1389
1390 {
1391 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1394 assert_eq!(blob.vmo().info().unwrap().committed_bytes, 0);
1395
1396 let volume = fixture.volume().volume();
1398 let handle = FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1399 let mut recorder = state.record_new(volume, Box::new(handle));
1400 recorder.record(blob.clone(), 4096).unwrap();
1401
1402 let volume = fixture.volume().volume();
1404 state.replay_profile(
1405 Box::new(get_test_profile_handle(volume).await),
1406 volume.clone(),
1407 volume.scope().try_active_guard().unwrap(),
1408 );
1409
1410 {
1412 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1413 while blob.vmo().info().unwrap().committed_bytes == 0 {
1414 fasync::Timer::new(Duration::from_millis(25)).await;
1415 }
1416 }
1417
1418 recorder.record_open(blob.clone()).unwrap();
1421 }
1422
1423 state.wait_for_recording_to_finish().await;
1424
1425 let volume = fixture.volume().volume();
1426 let second_recording = get_test_profile_contents(volume).await;
1427 assert_ne!(second_recording.len(), 0);
1428 assert_ne!(&second_recording, &first_recording);
1429
1430 fixture.close().await;
1431 }
1432
1433 #[fuchsia::test]
1436 async fn test_replay_profile_stop_reading_early() {
1437 let mut state = new_profile_state(true);
1438 let fixture = new_blob_fixture().await;
1439
1440 {
1441 let volume = fixture.volume().volume();
1442
1443 let message;
1445 {
1446 let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1447 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1448 message = BlobMessage { id: blob.root(), offset: 0 };
1449 }
1450 state.wait_for_recording_to_finish().await;
1451
1452 let replay_handle = Box::new(FakeReaderWriter::new());
1454 let mut buff = vec![0u8; IO_SIZE * 2];
1455 message.encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1456 message.encode_to_impl(
1457 (&mut buff[IO_SIZE..IO_SIZE + size_of::<BlobMessage>()]).try_into().unwrap(),
1458 );
1459
1460 replay_handle.inner.lock().data = buff;
1461 let delay1 = Event::new();
1462 replay_handle.push_delay(delay1.listen());
1463 let delay2 = Event::new();
1464 replay_handle.push_delay(delay2.listen());
1465
1466 state.replay_profile(
1467 replay_handle,
1468 volume.clone(),
1469 volume.scope().try_active_guard().unwrap(),
1470 );
1471
1472 fasync::Task::spawn(async move {
1474 fasync::Timer::new(Duration::from_millis(100)).await;
1476 delay1.notify(usize::MAX);
1477 })
1478 .detach();
1479 }
1480
1481 fixture.close().await;
1484 }
1485
1486 #[fuchsia::test]
1487 async fn test_replay_blob_missing() {
1488 let fixture = new_blob_fixture().await;
1489 let hash = fixture.write_blob(&[0, 1, 2, 3], CompressionMode::Never).await;
1492 let mut buff = vec![0u8; IO_SIZE];
1493 {
1494 {
1497 let message = BlobMessage { id: [42u8; 32].into(), offset: 0 };
1498 message
1499 .encode_to_impl((&mut buff[0..size_of::<BlobMessage>()]).try_into().unwrap());
1500 }
1501
1502 {
1504 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1505 let message = BlobMessage { id: blob.root(), offset: 0 };
1506 message.encode_to_impl(
1507 (&mut buff[size_of::<BlobMessage>()..(size_of::<BlobMessage>() * 2)])
1508 .try_into()
1509 .unwrap(),
1510 );
1511 }
1512 }
1513 let device = fixture.close().await;
1514 device.ensure_unique();
1515
1516 device.reopen(false);
1517 let fixture = open_blob_fixture(device).await;
1518 {
1519 let mut state = new_profile_state(true);
1520 let volume = fixture.volume().volume();
1521
1522 let replay_handle = Box::new(FakeReaderWriter::new());
1523 replay_handle.inner.lock().data = buff;
1524
1525 state.replay_profile(
1526 replay_handle,
1527 volume.clone(),
1528 volume.scope().try_active_guard().unwrap(),
1529 );
1530
1531 let blob = fixture.get_blob((*hash).into()).await.expect("Opening blob");
1533 while blob.vmo().info().unwrap().committed_bytes == 0 {
1534 fasync::Timer::new(Duration::from_millis(25)).await;
1535 }
1536 }
1537 fixture.close().await;
1538 }
1539
1540 #[fuchsia::test]
1541 async fn test_replay_file_missing_or_tombstoned() {
1542 let fixture = TestFixture::new().await;
1543 let mut buff = vec![0u8; IO_SIZE];
1544 let remaining_file_id;
1547 let tombstoned_file_id;
1548 {
1550 let id = write_file(&fixture, "foo", &[1, 2, 3, 4]).await;
1551 let message = FileMessage { id, offset: 0 };
1552 message.encode_to_impl((&mut buff[0..size_of::<FileMessage>()]).try_into().unwrap());
1553 }
1554 fixture
1556 .root()
1557 .unlink("foo", &fio::UnlinkOptions::default())
1558 .await
1559 .unwrap()
1560 .expect("Unlinking");
1561
1562 {
1564 tombstoned_file_id = write_file(&fixture, "bar", &[1, 2, 3, 4]).await;
1565 let message = FileMessage { id: tombstoned_file_id, offset: 0 };
1566 message.encode_to_impl(
1567 (&mut buff[size_of::<FileMessage>()..(size_of::<FileMessage>() * 2)])
1568 .try_into()
1569 .unwrap(),
1570 );
1571 }
1572
1573 {
1575 remaining_file_id = write_file(&fixture, "baz", &[1, 2, 3, 4]).await;
1576 let message = FileMessage { id: remaining_file_id, offset: 0 };
1577 message.encode_to_impl(
1578 (&mut buff[(size_of::<FileMessage>() * 2)..(size_of::<FileMessage>() * 3)])
1579 .try_into()
1580 .unwrap(),
1581 );
1582 }
1583 let device = fixture.close().await;
1584 device.ensure_unique();
1585
1586 device.reopen(false);
1587 let fixture =
1588 TestFixture::open(device, TestFixtureOptions { format: false, ..Default::default() })
1589 .await;
1590 {
1591 let tombstoned_file = fixture
1594 .volume()
1595 .volume()
1596 .get_or_load_node(tombstoned_file_id, ObjectDescriptor::File, None)
1597 .await
1598 .expect("Opening file object")
1599 .into_any()
1600 .downcast::<FxFile>()
1601 .unwrap();
1602 fixture
1603 .root()
1604 .unlink("bar", &fio::UnlinkOptions::default())
1605 .await
1606 .unwrap()
1607 .expect("Unlinking");
1608
1609 let mut state = new_profile_state(false);
1610 let volume = fixture.volume().volume();
1611
1612 let replay_handle = Box::new(FakeReaderWriter::new());
1613 replay_handle.inner.lock().data = buff;
1614
1615 state.replay_profile(
1616 replay_handle,
1617 volume.clone(),
1618 volume.scope().try_active_guard().unwrap(),
1619 );
1620
1621 let remaining_file = fixture
1623 .volume()
1624 .volume()
1625 .get_or_load_node(remaining_file_id, ObjectDescriptor::File, None)
1626 .await
1627 .expect("Opening file object")
1628 .into_any()
1629 .downcast::<FxFile>()
1630 .unwrap();
1631 while remaining_file.vmo().info().unwrap().committed_bytes == 0 {
1632 fasync::Timer::new(Duration::from_millis(25)).await;
1633 }
1634
1635 assert_eq!(tombstoned_file.vmo().info().unwrap().committed_bytes, 0);
1638 }
1639 fixture.close().await;
1640 }
1641
1642 #[fuchsia::test]
1643 async fn test_recording_stopped_by_scope_shutdown() {
1644 let fixture = TestFixture::new().await;
1645 {
1646 let volumes_directory = fixture.volumes_directory();
1647 let mut state = new_profile_state(false);
1648 let (store_id, _recorder) = {
1651 let volume_and_root = volumes_directory
1652 .create_and_mount_volume("other_volume", None, false, None)
1653 .await
1654 .unwrap();
1655 let volume = volume_and_root.into_volume();
1656
1657 let handle =
1658 FileRecordingHandle::new(TEST_PROFILE_NAME, volume.clone()).await.unwrap();
1659 (volume.store().store_object_id(), state.record_new(&volume, Box::new(handle)))
1660 };
1661 volumes_directory.lock().await.unmount(store_id).await.expect("Unmounting");
1662 state.wait_for_recording_to_finish().await;
1663
1664 let volume_and_root =
1665 volumes_directory.mount_volume("other_volume", None, false).await.unwrap();
1666 let volume = volume_and_root.into_volume();
1667 let profile_dir = volume.get_profile_directory().await.unwrap();
1668 assert!(profile_dir.lookup(TEST_PROFILE_NAME).await.unwrap().is_none());
1669 }
1670 fixture.close().await;
1671 }
1672}