Skip to main content

ab_farmer/
farmer_cache.rs

1//! A container that caches pieces
2//!
3//! Farmer cache is a container that orchestrates a bunch of piece and plot caches that together
4//! persist pieces in a way that is easy to retrieve comparing to decoding pieces from plots.
5
6mod metrics;
7mod piece_cache_state;
8// TODO: Not supported under Miri: https://github.com/rust-lang/miri/issues/5137
9#[cfg(not(miri))]
10#[cfg(test)]
11mod tests;
12
13use crate::farm::{MaybePieceStoredResult, PieceCache, PieceCacheId, PieceCacheOffset, PlotCache};
14use crate::farmer_cache::metrics::FarmerCacheMetrics;
15use crate::farmer_cache::piece_cache_state::PieceCachesState;
16use crate::node_client::NodeClient;
17use crate::utils::run_future_in_dedicated_thread;
18use ab_core_primitives::pieces::{Piece, PieceIndex};
19use ab_core_primitives::segments::SegmentIndex;
20use ab_data_retrieval::piece_getter::PieceGetter;
21use ab_networking::KeyWithDistance;
22use ab_networking::libp2p::PeerId;
23use ab_networking::libp2p::kad::RecordKey;
24use ab_networking::utils::multihash::ToMultihash;
25use async_lock::RwLock as AsyncRwLock;
26use bytesize::ByteSize;
27use event_listener_primitives::{Bag, HandlerId};
28use futures::channel::mpsc;
29use futures::future::{Either, FusedFuture};
30use futures::stream::{FuturesOrdered, FuturesUnordered};
31use futures::{FutureExt, SinkExt, Stream, StreamExt, select, stream};
32use parking_lot::{Mutex, RwLock};
33use prometheus_client::registry::Registry;
34use rand::prelude::*;
35use rayon::prelude::*;
36use std::collections::hash_map::Entry;
37use std::collections::{HashMap, HashSet};
38use std::future::join;
39use std::sync::Arc as StdArc;
40use std::sync::atomic::{AtomicUsize, Ordering};
41use std::task::Poll;
42use std::time::Duration;
43use std::{fmt, mem};
44use tokio::sync::Semaphore;
45use tokio::task::yield_now;
46use tracing::{Instrument, debug, error, info, info_span, trace, warn};
47use triomphe::Arc;
48
49const WORKER_CHANNEL_CAPACITY: usize = 100;
50const SYNC_BATCH_SIZE: usize = 256;
51const SYNC_CONCURRENT_BATCHES: usize = 4;
52/// Make caches available as they are building without waiting for the initialization to finish,
53/// this number defines an interval in pieces after which cache is updated
54const INTERMEDIATE_CACHE_UPDATE_INTERVAL: usize = 100;
55const INITIAL_SYNC_FARM_INFO_CHECK_INTERVAL: Duration = Duration::from_secs(1);
56
57type HandlerFn<A> = StdArc<dyn Fn(&A) + Send + Sync + 'static>;
58type Handler<A> = Bag<HandlerFn<A>, A>;
59type CacheIndex = u8;
60
61#[derive(Default, Debug)]
62struct Handlers {
63    progress: Handler<f32>,
64}
65
66#[derive(Debug, Clone, Copy)]
67struct FarmerCacheOffset {
68    cache_index: CacheIndex,
69    piece_offset: PieceCacheOffset,
70}
71
72impl FarmerCacheOffset {
73    fn new(cache_index: CacheIndex, piece_offset: PieceCacheOffset) -> Self {
74        Self {
75            cache_index,
76            piece_offset,
77        }
78    }
79}
80
81#[derive(Debug, Clone)]
82struct CacheBackend {
83    backend: StdArc<dyn PieceCache>,
84    used_capacity: u32,
85    total_capacity: u32,
86}
87
88impl std::ops::Deref for CacheBackend {
89    type Target = StdArc<dyn PieceCache>;
90
91    fn deref(&self) -> &Self::Target {
92        &self.backend
93    }
94}
95
96impl CacheBackend {
97    fn new(backend: StdArc<dyn PieceCache>, total_capacity: u32) -> Self {
98        Self {
99            backend,
100            used_capacity: 0,
101            total_capacity,
102        }
103    }
104
105    fn next_free(&mut self) -> Option<PieceCacheOffset> {
106        let offset = self.used_capacity;
107        if offset < self.total_capacity {
108            self.used_capacity += 1;
109            Some(PieceCacheOffset(offset))
110        } else {
111            debug!(?offset, total_capacity = ?self.total_capacity, "No free space in cache backend");
112            None
113        }
114    }
115
116    fn free_size(&self) -> u32 {
117        self.total_capacity - self.used_capacity
118    }
119}
120
121#[derive(Debug)]
122struct CacheState {
123    cache_stored_pieces: HashMap<KeyWithDistance, FarmerCacheOffset>,
124    cache_free_offsets: Vec<FarmerCacheOffset>,
125    backend: CacheBackend,
126}
127
128#[derive(Debug)]
129enum WorkerCommand {
130    ReplaceBackingCaches {
131        new_piece_caches: Vec<StdArc<dyn PieceCache>>,
132    },
133    ForgetKey {
134        key: RecordKey,
135    },
136}
137
138/// Farmer cache worker used to drive the farmer cache backend
139#[derive(Debug)]
140#[must_use = "Farmer cache will not work unless its worker is running"]
141pub struct FarmerCacheWorker<NC>
142where
143    NC: fmt::Debug,
144{
145    peer_id: PeerId,
146    node_client: NC,
147    piece_caches: Arc<AsyncRwLock<PieceCachesState>>,
148    plot_caches: Arc<PlotCaches>,
149    handlers: Arc<Handlers>,
150    worker_receiver: Option<mpsc::Receiver<WorkerCommand>>,
151    metrics: Option<Arc<FarmerCacheMetrics>>,
152}
153
154impl<NC> FarmerCacheWorker<NC>
155where
156    NC: NodeClient,
157{
158    /// Run the cache worker with provided piece getter.
159    ///
160    /// NOTE: Piece getter must not depend on farmer cache in order to avoid reference cycles!
161    pub async fn run<PG>(mut self, piece_getter: PG)
162    where
163        PG: PieceGetter,
164    {
165        // Limit is dynamically set later
166        let mut last_segment_index_internal = SegmentIndex::ZERO;
167
168        let mut worker_receiver = self
169            .worker_receiver
170            .take()
171            .expect("Always set during worker instantiation");
172
173        if let Some(WorkerCommand::ReplaceBackingCaches { new_piece_caches }) =
174            worker_receiver.next().await
175        {
176            self.initialize(
177                &piece_getter,
178                &mut last_segment_index_internal,
179                new_piece_caches,
180            )
181            .await;
182        } else {
183            // Piece cache is dropped before backing caches were sent
184            return;
185        }
186
187        let mut super_segment_headers_notifications = match self
188            .node_client
189            .subscribe_new_super_segment_headers()
190            .await
191        {
192            Ok(super_segment_headers_notifications) => super_segment_headers_notifications,
193            Err(error) => {
194                error!(%error, "Failed to subscribe to new super segment headers notifications");
195                return;
196            }
197        };
198
199        // Keep up with segment indices that were potentially created since reinitialization
200        self.keep_up_after_initial_sync(&piece_getter, &mut last_segment_index_internal)
201            .await;
202
203        loop {
204            select! {
205                maybe_command = worker_receiver.next() => {
206                    let Some(command) = maybe_command else {
207                        // Nothing else left to do
208                        return;
209                    };
210
211                    self.handle_command(command, &piece_getter, &mut last_segment_index_internal).await;
212                }
213                maybe_super_segment_header = super_segment_headers_notifications.next().fuse() => {
214                    if let Some(super_segment_header) = maybe_super_segment_header {
215                        self
216                            .process_new_segment_index(
217                                &piece_getter,
218                                super_segment_header.max_segment_index.as_inner(),
219                                &mut last_segment_index_internal
220                            )
221                            .await;
222                    } else {
223                        // Keep-up sync only ends with subscription, which lasts for duration of an
224                        // instance
225                        return;
226                    }
227                }
228            }
229        }
230    }
231
232    async fn handle_command<PG>(
233        &self,
234        command: WorkerCommand,
235        piece_getter: &PG,
236        last_segment_index_internal: &mut SegmentIndex,
237    ) where
238        PG: PieceGetter,
239    {
240        match command {
241            WorkerCommand::ReplaceBackingCaches { new_piece_caches } => {
242                self.initialize(piece_getter, last_segment_index_internal, new_piece_caches)
243                    .await;
244            }
245            // TODO: Consider implementing optional re-sync of the piece instead of just forgetting
246            WorkerCommand::ForgetKey { key } => {
247                let mut caches = self.piece_caches.write().await;
248                let key = KeyWithDistance::new_with_record_key(self.peer_id, key);
249                let Some(offset) = caches.remove_stored_piece(&key) else {
250                    // Key not exist
251                    return;
252                };
253
254                let cache_index = offset.cache_index;
255                let piece_offset = offset.piece_offset;
256                let Some(backend) = caches.get_backend(cache_index).cloned() else {
257                    // Cache backend not exist
258                    return;
259                };
260
261                caches.push_dangling_free_offset(offset);
262                match backend.read_piece_index(piece_offset).await {
263                    Ok(Some(piece_index)) => {
264                        trace!(%piece_index, %cache_index, %piece_offset, "Forget piece");
265                    }
266                    Ok(None) => {
267                        warn!(
268                            %cache_index,
269                            %piece_offset,
270                            "Piece index out of range, this is likely an implementation bug, \
271                            not freeing heap element"
272                        );
273                    }
274                    Err(error) => {
275                        error!(
276                            %error,
277                            %cache_index,
278                            ?key,
279                            %piece_offset,
280                            "Error while reading piece from cache"
281                        );
282                    }
283                }
284            }
285        }
286    }
287
288    async fn initialize<PG>(
289        &self,
290        piece_getter: &PG,
291        last_segment_index_internal: &mut SegmentIndex,
292        new_piece_caches: Vec<StdArc<dyn PieceCache>>,
293    ) where
294        PG: PieceGetter,
295    {
296        info!("Initializing piece cache");
297
298        // Pull old cache state since it will be replaced with a new one and reuse its allocations
299        let (mut stored_pieces, mut dangling_free_offsets) =
300            mem::take(&mut *self.piece_caches.write().await).reuse();
301
302        debug!("Collecting pieces that were in the cache before");
303
304        if let Some(metrics) = &self.metrics {
305            metrics.piece_cache_capacity_total.set(0);
306            metrics.piece_cache_capacity_used.set(0);
307        }
308
309        let peer_id = self.peer_id;
310
311        // Build cache state of all backends
312        let piece_caches_number = new_piece_caches.len();
313        let maybe_caches_futures = new_piece_caches
314            .into_iter()
315            .enumerate()
316            .filter_map(|(cache_index, new_cache)| {
317                let total_capacity = new_cache.max_num_elements();
318                let mut backend = CacheBackend::new(new_cache, total_capacity);
319                let Ok(cache_index) = CacheIndex::try_from(cache_index) else {
320                    warn!(
321                        ?piece_caches_number,
322                        "Too many piece caches provided, {cache_index} cache will be ignored",
323                    );
324                    return None;
325                };
326
327                if let Some(metrics) = &self.metrics {
328                    metrics
329                        .piece_cache_capacity_total
330                        .inc_by(i64::from(total_capacity));
331                }
332
333                let init_fut = async move {
334                    let used_capacity = &mut backend.used_capacity;
335
336                    // Hack with first collecting into `Option` with `Option::take()` call
337                    // later is to satisfy compiler that gets confused about ownership
338                    // otherwise
339                    let mut maybe_contents = match backend.backend.contents().await {
340                        Ok(contents) => Some(contents),
341                        Err(error) => {
342                            warn!(%error, "Failed to get cache contents");
343
344                            None
345                        }
346                    };
347
348                    let mut cache_stored_pieces = HashMap::new();
349                    let mut cache_free_offsets = Vec::new();
350
351                    let Some(mut contents) = maybe_contents.take() else {
352                        drop(maybe_contents);
353
354                        return CacheState {
355                            cache_stored_pieces,
356                            cache_free_offsets,
357                            backend,
358                        };
359                    };
360
361                    while let Some(maybe_element_details) = contents.next().await {
362                        let (piece_offset, maybe_piece_index) = match maybe_element_details {
363                            Ok(element_details) => element_details,
364                            Err(error) => {
365                                warn!(%error, "Failed to get cache contents element details");
366                                break;
367                            }
368                        };
369                        let offset = FarmerCacheOffset::new(cache_index, piece_offset);
370                        match maybe_piece_index {
371                            Some(piece_index) => {
372                                *used_capacity = piece_offset.0 + 1;
373                                let record_key = RecordKey::from(piece_index.to_multihash());
374                                let key = KeyWithDistance::new_with_record_key(peer_id, record_key);
375                                cache_stored_pieces.insert(key, offset);
376                            }
377                            None => {
378                                // TODO: Optimize to not store all free offsets, only dangling
379                                //  offsets are actually necessary
380                                cache_free_offsets.push(offset);
381                            }
382                        }
383
384                        // Allow for task to be aborted
385                        yield_now().await;
386                    }
387
388                    drop(maybe_contents);
389                    drop(contents);
390
391                    CacheState {
392                        cache_stored_pieces,
393                        cache_free_offsets,
394                        backend,
395                    }
396                };
397
398                Some(run_future_in_dedicated_thread(
399                    move || init_fut.instrument(info_span!("", %cache_index)),
400                    format!("piece-cache.{cache_index}"),
401                ))
402            })
403            .collect::<Result<Vec<_>, _>>();
404
405        let caches_futures = match maybe_caches_futures {
406            Ok(caches_futures) => caches_futures,
407            Err(error) => {
408                error!(%error, "Failed to spawn piece cache reading thread");
409
410                return;
411            }
412        };
413
414        let mut backends = Vec::with_capacity(caches_futures.len());
415        let mut caches_futures = caches_futures.into_iter().collect::<FuturesOrdered<_>>();
416
417        while let Some(maybe_cache) = caches_futures.next().await {
418            match maybe_cache {
419                Ok(cache) => {
420                    let backend = cache.backend;
421                    for (key, cache_offset) in cache.cache_stored_pieces {
422                        if let Some(old_cache_offset) = stored_pieces.insert(key, cache_offset) {
423                            dangling_free_offsets.push_front(old_cache_offset);
424                        }
425                    }
426                    dangling_free_offsets.extend(
427                        cache.cache_free_offsets.into_iter().filter(|free_offset| {
428                            free_offset.piece_offset.0 < backend.used_capacity
429                        }),
430                    );
431                    backends.push(backend);
432                }
433                Err(_cancelled) => {
434                    error!("Piece cache reading thread panicked");
435
436                    return;
437                }
438            }
439        }
440
441        let mut caches = PieceCachesState::new(stored_pieces, dangling_free_offsets, backends);
442
443        info!("Synchronizing piece cache");
444
445        let last_segment_index = loop {
446            match self.node_client.farmer_app_info().await {
447                Ok(farmer_app_info) => {
448                    let last_segment_index =
449                        farmer_app_info.protocol_info.history_size.segment_index();
450                    // Wait for node to be either fully synced or to be aware of non-zero segment
451                    // index, which would indicate it has started DSN sync and knows about
452                    // up-to-date archived history.
453                    //
454                    // While this doesn't account for situations where node was offline for a long
455                    // time and is aware of old segment headers, this is good enough for piece cache
456                    // sync to proceed and should result in better user experience on average.
457                    if !farmer_app_info.syncing || last_segment_index > SegmentIndex::ZERO {
458                        break last_segment_index;
459                    }
460                }
461                Err(error) => {
462                    error!(
463                        %error,
464                        "Failed to get farmer app info from node, keeping old cache state without \
465                        updates"
466                    );
467
468                    // Not the latest, but at least something
469                    *self.piece_caches.write().await = caches;
470                    return;
471                }
472            }
473
474            tokio::time::sleep(INITIAL_SYNC_FARM_INFO_CHECK_INTERVAL).await;
475        };
476
477        debug!(%last_segment_index, "Identified last segment index");
478
479        // Collect all the piece indices that need to be stored, we will sort them later
480        let segment_indices = Vec::from_iter(SegmentIndex::ZERO..=last_segment_index);
481        // TODO: This may eventually be too much to store in memory, though right now it is a
482        //  non-issue in practice
483        let mut piece_indices_to_store = segment_indices
484            .into_par_iter()
485            .flat_map(|segment_index| {
486                segment_index
487                    .segment_piece_indexes()
488                    .into_par_iter()
489                    .map(|piece_index| {
490                        (
491                            KeyWithDistance::new(self.peer_id, piece_index.to_multihash()),
492                            piece_index,
493                        )
494                    })
495            })
496            .collect::<Vec<_>>();
497
498        // Sort pieces by distance from peer to piece such that they are in ascending order
499        // and have higher chance of download
500        piece_indices_to_store.par_sort_unstable_by(|(a_key, _), (b_key, _)| a_key.cmp(b_key));
501
502        // `HashMap` is faster than `BTreeMap`
503        let mut piece_indices_to_store = piece_indices_to_store
504            .into_iter()
505            .take(caches.total_capacity())
506            .collect::<HashMap<_, _>>();
507
508        let mut piece_caches_capacity_used = vec![0u32; caches.backends().len()];
509        // Filter-out piece indices that are stored, but should not be as well as clean
510        // `inserted_piece_indices` from already stored piece indices, leaving just those that are
511        // still missing in cache
512        caches.free_unneeded_stored_pieces(&mut piece_indices_to_store);
513
514        if let Some(metrics) = &self.metrics {
515            for offset in caches.stored_pieces_offsets() {
516                piece_caches_capacity_used[usize::from(offset.cache_index)] += 1;
517            }
518
519            for cache_used in piece_caches_capacity_used {
520                metrics
521                    .piece_cache_capacity_used
522                    .inc_by(i64::from(cache_used));
523            }
524        }
525
526        // Store whatever correct pieces are immediately available after restart
527        self.piece_caches.write().await.clone_from(&caches);
528        let stored_count = caches.stored_pieces_offsets().len();
529
530        debug!(
531            %stored_count,
532            count = %piece_indices_to_store.len(),
533            "Identified piece indices that should be cached",
534        );
535
536        let pieces_to_download_total = piece_indices_to_store.len() + stored_count;
537        let piece_indices_to_store = piece_indices_to_store
538            .into_values()
539            .collect::<Vec<_>>()
540            // TODO: Allocating chunks here shouldn't be necessary, but otherwise it fails with
541            //  confusing error described in https://github.com/rust-lang/rust/issues/64552 and
542            //  similar upstream issues
543            .chunks(SYNC_BATCH_SIZE)
544            .map(<[PieceIndex]>::to_vec)
545            .collect::<Vec<_>>();
546
547        let downloaded_pieces_count = AtomicUsize::new(stored_count);
548        let caches = Mutex::new(caches);
549        self.handlers.progress.call_simple(&0.0);
550        let batch_count = piece_indices_to_store.len();
551        let piece_indices_to_store = piece_indices_to_store.into_iter().enumerate();
552
553        let downloading_semaphore = &Semaphore::new(SYNC_BATCH_SIZE * SYNC_CONCURRENT_BATCHES);
554        let ignored_cache_indices = &RwLock::new(HashSet::new());
555
556        let downloading_pieces_stream =
557            stream::iter(piece_indices_to_store.map(|(batch, piece_indices)| {
558                let downloaded_pieces_count = &downloaded_pieces_count;
559                let caches = &caches;
560                let num_pieces = piece_indices.len();
561
562                trace!(
563                    %num_pieces,
564                    %batch,
565                    %batch_count,
566                    first_piece_index = ?piece_indices.first().expect("chunks are never empty"),
567                    last_piece_index = ?piece_indices.last().expect("chunks are never empty"),
568                    downloaded_pieces_count = %downloaded_pieces_count.load(Ordering::Relaxed),
569                    %pieces_to_download_total,
570                    available_permits = %downloading_semaphore.available_permits(),
571                    "Started piece cache sync batch",
572                );
573
574                async move {
575                    let mut permit = downloading_semaphore
576                        .acquire_many(SYNC_BATCH_SIZE as u32)
577                        .await
578                        .expect("Semaphore is never closed; qed");
579                    debug!(%batch, %num_pieces, "Downloading pieces");
580
581                    let pieces_stream = match piece_getter.get_pieces(piece_indices).await {
582                        Ok(pieces_stream) => pieces_stream,
583                        Err(error) => {
584                            error!(
585                                %error,
586                                "Failed to get pieces from piece getter"
587                            );
588                            return;
589                        }
590                    };
591                    let mut pieces_stream = pieces_stream.enumerate();
592
593                    while let Some((index, (piece_index, result))) = pieces_stream.next().await {
594                        debug!(%batch, %index, %piece_index, "Downloaded piece");
595                        // Release slot for future batches, by dropping it along with the piece.
596                        let _permit = permit.split(1);
597
598                        let piece = match result {
599                            Ok(Some(piece)) => {
600                                trace!(%batch, %piece_index, "Downloaded piece successfully");
601                                piece
602                            }
603                            Ok(None) => {
604                                debug!(%batch, %piece_index, "Couldn't find piece");
605                                continue;
606                            }
607                            Err(error) => {
608                                debug!(
609                                    %batch,
610                                    %error,
611                                    %piece_index,
612                                    "Failed to get piece for piece cache"
613                                );
614                                continue;
615                            }
616                        };
617
618                        let (offset, maybe_backend) = {
619                            let mut caches = caches.lock();
620
621                            // Find plot in which there is a place for new piece to be stored
622                            let Some(offset) = caches.pop_free_offset() else {
623                                error!(
624                                    %batch,
625                                    %piece_index,
626                                    "Failed to store piece in cache, there was no space"
627                                );
628                                break;
629                            };
630
631                            (offset, caches.get_backend(offset.cache_index).cloned())
632                        };
633
634                        let cache_index = offset.cache_index;
635                        let piece_offset = offset.piece_offset;
636
637                        let skip_write = ignored_cache_indices.read().contains(&cache_index);
638                        if skip_write {
639                            trace!(
640                                %batch,
641                                %cache_index,
642                                %piece_index,
643                                %piece_offset,
644                                "Skipping known problematic cache index"
645                            );
646                        } else {
647                            if let Some(backend) = maybe_backend
648                                && let Err(error) =
649                                    backend.write_piece(piece_offset, piece_index, &piece).await
650                            {
651                                error!(
652                                    %error,
653                                    %batch,
654                                    %cache_index,
655                                    %piece_index,
656                                    %piece_offset,
657                                    "Failed to write piece into cache, ignoring this cache going \
658                                    forward"
659                                );
660                                ignored_cache_indices.write().insert(cache_index);
661                                continue;
662                            }
663
664                            let key =
665                                KeyWithDistance::new(self.peer_id, piece_index.to_multihash());
666                            caches.lock().push_stored_piece(key, offset);
667                        }
668
669                        let prev_downloaded_pieces_count =
670                            downloaded_pieces_count.fetch_add(1, Ordering::Relaxed);
671                        // Do not print anything or send progress notification after last piece
672                        // until piece cache is written fully below
673                        if prev_downloaded_pieces_count != pieces_to_download_total {
674                            let progress = prev_downloaded_pieces_count as f32
675                                / pieces_to_download_total as f32
676                                * 100.0;
677                            if prev_downloaded_pieces_count
678                                .is_multiple_of(INTERMEDIATE_CACHE_UPDATE_INTERVAL)
679                            {
680                                let mut piece_caches = self.piece_caches.write().await;
681                                piece_caches.clone_from(&caches.lock());
682
683                                info!(
684                                    "Piece cache sync {progress:.2}% complete ({} / {})",
685                                    ByteSize::b(
686                                        (prev_downloaded_pieces_count * Piece::SIZE) as u64,
687                                    )
688                                    .display()
689                                    .iec(),
690                                    ByteSize::b((pieces_to_download_total * Piece::SIZE) as u64,)
691                                        .display()
692                                        .iec(),
693                                );
694                            }
695
696                            self.handlers.progress.call_simple(&progress);
697                        }
698                    }
699
700                    trace!(
701                        %num_pieces,
702                        %batch,
703                        %batch_count,
704                        downloaded_pieces_count = %downloaded_pieces_count.load(Ordering::Relaxed),
705                        %pieces_to_download_total,
706                        available_permits = %downloading_semaphore.available_permits(),
707                        "Finished piece cache sync batch",
708                    );
709                }
710            }));
711
712        // Download several batches concurrently to make sure slow tail of one is compensated by
713        // another
714        downloading_pieces_stream
715            // This allows to schedule new batch while previous batches partially completed, but
716            // avoids excessive memory usage like when all futures are created upfront
717            .buffer_unordered(SYNC_CONCURRENT_BATCHES * 10)
718            // Simply drain everything
719            .for_each(|()| async {})
720            .await;
721
722        *self.piece_caches.write().await = caches.into_inner();
723        self.handlers.progress.call_simple(&100.0);
724        *last_segment_index_internal = last_segment_index;
725
726        info!("Finished piece cache synchronization");
727    }
728
729    async fn process_new_segment_index<PG>(
730        &self,
731        piece_getter: &PG,
732        segment_index: SegmentIndex,
733        last_segment_index_internal: &mut SegmentIndex,
734    ) where
735        PG: PieceGetter,
736    {
737        debug!(
738            %last_segment_index_internal,
739            %segment_index,
740            "Starting to process new segment index"
741        );
742
743        let segment_index_range =
744            (*last_segment_index_internal + SegmentIndex::ONE)..=segment_index;
745
746        if segment_index_range.is_empty() {
747            return;
748        }
749
750        debug!(
751            ?segment_index_range,
752            "Downloading potentially useful pieces"
753        );
754
755        // TODO: Download local pieces first, then download other pieces only after some delay, so
756        //  other farmers on the network have enough time to store their pieces too
757
758        // We do not insert pieces into cache/heap yet, so we don't know if all of these pieces will
759        // be included, but there is a good chance they will be
760        let pieces_to_maybe_include = segment_index_range
761            .flat_map(|segment_index| segment_index.segment_piece_indexes())
762            .map(|piece_index| async move {
763                let should_store_in_piece_cache = self
764                    .piece_caches
765                    .read()
766                    .await
767                    .should_include_key(self.peer_id, piece_index);
768
769                let key = RecordKey::from(piece_index.to_multihash());
770                let should_store_in_plot_cache =
771                    self.plot_caches.should_store(piece_index, &key).await;
772
773                if !(should_store_in_piece_cache || should_store_in_plot_cache) {
774                    trace!(%piece_index, "Piece doesn't need to be cached #1");
775
776                    return None;
777                }
778
779                let maybe_piece_result =
780                    self.node_client
781                        .piece(piece_index)
782                        .await
783                        .inspect_err(|error| {
784                            debug!(
785                                %error,
786                                %segment_index,
787                                %piece_index,
788                                "Failed to retrieve piece from node right after archiving"
789                            );
790                        });
791
792                if let Ok(Some(piece)) = maybe_piece_result {
793                    return Some((piece_index, piece));
794                }
795
796                match piece_getter.get_piece(piece_index).await {
797                    Ok(Some(piece)) => Some((piece_index, piece)),
798                    Ok(None) => {
799                        warn!(
800                            %segment_index,
801                            %piece_index,
802                            "Failed to retrieve piece right after archiving"
803                        );
804
805                        None
806                    }
807                    Err(error) => {
808                        warn!(
809                            %error,
810                            %segment_index,
811                            %piece_index,
812                            "Failed to retrieve piece right after archiving"
813                        );
814
815                        None
816                    }
817                }
818            })
819            .collect::<FuturesUnordered<_>>()
820            .filter_map(|maybe_piece| async move { maybe_piece })
821            .collect::<Vec<_>>()
822            .await;
823
824        debug!(%segment_index, "Downloaded potentially useful pieces");
825
826        // Go through potentially matching pieces again now that segment was acknowledged and try to
827        // persist them if necessary
828        for (piece_index, piece) in pieces_to_maybe_include {
829            if !self
830                .plot_caches
831                .store_additional_piece(piece_index, &piece)
832                .await
833            {
834                trace!(%piece_index, "Piece could not be cached in plot cache");
835            }
836
837            if !self
838                .piece_caches
839                .read()
840                .await
841                .should_include_key(self.peer_id, piece_index)
842            {
843                trace!(%piece_index, "Piece doesn't need to be cached #2");
844
845                continue;
846            }
847
848            trace!(%piece_index, "Piece needs to be cached #1");
849
850            self.persist_piece_in_cache(piece_index, piece).await;
851        }
852
853        *last_segment_index_internal = segment_index;
854
855        debug!(%segment_index, "Finished processing new segment indices");
856    }
857
858    async fn keep_up_after_initial_sync<PG>(
859        &self,
860        piece_getter: &PG,
861        last_segment_index_internal: &mut SegmentIndex,
862    ) where
863        PG: PieceGetter,
864    {
865        let last_segment_index = match self.node_client.farmer_app_info().await {
866            Ok(farmer_app_info) => farmer_app_info.protocol_info.history_size.segment_index(),
867            Err(error) => {
868                error!(
869                    %error,
870                    "Failed to get farmer app info from node, keeping old cache state without \
871                    updates"
872                );
873                return;
874            }
875        };
876
877        if last_segment_index <= *last_segment_index_internal {
878            return;
879        }
880
881        info!(
882            "Syncing piece cache to the latest history size, this may pause block production if \
883            takes too long"
884        );
885
886        // Keep up with segment indices that were potentially created since reinitialization
887        let piece_indices = (*last_segment_index_internal..=last_segment_index)
888            .flat_map(|segment_index| segment_index.segment_piece_indexes());
889
890        // TODO: Download pieces concurrently
891        for piece_index in piece_indices {
892            if !self
893                .piece_caches
894                .read()
895                .await
896                .should_include_key(self.peer_id, piece_index)
897            {
898                trace!(%piece_index, "Piece doesn't need to be cached #3");
899
900                continue;
901            }
902
903            trace!(%piece_index, "Piece needs to be cached #2");
904
905            let result = piece_getter.get_piece(piece_index).await;
906
907            let piece = match result {
908                Ok(Some(piece)) => piece,
909                Ok(None) => {
910                    debug!(%piece_index, "Couldn't find piece");
911                    continue;
912                }
913                Err(error) => {
914                    debug!(
915                        %error,
916                        %piece_index,
917                        "Failed to get piece for piece cache"
918                    );
919                    continue;
920                }
921            };
922
923            self.persist_piece_in_cache(piece_index, piece).await;
924        }
925
926        info!("Finished syncing piece cache to the latest history size");
927
928        *last_segment_index_internal = last_segment_index;
929    }
930
931    /// This assumes it was already checked that piece needs to be stored, no verification for this
932    /// is done internally and invariants will break if this assumption doesn't hold true
933    async fn persist_piece_in_cache(&self, piece_index: PieceIndex, piece: Piece) {
934        let key = KeyWithDistance::new(self.peer_id, piece_index.to_multihash());
935        let mut caches = self.piece_caches.write().await;
936        if let Some((old_key, offset)) = caches.should_replace(&key) {
937            // Entry is already occupied, we need to find and replace old piece with new one
938            let cache_index = offset.cache_index;
939            let piece_offset = offset.piece_offset;
940            let Some(backend) = caches.get_backend(cache_index) else {
941                // Cache backend not exist
942                warn!(
943                    %cache_index,
944                    %piece_index,
945                    "Should have a cached backend, but it didn't exist, this is an \
946                    implementation bug"
947                );
948                return;
949            };
950            if let Err(error) = backend.write_piece(piece_offset, piece_index, &piece).await {
951                error!(
952                    %error,
953                    %cache_index,
954                    %piece_index,
955                    %piece_offset,
956                    "Failed to write piece into cache"
957                );
958            } else {
959                let old_piece_index = decode_piece_index_from_record_key(old_key.record_key());
960                trace!(
961                    %cache_index,
962                    %old_piece_index,
963                    %piece_index,
964                    %piece_offset,
965                    "Successfully replaced old cached piece"
966                );
967                caches.push_stored_piece(key, offset);
968            }
969        } else {
970            // There is free space in cache, need to find a free spot and place piece there
971
972            let Some(offset) = caches.pop_free_offset() else {
973                warn!(
974                    %piece_index,
975                    "Should have inserted piece into cache, but it didn't happen, this is an \
976                    implementation bug"
977                );
978                return;
979            };
980            let cache_index = offset.cache_index;
981            let piece_offset = offset.piece_offset;
982            let Some(backend) = caches.get_backend(cache_index) else {
983                // Cache backend not exist
984                warn!(
985                    %cache_index,
986                    %piece_index,
987                    "Should have a cached backend, but it didn't exist, this is an \
988                    implementation bug"
989                );
990                return;
991            };
992
993            if let Err(error) = backend.write_piece(piece_offset, piece_index, &piece).await {
994                error!(
995                    %error,
996                    %cache_index,
997                    %piece_index,
998                    %piece_offset,
999                    "Failed to write piece into cache"
1000                );
1001            } else {
1002                trace!(
1003                    %cache_index,
1004                    %piece_index,
1005                    %piece_offset,
1006                    "Successfully stored piece in cache"
1007                );
1008                if let Some(metrics) = &self.metrics {
1009                    metrics.piece_cache_capacity_used.inc();
1010                }
1011                caches.push_stored_piece(key, offset);
1012            }
1013        }
1014    }
1015}
1016
1017#[derive(Debug)]
1018struct PlotCaches {
1019    /// Additional piece caches
1020    caches: AsyncRwLock<Vec<StdArc<dyn PlotCache>>>,
1021    /// Next plot cache to use for storing pieces
1022    next_plot_cache: AtomicUsize,
1023}
1024
1025impl PlotCaches {
1026    /// Returns true if there might be space to add a piece to a cache.
1027    /// Returns false if it is already in a cache, or it can't be added to any of the caches.
1028    ///
1029    /// Available space can be overwritten by a sector at any time, so the piece write can still
1030    /// fail even if this returns `true`.
1031    async fn should_store(&self, piece_index: PieceIndex, key: &RecordKey) -> bool {
1032        for (cache_index, cache) in self.caches.read().await.iter().enumerate() {
1033            match cache.is_piece_maybe_stored(key).await {
1034                Ok(MaybePieceStoredResult::No) => {
1035                    // Isn't stored or can't be stored, try another cache if there is one
1036                }
1037                Ok(MaybePieceStoredResult::Vacant) => {
1038                    return true;
1039                }
1040                Ok(MaybePieceStoredResult::Yes) => {
1041                    // Already stored, nothing else left to do
1042                    return false;
1043                }
1044                Err(error) => {
1045                    warn!(
1046                        %cache_index,
1047                        %piece_index,
1048                        %error,
1049                        "Failed to check piece stored in cache"
1050                    );
1051                }
1052            }
1053        }
1054
1055        false
1056    }
1057
1058    /// Store a piece in additional downloaded pieces, if there is space for it.
1059    /// Returns `true` if the piece was added to a cache, and `false` if it couldn't be stored,
1060    /// typically because the cache is full.
1061    async fn store_additional_piece(&self, piece_index: PieceIndex, piece: &Piece) -> bool {
1062        let plot_caches = self.caches.read().await;
1063        let plot_caches_len = plot_caches.len();
1064
1065        // Store pieces in plots using round-robin distribution
1066        for _ in 0..plot_caches_len {
1067            let plot_cache_index =
1068                self.next_plot_cache.fetch_add(1, Ordering::Relaxed) % plot_caches_len;
1069
1070            match plot_caches[plot_cache_index]
1071                .try_store_piece(piece_index, piece)
1072                .await
1073            {
1074                Ok(true) => {
1075                    return true;
1076                }
1077                Ok(false) => {
1078                    // Nothing
1079                }
1080                Err(error) => {
1081                    error!(
1082                        %error,
1083                        %piece_index,
1084                        %plot_cache_index,
1085                        "Failed to store additional piece in cache"
1086                    );
1087                }
1088            }
1089        }
1090
1091        false
1092    }
1093}
1094
1095/// Farmer cache that aggregates different kinds of caches of multiple disks.
1096///
1097/// Pieces in [`PieceCache`] are stored based on capacity and proximity of piece index to farmer's
1098/// network identity. If capacity is not enough to store all pieces in cache then pieces that are
1099/// further from network identity will be evicted, this is helpful for quick retrieval of pieces
1100/// from DSN as well as plotting purposes.
1101///
1102/// [`PlotCache`] is used as a supplementary cache and is primarily helpful for smaller farmers
1103/// where piece cache is not enough to store all the pieces on the network, while there is a lot of
1104/// space in the plot that is not used by sectors yet and can be leverage as extra caching space.
1105#[derive(Debug, Clone)]
1106pub struct FarmerCache {
1107    peer_id: PeerId,
1108    /// Individual dedicated piece caches
1109    piece_caches: Arc<AsyncRwLock<PieceCachesState>>,
1110    /// Additional piece caches
1111    plot_caches: Arc<PlotCaches>,
1112    handlers: Arc<Handlers>,
1113    // We do not want to increase capacity unnecessarily on clone
1114    worker_sender: mpsc::Sender<WorkerCommand>,
1115    metrics: Option<Arc<FarmerCacheMetrics>>,
1116}
1117
1118impl FarmerCache {
1119    /// Create new piece cache instance and corresponding worker.
1120    ///
1121    /// NOTE: Returned future is async, but does blocking operations and should be running in
1122    /// dedicated thread.
1123    pub fn new<NC>(
1124        node_client: NC,
1125        peer_id: PeerId,
1126        registry: Option<&mut Registry>,
1127    ) -> (Self, FarmerCacheWorker<NC>)
1128    where
1129        NC: NodeClient,
1130    {
1131        let caches = Arc::default();
1132        let (worker_sender, worker_receiver) = mpsc::channel(WORKER_CHANNEL_CAPACITY);
1133        let handlers = Arc::new(Handlers::default());
1134
1135        let plot_caches = Arc::new(PlotCaches {
1136            caches: AsyncRwLock::default(),
1137            next_plot_cache: AtomicUsize::new(0),
1138        });
1139        let metrics = registry.map(|registry| Arc::new(FarmerCacheMetrics::new(registry)));
1140
1141        let instance = Self {
1142            peer_id,
1143            piece_caches: Arc::clone(&caches),
1144            plot_caches: Arc::clone(&plot_caches),
1145            handlers: Arc::clone(&handlers),
1146            worker_sender,
1147            metrics: metrics.clone(),
1148        };
1149        let worker = FarmerCacheWorker {
1150            peer_id,
1151            node_client,
1152            piece_caches: caches,
1153            plot_caches,
1154            handlers,
1155            worker_receiver: Some(worker_receiver),
1156            metrics,
1157        };
1158
1159        (instance, worker)
1160    }
1161
1162    /// Get piece from cache
1163    pub async fn get_piece<Key>(&self, key: Key) -> Option<Piece>
1164    where
1165        RecordKey: From<Key>,
1166    {
1167        let key = RecordKey::from(key);
1168        let maybe_piece_found = {
1169            let key = KeyWithDistance::new_with_record_key(self.peer_id, key.clone());
1170            let caches = self.piece_caches.read().await;
1171
1172            caches.get_stored_piece(&key).and_then(|offset| {
1173                let cache_index = offset.cache_index;
1174                let piece_offset = offset.piece_offset;
1175                Some((
1176                    piece_offset,
1177                    cache_index,
1178                    caches.get_backend(cache_index)?.clone(),
1179                ))
1180            })
1181        };
1182
1183        if let Some((piece_offset, cache_index, backend)) = maybe_piece_found {
1184            match backend.read_piece(piece_offset).await {
1185                Ok(maybe_piece) => {
1186                    return if let Some((_piece_index, piece)) = maybe_piece {
1187                        if let Some(metrics) = &self.metrics {
1188                            metrics.cache_get_hit.inc();
1189                        }
1190                        Some(piece)
1191                    } else {
1192                        error!(
1193                            %cache_index,
1194                            %piece_offset,
1195                            ?key,
1196                            "Piece was expected to be in cache, but wasn't found there"
1197                        );
1198                        if let Some(metrics) = &self.metrics {
1199                            metrics.cache_get_error.inc();
1200                        }
1201                        None
1202                    };
1203                }
1204                Err(error) => {
1205                    error!(
1206                        %error,
1207                        %cache_index,
1208                        ?key,
1209                        %piece_offset,
1210                        "Error while reading piece from cache"
1211                    );
1212
1213                    if let Err(error) = self
1214                        .worker_sender
1215                        .clone()
1216                        .send(WorkerCommand::ForgetKey { key })
1217                        .await
1218                    {
1219                        trace!(%error, "Failed to send ForgetKey command to worker");
1220                    }
1221
1222                    if let Some(metrics) = &self.metrics {
1223                        metrics.cache_get_error.inc();
1224                    }
1225                    return None;
1226                }
1227            }
1228        }
1229
1230        for cache in self.plot_caches.caches.read().await.iter() {
1231            if let Ok(Some(piece)) = cache.read_piece(&key).await {
1232                if let Some(metrics) = &self.metrics {
1233                    metrics.cache_get_hit.inc();
1234                }
1235                return Some(piece);
1236            }
1237        }
1238
1239        if let Some(metrics) = &self.metrics {
1240            metrics.cache_get_miss.inc();
1241        }
1242        None
1243    }
1244
1245    /// Get pieces from cache.
1246    ///
1247    /// Number of elements in returned stream is the same as number of unique `piece_indices`.
1248    pub async fn get_pieces<'a, PieceIndices>(
1249        &'a self,
1250        piece_indices: PieceIndices,
1251    ) -> impl Stream<Item = (PieceIndex, Option<Piece>)> + Send + Unpin + 'a
1252    where
1253        PieceIndices: IntoIterator<Item = PieceIndex, IntoIter: Send + 'a> + Send + 'a,
1254    {
1255        let mut pieces_to_get_from_plot_cache = Vec::new();
1256
1257        let pieces_to_read_from_piece_cache = {
1258            let caches = self.piece_caches.read().await;
1259            // Pieces to read from piece cache grouped by backend for efficiency reasons
1260            let mut pieces_to_read_from_piece_cache =
1261                HashMap::<CacheIndex, (CacheBackend, HashMap<_, _>)>::new();
1262
1263            for piece_index in piece_indices {
1264                let key = RecordKey::from(piece_index.to_multihash());
1265
1266                let Some(offset) = caches.get_stored_piece(&KeyWithDistance::new_with_record_key(
1267                    self.peer_id,
1268                    key.clone(),
1269                )) else {
1270                    pieces_to_get_from_plot_cache.push((piece_index, key));
1271                    continue;
1272                };
1273
1274                let cache_index = offset.cache_index;
1275                let piece_offset = offset.piece_offset;
1276
1277                match pieces_to_read_from_piece_cache.entry(cache_index) {
1278                    Entry::Occupied(mut entry) => {
1279                        let (_backend, pieces) = entry.get_mut();
1280                        pieces.insert(piece_offset, (piece_index, key));
1281                    }
1282                    Entry::Vacant(entry) => {
1283                        let backend = if let Some(backend) = caches.get_backend(cache_index) {
1284                            backend.clone()
1285                        } else {
1286                            pieces_to_get_from_plot_cache.push((piece_index, key));
1287                            continue;
1288                        };
1289                        entry
1290                            .insert((backend, HashMap::from([(piece_offset, (piece_index, key))])));
1291                    }
1292                }
1293            }
1294
1295            pieces_to_read_from_piece_cache
1296        };
1297
1298        let (tx, mut rx) = mpsc::unbounded();
1299
1300        let fut = async move {
1301            let tx = &tx;
1302
1303            let mut reading_from_piece_cache = pieces_to_read_from_piece_cache
1304                .into_iter()
1305                .map(|(cache_index, (backend, mut pieces_to_get))| async move {
1306                    let mut pieces_stream = match backend
1307                        .read_pieces(Box::new(
1308                            pieces_to_get
1309                                .keys()
1310                                .copied()
1311                                .collect::<Vec<_>>()
1312                                .into_iter(),
1313                        ))
1314                        .await
1315                    {
1316                        Ok(pieces_stream) => pieces_stream,
1317                        Err(error) => {
1318                            error!(
1319                                %error,
1320                                %cache_index,
1321                                "Error while reading pieces from cache"
1322                            );
1323
1324                            if let Some(metrics) = &self.metrics {
1325                                metrics.cache_get_error.inc_by(pieces_to_get.len() as u64);
1326                            }
1327                            for (piece_index, _key) in pieces_to_get.into_values() {
1328                                tx.unbounded_send((piece_index, None)).expect(
1329                                    "This future isn't polled after receiver is dropped; qed",
1330                                );
1331                            }
1332                            return;
1333                        }
1334                    };
1335
1336                    while let Some(maybe_piece) = pieces_stream.next().await {
1337                        let result = match maybe_piece {
1338                            Ok((piece_offset, Some((piece_index, piece)))) => {
1339                                pieces_to_get.remove(&piece_offset);
1340
1341                                if let Some(metrics) = &self.metrics {
1342                                    metrics.cache_get_hit.inc();
1343                                }
1344                                (piece_index, Some(piece))
1345                            }
1346                            Ok((piece_offset, None)) => {
1347                                let Some((piece_index, key)) = pieces_to_get.remove(&piece_offset)
1348                                else {
1349                                    debug!(
1350                                        %cache_index,
1351                                        %piece_offset,
1352                                        "Received piece offset that was not expected"
1353                                    );
1354                                    continue;
1355                                };
1356
1357                                error!(
1358                                    %cache_index,
1359                                    %piece_index,
1360                                    %piece_offset,
1361                                    ?key,
1362                                    "Piece was expected to be in cache, but wasn't found there"
1363                                );
1364                                if let Some(metrics) = &self.metrics {
1365                                    metrics.cache_get_error.inc();
1366                                }
1367                                (piece_index, None)
1368                            }
1369                            Err(error) => {
1370                                error!(
1371                                    %error,
1372                                    %cache_index,
1373                                    "Error while reading piece from cache"
1374                                );
1375
1376                                if let Some(metrics) = &self.metrics {
1377                                    metrics.cache_get_error.inc();
1378                                }
1379                                continue;
1380                            }
1381                        };
1382
1383                        tx.unbounded_send(result)
1384                            .expect("This future isn't polled after receiver is dropped; qed");
1385                    }
1386
1387                    if pieces_to_get.is_empty() {
1388                        return;
1389                    }
1390
1391                    if let Some(metrics) = &self.metrics {
1392                        metrics.cache_get_error.inc_by(pieces_to_get.len() as u64);
1393                    }
1394                    for (piece_offset, (piece_index, key)) in pieces_to_get {
1395                        error!(
1396                            %cache_index,
1397                            %piece_index,
1398                            %piece_offset,
1399                            ?key,
1400                            "Piece cache didn't return an entry for offset"
1401                        );
1402
1403                        // Uphold invariant of the method that some result should be returned
1404                        // for every unique piece index
1405                        tx.unbounded_send((piece_index, None))
1406                            .expect("This future isn't polled after receiver is dropped; qed");
1407                    }
1408                })
1409                .collect::<FuturesUnordered<_>>();
1410            // TODO: Can't use this due to https://github.com/rust-lang/rust/issues/64650
1411            // Simply drain everything
1412            // .for_each(|()| async {})
1413
1414            // TODO: Remove once https://github.com/rust-lang/rust/issues/64650 is resolved
1415            let reading_from_piece_cache_fut = async move {
1416                while let Some(()) = reading_from_piece_cache.next().await {
1417                    // Simply drain everything
1418                }
1419            };
1420
1421            let reading_from_plot_cache_fut = async {
1422                if pieces_to_get_from_plot_cache.is_empty() {
1423                    return;
1424                }
1425
1426                for cache in self.plot_caches.caches.read().await.iter() {
1427                    // Iterating over offsets in reverse order to both traverse elements in async
1428                    // code and being able to efficiently remove entries without extra allocations
1429                    for offset in (0..pieces_to_get_from_plot_cache.len()).rev() {
1430                        let (piece_index, key) = &pieces_to_get_from_plot_cache[offset];
1431
1432                        if let Ok(Some(piece)) = cache.read_piece(key).await {
1433                            if let Some(metrics) = &self.metrics {
1434                                metrics.cache_get_hit.inc();
1435                            }
1436                            tx.unbounded_send((*piece_index, Some(piece)))
1437                                .expect("This future isn't polled after receiver is dropped; qed");
1438
1439                            // Due to iteration in reverse order and swapping using elements at the
1440                            // end, this doesn't affect processing of the elements
1441                            pieces_to_get_from_plot_cache.swap_remove(offset);
1442                        }
1443                    }
1444
1445                    if pieces_to_get_from_plot_cache.is_empty() {
1446                        return;
1447                    }
1448                }
1449
1450                if let Some(metrics) = &self.metrics {
1451                    metrics
1452                        .cache_get_miss
1453                        .inc_by(pieces_to_get_from_plot_cache.len() as u64);
1454                }
1455
1456                for (piece_index, _key) in pieces_to_get_from_plot_cache {
1457                    tx.unbounded_send((piece_index, None))
1458                        .expect("This future isn't polled after receiver is dropped; qed");
1459                }
1460            };
1461
1462            join!(reading_from_piece_cache_fut, reading_from_plot_cache_fut).await
1463        };
1464        let mut fut = Box::pin(fut.fuse());
1465
1466        // Drive above future and stream back any pieces that were downloaded so far
1467        stream::poll_fn(move |cx| {
1468            if !fut.is_terminated() {
1469                // Result doesn't matter, we'll need to poll stream below anyway
1470                let _: Poll<_> = fut.poll_unpin(cx);
1471            }
1472
1473            if let Poll::Ready(maybe_result) = rx.poll_next_unpin(cx) {
1474                return Poll::Ready(maybe_result);
1475            }
1476
1477            // Exit will be done by the stream above
1478            Poll::Pending
1479        })
1480    }
1481
1482    /// Returns a filtered list of pieces that were found in farmer cache, order is not guaranteed
1483    pub async fn has_pieces(&self, mut piece_indices: Vec<PieceIndex>) -> Vec<PieceIndex> {
1484        let mut pieces_to_find = HashMap::<PieceIndex, RecordKey>::from_iter(
1485            piece_indices
1486                .iter()
1487                .map(|piece_index| (*piece_index, RecordKey::from(piece_index.to_multihash()))),
1488        );
1489
1490        // Quick check in piece caches
1491        {
1492            let piece_caches = self.piece_caches.read().await;
1493            pieces_to_find.retain(|_piece_index, key| {
1494                let distance_key = KeyWithDistance::new(self.peer_id, key.clone());
1495                !piece_caches.contains_stored_piece(&distance_key)
1496            });
1497        }
1498
1499        // Early exit if everything is cached
1500        if pieces_to_find.is_empty() {
1501            return piece_indices;
1502        }
1503
1504        // Check plot caches concurrently
1505        if let Some(plot_caches) = self.plot_caches.caches.try_read() {
1506            let plot_caches = &plot_caches;
1507            let not_found = pieces_to_find
1508                .into_iter()
1509                .map(|(piece_index, key)| async move {
1510                    let key = &key;
1511
1512                    let found = plot_caches
1513                        .iter()
1514                        .map(|plot_cache| async {
1515                            matches!(
1516                                plot_cache.is_piece_maybe_stored(key).await,
1517                                Ok(MaybePieceStoredResult::Yes)
1518                            )
1519                        })
1520                        .collect::<FuturesUnordered<_>>()
1521                        .any(|found| async move { found })
1522                        .await;
1523
1524                    if found { None } else { Some(piece_index) }
1525                })
1526                .collect::<FuturesUnordered<_>>()
1527                .filter_map(|maybe_piece_index| async move { maybe_piece_index })
1528                .collect::<HashSet<_>>()
1529                .await;
1530            piece_indices.retain(|piece_index| !not_found.contains(piece_index));
1531        }
1532        piece_indices
1533    }
1534
1535    /// Find piece in cache and return its retrieval details
1536    pub async fn find_piece(
1537        &self,
1538        piece_index: PieceIndex,
1539    ) -> Option<(PieceCacheId, PieceCacheOffset)> {
1540        let caches = self.piece_caches.read().await;
1541
1542        self.find_piece_internal(&caches, piece_index)
1543    }
1544
1545    /// Find pieces in cache and return their retrieval details
1546    pub async fn find_pieces<PieceIndices>(
1547        &self,
1548        piece_indices: PieceIndices,
1549    ) -> Vec<(PieceIndex, PieceCacheId, PieceCacheOffset)>
1550    where
1551        PieceIndices: IntoIterator<Item = PieceIndex>,
1552    {
1553        let caches = self.piece_caches.read().await;
1554
1555        piece_indices
1556            .into_iter()
1557            .filter_map(|piece_index| {
1558                self.find_piece_internal(&caches, piece_index)
1559                    .map(|(cache_id, piece_offset)| (piece_index, cache_id, piece_offset))
1560            })
1561            .collect()
1562    }
1563
1564    fn find_piece_internal(
1565        &self,
1566        caches: &PieceCachesState,
1567        piece_index: PieceIndex,
1568    ) -> Option<(PieceCacheId, PieceCacheOffset)> {
1569        let key = KeyWithDistance::new(self.peer_id, piece_index.to_multihash());
1570
1571        let Some(offset) = caches.get_stored_piece(&key) else {
1572            if let Some(metrics) = &self.metrics {
1573                metrics.cache_find_miss.inc();
1574            }
1575
1576            return None;
1577        };
1578        let piece_offset = offset.piece_offset;
1579
1580        if let Some(backend) = caches.get_backend(offset.cache_index) {
1581            if let Some(metrics) = &self.metrics {
1582                metrics.cache_find_hit.inc();
1583            }
1584            return Some((*backend.id(), piece_offset));
1585        }
1586
1587        if let Some(metrics) = &self.metrics {
1588            metrics.cache_find_miss.inc();
1589        }
1590        None
1591    }
1592
1593    /// Try to store a piece in additional downloaded pieces, if there is space for it.
1594    /// Returns `true` if the piece was added to this cache, and `false` if it was already stored,
1595    /// or there was no space.
1596    pub async fn maybe_store_additional_piece(
1597        &self,
1598        piece_index: PieceIndex,
1599        piece: &Piece,
1600    ) -> bool {
1601        let key = RecordKey::from(piece_index.to_multihash());
1602
1603        let should_store = self.plot_caches.should_store(piece_index, &key).await;
1604
1605        if !should_store {
1606            return false;
1607        }
1608
1609        self.plot_caches
1610            .store_additional_piece(piece_index, piece)
1611            .await
1612    }
1613
1614    /// Initialize replacement of backing caches
1615    pub async fn replace_backing_caches(
1616        &self,
1617        new_piece_caches: Vec<StdArc<dyn PieceCache>>,
1618        new_plot_caches: Vec<StdArc<dyn PlotCache>>,
1619    ) {
1620        if let Err(error) = self
1621            .worker_sender
1622            .clone()
1623            .send(WorkerCommand::ReplaceBackingCaches { new_piece_caches })
1624            .await
1625        {
1626            warn!(%error, "Failed to replace backing caches, worker exited");
1627        }
1628
1629        *self.plot_caches.caches.write().await = new_plot_caches;
1630    }
1631
1632    /// Subscribe to cache sync notifications
1633    pub fn on_sync_progress(&self, callback: HandlerFn<f32>) -> HandlerId {
1634        self.handlers.progress.add(callback)
1635    }
1636}
1637
1638/// Collection of [`FarmerCache`] instances for load balancing
1639#[derive(Debug, Clone)]
1640pub struct FarmerCaches {
1641    caches: Arc<[FarmerCache]>,
1642}
1643
1644impl From<Arc<[FarmerCache]>> for FarmerCaches {
1645    fn from(caches: Arc<[FarmerCache]>) -> Self {
1646        Self { caches }
1647    }
1648}
1649
1650impl From<FarmerCache> for FarmerCaches {
1651    fn from(cache: FarmerCache) -> Self {
1652        Self {
1653            caches: Arc::from_iter([cache]),
1654        }
1655    }
1656}
1657
1658impl FarmerCaches {
1659    /// Get piece from cache
1660    pub async fn get_piece<Key>(&self, key: Key) -> Option<Piece>
1661    where
1662        RecordKey: From<Key>,
1663    {
1664        let farmer_cache = self.caches.choose(&mut rand::rng())?;
1665        farmer_cache.get_piece(key).await
1666    }
1667
1668    /// Get pieces from cache.
1669    ///
1670    /// Number of elements in returned stream is the same as number of unique `piece_indices`.
1671    pub async fn get_pieces<'a, PieceIndices>(
1672        &'a self,
1673        piece_indices: PieceIndices,
1674    ) -> impl Stream<Item = (PieceIndex, Option<Piece>)> + Send + Unpin + 'a
1675    where
1676        PieceIndices: IntoIterator<Item = PieceIndex, IntoIter: Send + 'a> + Send + 'a,
1677    {
1678        let Some(farmer_cache) = self.caches.choose(&mut rand::rng()) else {
1679            return Either::Left(stream::iter(
1680                piece_indices
1681                    .into_iter()
1682                    .map(|piece_index| (piece_index, None)),
1683            ));
1684        };
1685
1686        Either::Right(farmer_cache.get_pieces(piece_indices).await)
1687    }
1688
1689    /// Returns a filtered list of pieces that were found in farmer cache, order is not guaranteed
1690    pub async fn has_pieces(&self, piece_indices: Vec<PieceIndex>) -> Vec<PieceIndex> {
1691        let Some(farmer_cache) = self.caches.choose(&mut rand::rng()) else {
1692            return Vec::new();
1693        };
1694
1695        farmer_cache.has_pieces(piece_indices).await
1696    }
1697
1698    /// Try to store a piece in additional downloaded pieces, if there is space for it.
1699    /// Returns `true` if the piece was added to one or more caches, and `false` if it was already
1700    /// stored, or there was no space.
1701    pub async fn maybe_store_additional_piece(
1702        &self,
1703        piece_index: PieceIndex,
1704        piece: &Piece,
1705    ) -> bool {
1706        // Run all the futures to completion, and take a non-short-circuiting any() on the results.
1707        self.caches
1708            .iter()
1709            .map(|farmer_cache| farmer_cache.maybe_store_additional_piece(piece_index, piece))
1710            .collect::<FuturesUnordered<_>>()
1711            .fold::<bool, _, _>(false, |acc, stored| async move { acc || stored })
1712            .await
1713    }
1714}
1715
1716/// Extracts the `PieceIndex` from a `RecordKey`.
1717fn decode_piece_index_from_record_key(key: &RecordKey) -> PieceIndex {
1718    let len = key.as_ref().len();
1719    let s = len - PieceIndex::SIZE;
1720
1721    let mut piece_index_bytes = [0u8; PieceIndex::SIZE];
1722    piece_index_bytes.copy_from_slice(&key.as_ref()[s..]);
1723
1724    PieceIndex::from_bytes(piece_index_bytes)
1725}