Skip to main content

subspace_farmer/cluster/
plotter.rs

1//! Farming cluster plotter
2//!
3//! Plotter is responsible for plotting sectors in response to farmer requests.
4//!
5//! This module exposes some data structures for NATS communication, custom plotter
6//! implementation designed to work with cluster plotter and a service function to drive the backend
7//! part of the plotter.
8
9use crate::cluster::nats_client::{GenericRequest, GenericStreamRequest, NatsClient};
10use crate::plotter::{Plotter, SectorPlottingProgress};
11use anyhow::anyhow;
12use async_nats::RequestErrorKind;
13use async_trait::async_trait;
14use backoff::ExponentialBackoff;
15use backoff::backoff::Backoff;
16use bytes::Bytes;
17use derive_more::Display;
18use event_listener_primitives::{Bag, HandlerId};
19use futures::channel::mpsc;
20use futures::future::FusedFuture;
21use futures::stream::FuturesUnordered;
22use futures::{FutureExt, Sink, SinkExt, StreamExt, select, stream};
23use parity_scale_codec::{Decode, Encode};
24use std::error::Error;
25use std::future::pending;
26use std::num::NonZeroUsize;
27use std::pin::pin;
28use std::sync::Arc;
29use std::task::Poll;
30use std::time::{Duration, Instant};
31use subspace_core_primitives::PublicKey;
32use subspace_core_primitives::sectors::SectorIndex;
33use subspace_farmer_components::FarmerProtocolInfo;
34use subspace_farmer_components::plotting::PlottedSector;
35use subspace_farmer_components::sector::sector_size;
36use subspace_process::AsyncJoinOnDrop;
37use tokio::sync::{OwnedSemaphorePermit, Semaphore};
38use tokio::time::MissedTickBehavior;
39use tracing::{Instrument, debug, info, info_span, trace, warn};
40use ulid::Ulid;
41
42const FREE_CAPACITY_CHECK_INTERVAL: Duration = Duration::from_secs(1);
43/// Intervals between pings from plotter server to client
44const PING_INTERVAL: Duration = Duration::from_secs(10);
45/// Timeout after which plotter that doesn't send pings is assumed to be down
46const PING_TIMEOUT: Duration = Duration::from_mins(1);
47
48/// Type alias used for event handlers
49pub type HandlerFn3<A, B, C> = Arc<dyn Fn(&A, &B, &C) + Send + Sync + 'static>;
50type Handler3<A, B, C> = Bag<HandlerFn3<A, B, C>, A, B, C>;
51
52/// An ephemeral identifier for a plotter
53#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display)]
54pub enum ClusterPlotterId {
55    /// Plotter ID
56    Ulid(Ulid),
57}
58
59#[allow(clippy::new_without_default)]
60impl ClusterPlotterId {
61    /// Creates new ID
62    pub fn new() -> Self {
63        Self::Ulid(Ulid::new())
64    }
65}
66
67/// Request for free plotter instance
68#[derive(Debug, Clone, Encode, Decode)]
69struct ClusterPlotterFreeInstanceRequest;
70
71impl GenericRequest for ClusterPlotterFreeInstanceRequest {
72    const SUBJECT: &'static str = "subspace.plotter.free-instance";
73    /// Might be `None` if instance had to respond, but turned out it was fully occupied already
74    type Response = Option<String>;
75}
76
77#[derive(Debug, Encode, Decode)]
78enum ClusterSectorPlottingProgress {
79    /// Plotter is already fully occupied with other work
80    Occupied,
81    /// Periodic ping indicating plotter is still busy
82    Ping,
83    /// Downloading sector pieces
84    Downloading,
85    /// Downloaded sector pieces
86    Downloaded(Duration),
87    /// Encoding sector pieces
88    Encoding,
89    /// Encoded sector pieces
90    Encoded(Duration),
91    /// Finished plotting, followed by a series of sector chunks
92    Finished {
93        /// Information about plotted sector
94        plotted_sector: PlottedSector,
95        /// How much time it took to plot a sector
96        time: Duration,
97    },
98    /// Sector chunk after finished plotting
99    SectorChunk(Result<Bytes, String>),
100    /// Plotting failed
101    Error {
102        /// Error message
103        error: String,
104    },
105}
106
107/// Request to plot sector from plotter
108#[derive(Debug, Clone, Encode, Decode)]
109struct ClusterPlotterPlotSectorRequest {
110    public_key: PublicKey,
111    sector_index: SectorIndex,
112    farmer_protocol_info: FarmerProtocolInfo,
113    pieces_in_sector: u16,
114}
115
116impl GenericStreamRequest for ClusterPlotterPlotSectorRequest {
117    const SUBJECT: &'static str = "subspace.plotter.*.plot-sector";
118    type Response = ClusterSectorPlottingProgress;
119}
120
121#[derive(Default, Debug)]
122struct Handlers {
123    plotting_progress: Handler3<PublicKey, SectorIndex, SectorPlottingProgress>,
124}
125
126/// Cluster plotter
127#[derive(Debug)]
128pub struct ClusterPlotter {
129    sector_encoding_semaphore: Arc<Semaphore>,
130    retry_backoff_policy: ExponentialBackoff,
131    nats_client: NatsClient,
132    handlers: Arc<Handlers>,
133    tasks_sender: mpsc::Sender<AsyncJoinOnDrop<()>>,
134    _background_tasks: AsyncJoinOnDrop<()>,
135}
136
137impl Drop for ClusterPlotter {
138    #[inline]
139    fn drop(&mut self) {
140        self.tasks_sender.close_channel();
141    }
142}
143
144#[async_trait]
145impl Plotter for ClusterPlotter {
146    async fn has_free_capacity(&self) -> Result<bool, String> {
147        Ok(self.sector_encoding_semaphore.available_permits() > 0
148            && self
149                .nats_client
150                .request(&ClusterPlotterFreeInstanceRequest, None)
151                .await
152                .map_err(|error| error.to_string())?
153                .is_some())
154    }
155
156    async fn plot_sector(
157        &self,
158        public_key: PublicKey,
159        sector_index: SectorIndex,
160        farmer_protocol_info: FarmerProtocolInfo,
161        pieces_in_sector: u16,
162        _replotting: bool,
163        mut progress_sender: mpsc::Sender<SectorPlottingProgress>,
164    ) {
165        let start = Instant::now();
166
167        // Done outside the future below as a backpressure, ensuring that it is not possible to
168        // schedule unbounded number of plotting tasks
169        let sector_encoding_permit = match Arc::clone(&self.sector_encoding_semaphore)
170            .acquire_owned()
171            .await
172        {
173            Ok(sector_encoding_permit) => sector_encoding_permit,
174            Err(error) => {
175                warn!(%error, "Failed to acquire sector encoding permit");
176
177                let progress_updater = ProgressUpdater {
178                    public_key,
179                    sector_index,
180                    handlers: Arc::clone(&self.handlers),
181                };
182
183                progress_updater
184                    .update_progress_and_events(
185                        &mut progress_sender,
186                        SectorPlottingProgress::Error {
187                            error: format!("Failed to acquire sector encoding permit: {error}"),
188                        },
189                    )
190                    .await;
191
192                return;
193            }
194        };
195
196        self.plot_sector_internal(
197            start,
198            sector_encoding_permit,
199            public_key,
200            sector_index,
201            farmer_protocol_info,
202            pieces_in_sector,
203            progress_sender,
204        )
205        .await
206    }
207
208    async fn try_plot_sector(
209        &self,
210        public_key: PublicKey,
211        sector_index: SectorIndex,
212        farmer_protocol_info: FarmerProtocolInfo,
213        pieces_in_sector: u16,
214        _replotting: bool,
215        progress_sender: mpsc::Sender<SectorPlottingProgress>,
216    ) -> bool {
217        let start = Instant::now();
218
219        let Ok(sector_encoding_permit) =
220            Arc::clone(&self.sector_encoding_semaphore).try_acquire_owned()
221        else {
222            return false;
223        };
224
225        self.plot_sector_internal(
226            start,
227            sector_encoding_permit,
228            public_key,
229            sector_index,
230            farmer_protocol_info,
231            pieces_in_sector,
232            progress_sender,
233        )
234        .await;
235
236        true
237    }
238}
239
240impl ClusterPlotter {
241    /// Create new instance
242    pub fn new(
243        nats_client: NatsClient,
244        sector_encoding_concurrency: NonZeroUsize,
245        retry_backoff_policy: ExponentialBackoff,
246    ) -> Self {
247        let sector_encoding_semaphore = Arc::new(Semaphore::new(sector_encoding_concurrency.get()));
248
249        let (tasks_sender, mut tasks_receiver) = mpsc::channel(1);
250
251        // Basically runs plotting tasks in the background and allows to abort on drop
252        let background_tasks = AsyncJoinOnDrop::new(
253            tokio::spawn(async move {
254                let background_tasks = FuturesUnordered::new();
255                let mut background_tasks = pin!(background_tasks);
256                // Just so that `FuturesUnordered` will never end
257                background_tasks.push(AsyncJoinOnDrop::new(tokio::spawn(pending::<()>()), true));
258
259                loop {
260                    select! {
261                        maybe_background_task = tasks_receiver.next().fuse() => {
262                            let Some(background_task) = maybe_background_task else {
263                                break;
264                            };
265
266                            background_tasks.push(background_task);
267                        },
268                        _ = background_tasks.select_next_some() => {
269                            // Nothing to do
270                        }
271                    }
272                }
273            }),
274            true,
275        );
276
277        Self {
278            sector_encoding_semaphore,
279            retry_backoff_policy,
280            nats_client,
281            handlers: Arc::default(),
282            tasks_sender,
283            _background_tasks: background_tasks,
284        }
285    }
286
287    /// Subscribe to plotting progress notifications
288    pub fn on_plotting_progress(
289        &self,
290        callback: HandlerFn3<PublicKey, SectorIndex, SectorPlottingProgress>,
291    ) -> HandlerId {
292        self.handlers.plotting_progress.add(callback)
293    }
294
295    #[allow(clippy::too_many_arguments)]
296    async fn plot_sector_internal<PS>(
297        &self,
298        start: Instant,
299        sector_encoding_permit: OwnedSemaphorePermit,
300        public_key: PublicKey,
301        sector_index: SectorIndex,
302        farmer_protocol_info: FarmerProtocolInfo,
303        pieces_in_sector: u16,
304        mut progress_sender: PS,
305    ) where
306        PS: Sink<SectorPlottingProgress> + Unpin + Send + 'static,
307        PS::Error: Error,
308    {
309        trace!("Starting plotting, getting plotting permit");
310
311        let progress_updater = ProgressUpdater {
312            public_key,
313            sector_index,
314            handlers: Arc::clone(&self.handlers),
315        };
316
317        let mut retry_backoff_policy = self.retry_backoff_policy.clone();
318        retry_backoff_policy.reset();
319
320        // Try to get plotter instance here first as a backpressure measure
321        let free_plotter_instance_fut = get_free_plotter_instance(
322            &self.nats_client,
323            &progress_updater,
324            &mut progress_sender,
325            &mut retry_backoff_policy,
326        );
327        let mut maybe_free_instance = free_plotter_instance_fut.await;
328        if maybe_free_instance.is_none() {
329            return;
330        }
331
332        trace!("Got plotting permit #1");
333
334        let nats_client = self.nats_client.clone();
335
336        let plotting_fut = async move {
337            'outer: loop {
338                // Take free instance that was found earlier if available or try to find a new one
339                let free_instance = match maybe_free_instance.take() {
340                    Some(free_instance) => free_instance,
341                    None => {
342                        let free_plotter_instance_fut = get_free_plotter_instance(
343                            &nats_client,
344                            &progress_updater,
345                            &mut progress_sender,
346                            &mut retry_backoff_policy,
347                        );
348                        let Some(free_instance) = free_plotter_instance_fut.await else {
349                            break;
350                        };
351                        trace!("Got plotting permit #2");
352                        free_instance
353                    }
354                };
355
356                let response_stream_result = nats_client
357                    .stream_request(
358                        &ClusterPlotterPlotSectorRequest {
359                            public_key,
360                            sector_index,
361                            farmer_protocol_info,
362                            pieces_in_sector,
363                        },
364                        Some(&free_instance),
365                    )
366                    .await;
367                trace!("Subscribed to plotting notifications");
368
369                let mut response_stream = match response_stream_result {
370                    Ok(response_stream) => response_stream,
371                    Err(error) => {
372                        progress_updater
373                            .update_progress_and_events(
374                                &mut progress_sender,
375                                SectorPlottingProgress::Error {
376                                    error: format!("Failed make stream request: {error}"),
377                                },
378                            )
379                            .await;
380
381                        break;
382                    }
383                };
384
385                // Allow to buffer up to the whole sector in memory to not block plotter on the
386                // other side
387                let (mut sector_sender, sector_receiver) = mpsc::channel(
388                    (sector_size(pieces_in_sector) / nats_client.approximate_max_message_size())
389                        .max(1),
390                );
391                let mut maybe_sector_receiver = Some(sector_receiver);
392                loop {
393                    match tokio::time::timeout(PING_TIMEOUT, response_stream.next()).await {
394                        Ok(Some(response)) => {
395                            match process_response_notification(
396                                &start,
397                                &free_instance,
398                                &progress_updater,
399                                &mut progress_sender,
400                                &mut retry_backoff_policy,
401                                response,
402                                &mut sector_sender,
403                                &mut maybe_sector_receiver,
404                            )
405                            .await
406                            {
407                                ResponseProcessingResult::Retry => {
408                                    debug!("Retrying");
409                                    continue 'outer;
410                                }
411                                ResponseProcessingResult::Abort => {
412                                    debug!("Aborting");
413                                    break 'outer;
414                                }
415                                ResponseProcessingResult::Continue => {
416                                    trace!("Continue");
417                                    // Nothing to do
418                                }
419                            }
420                        }
421                        Ok(None) => {
422                            trace!("Plotting done");
423                            break;
424                        }
425                        Err(_error) => {
426                            progress_updater
427                                .update_progress_and_events(
428                                    &mut progress_sender,
429                                    SectorPlottingProgress::Error {
430                                        error: "Timed out without ping from plotter".to_string(),
431                                    },
432                                )
433                                .await;
434                            break;
435                        }
436                    }
437                }
438
439                break;
440            }
441
442            drop(sector_encoding_permit);
443        };
444
445        let plotting_task =
446            AsyncJoinOnDrop::new(tokio::spawn(plotting_fut.in_current_span()), true);
447        if let Err(error) = self.tasks_sender.clone().send(plotting_task).await {
448            warn!(%error, "Failed to send plotting task");
449
450            let progress = SectorPlottingProgress::Error {
451                error: format!("Failed to send plotting task: {error}"),
452            };
453
454            self.handlers
455                .plotting_progress
456                .call_simple(&public_key, &sector_index, &progress);
457        }
458    }
459}
460
461// Try to get free plotter instance and return `None` if it is not possible
462async fn get_free_plotter_instance<PS>(
463    nats_client: &NatsClient,
464    progress_updater: &ProgressUpdater,
465    progress_sender: &mut PS,
466    retry_backoff_policy: &mut ExponentialBackoff,
467) -> Option<String>
468where
469    PS: Sink<SectorPlottingProgress> + Unpin + Send + 'static,
470    PS::Error: Error,
471{
472    loop {
473        match nats_client
474            .request(&ClusterPlotterFreeInstanceRequest, None)
475            .await
476        {
477            Ok(Some(free_instance)) => {
478                return Some(free_instance);
479            }
480            Ok(None) => {
481                if let Some(delay) = retry_backoff_policy.next_backoff() {
482                    debug!("Instance was occupied, retrying #1");
483
484                    tokio::time::sleep(delay).await;
485                    continue;
486                } else {
487                    progress_updater
488                        .update_progress_and_events(
489                            progress_sender,
490                            SectorPlottingProgress::Error {
491                                error: "Instance was occupied, exiting #1".to_string(),
492                            },
493                        )
494                        .await;
495                    return None;
496                }
497            }
498            Err(error) => match error.kind() {
499                RequestErrorKind::TimedOut => {
500                    if let Some(delay) = retry_backoff_policy.next_backoff() {
501                        debug!("Plotter request timed out, retrying");
502
503                        tokio::time::sleep(delay).await;
504                        continue;
505                    } else {
506                        progress_updater
507                            .update_progress_and_events(
508                                progress_sender,
509                                SectorPlottingProgress::Error {
510                                    error: "Plotter request timed out, exiting".to_string(),
511                                },
512                            )
513                            .await;
514                        return None;
515                    }
516                }
517                RequestErrorKind::NoResponders => {
518                    if let Some(delay) = retry_backoff_policy.next_backoff() {
519                        debug!("No plotters, retrying");
520
521                        tokio::time::sleep(delay).await;
522                        continue;
523                    } else {
524                        progress_updater
525                            .update_progress_and_events(
526                                progress_sender,
527                                SectorPlottingProgress::Error {
528                                    error: "No plotters, exiting".to_string(),
529                                },
530                            )
531                            .await;
532                        return None;
533                    }
534                }
535                RequestErrorKind::Other
536                | RequestErrorKind::InvalidSubject
537                | RequestErrorKind::MaxPayloadExceeded => {
538                    progress_updater
539                        .update_progress_and_events(
540                            progress_sender,
541                            SectorPlottingProgress::Error {
542                                error: format!("Failed to get free plotter instance: {error}"),
543                            },
544                        )
545                        .await;
546                    return None;
547                }
548            },
549        };
550    }
551}
552
553enum ResponseProcessingResult {
554    Retry,
555    Abort,
556    Continue,
557}
558
559#[allow(clippy::too_many_arguments)]
560async fn process_response_notification<PS>(
561    start: &Instant,
562    free_instance: &str,
563    progress_updater: &ProgressUpdater,
564    progress_sender: &mut PS,
565    retry_backoff_policy: &mut ExponentialBackoff,
566    response: ClusterSectorPlottingProgress,
567    sector_sender: &mut mpsc::Sender<Result<Bytes, String>>,
568    maybe_sector_receiver: &mut Option<mpsc::Receiver<Result<Bytes, String>>>,
569) -> ResponseProcessingResult
570where
571    PS: Sink<SectorPlottingProgress> + Unpin + Send + 'static,
572    PS::Error: Error,
573{
574    if !matches!(response, ClusterSectorPlottingProgress::SectorChunk(_)) {
575        trace!(?response, "Processing plotting response notification");
576    } else {
577        trace!("Processing plotting response notification (sector chunk)");
578    }
579
580    match response {
581        ClusterSectorPlottingProgress::Occupied => {
582            debug!(%free_instance, "Instance was occupied, retrying #2");
583
584            if let Some(delay) = retry_backoff_policy.next_backoff() {
585                debug!("Instance was occupied, retrying #2");
586
587                tokio::time::sleep(delay).await;
588                return ResponseProcessingResult::Retry;
589            } else {
590                debug!("Instance was occupied, exiting #2");
591                return ResponseProcessingResult::Abort;
592            }
593        }
594        ClusterSectorPlottingProgress::Ping => {
595            // Expected
596        }
597        ClusterSectorPlottingProgress::Downloading => {
598            if !progress_updater
599                .update_progress_and_events(progress_sender, SectorPlottingProgress::Downloading)
600                .await
601            {
602                return ResponseProcessingResult::Abort;
603            }
604        }
605        ClusterSectorPlottingProgress::Downloaded(time) => {
606            if !progress_updater
607                .update_progress_and_events(
608                    progress_sender,
609                    SectorPlottingProgress::Downloaded(time),
610                )
611                .await
612            {
613                return ResponseProcessingResult::Abort;
614            }
615        }
616        ClusterSectorPlottingProgress::Encoding => {
617            if !progress_updater
618                .update_progress_and_events(progress_sender, SectorPlottingProgress::Encoding)
619                .await
620            {
621                return ResponseProcessingResult::Abort;
622            }
623        }
624        ClusterSectorPlottingProgress::Encoded(time) => {
625            if !progress_updater
626                .update_progress_and_events(progress_sender, SectorPlottingProgress::Encoded(time))
627                .await
628            {
629                return ResponseProcessingResult::Abort;
630            }
631        }
632        ClusterSectorPlottingProgress::Finished {
633            plotted_sector,
634            time: _,
635        } => {
636            let Some(sector_receiver) = maybe_sector_receiver.take() else {
637                debug!("Unexpected duplicated sector plotting progress Finished");
638
639                progress_updater
640                    .update_progress_and_events(
641                        progress_sender,
642                        SectorPlottingProgress::Error {
643                            error: "Unexpected duplicated sector plotting progress Finished"
644                                .to_string(),
645                        },
646                    )
647                    .await;
648                return ResponseProcessingResult::Abort;
649            };
650
651            let progress = SectorPlottingProgress::Finished {
652                plotted_sector,
653                // Use local time instead of reported by remote plotter
654                time: start.elapsed(),
655                sector: Box::pin(sector_receiver),
656            };
657            if !progress_updater
658                .update_progress_and_events(progress_sender, progress)
659                .await
660            {
661                return ResponseProcessingResult::Abort;
662            }
663
664            return ResponseProcessingResult::Continue;
665        }
666        // This variant must be sent after Finished and it handled above
667        ClusterSectorPlottingProgress::SectorChunk(maybe_sector_chunk) => {
668            if let Err(error) = sector_sender.send(maybe_sector_chunk).await {
669                warn!(%error, "Failed to send sector chunk");
670                return ResponseProcessingResult::Abort;
671            }
672            return ResponseProcessingResult::Continue;
673        }
674        ClusterSectorPlottingProgress::Error { error } => {
675            if !progress_updater
676                .update_progress_and_events(
677                    progress_sender,
678                    SectorPlottingProgress::Error { error },
679                )
680                .await
681            {
682                return ResponseProcessingResult::Abort;
683            }
684        }
685    }
686
687    ResponseProcessingResult::Continue
688}
689
690struct ProgressUpdater {
691    public_key: PublicKey,
692    sector_index: SectorIndex,
693    handlers: Arc<Handlers>,
694}
695
696impl ProgressUpdater {
697    /// Returns `true` on success and `false` if progress receiver channel is gone
698    async fn update_progress_and_events<PS>(
699        &self,
700        progress_sender: &mut PS,
701        progress: SectorPlottingProgress,
702    ) -> bool
703    where
704        PS: Sink<SectorPlottingProgress> + Unpin,
705        PS::Error: Error,
706    {
707        self.handlers.plotting_progress.call_simple(
708            &self.public_key,
709            &self.sector_index,
710            &progress,
711        );
712
713        if let Err(error) = progress_sender.send(progress).await {
714            warn!(%error, "Failed to send error progress update");
715
716            false
717        } else {
718            true
719        }
720    }
721}
722
723/// Create plotter service that will be processing incoming requests.
724///
725/// Implementation is using concurrency with multiple tokio tasks, but can be started multiple times
726/// per controller instance in order to parallelize more work across threads if needed.
727pub async fn plotter_service<P>(nats_client: &NatsClient, plotter: &P) -> anyhow::Result<()>
728where
729    P: Plotter + Sync,
730{
731    let plotter_id = ClusterPlotterId::new();
732
733    select! {
734        result = free_instance_responder(&plotter_id, nats_client, plotter).fuse() => {
735            result
736        }
737        result = plot_sector_responder(&plotter_id, nats_client, plotter).fuse() => {
738            result
739        }
740    }
741}
742
743async fn free_instance_responder<P>(
744    plotter_id: &ClusterPlotterId,
745    nats_client: &NatsClient,
746    plotter: &P,
747) -> anyhow::Result<()>
748where
749    P: Plotter + Sync,
750{
751    loop {
752        while !plotter.has_free_capacity().await.unwrap_or_default() {
753            tokio::time::sleep(FREE_CAPACITY_CHECK_INTERVAL).await;
754        }
755
756        let mut subscription = nats_client
757            .queue_subscribe(
758                ClusterPlotterFreeInstanceRequest::SUBJECT,
759                "subspace.plotter".to_string(),
760            )
761            .await
762            .map_err(|error| anyhow!("Failed to subscribe to free instance requests: {error}"))?;
763        debug!(?subscription, "Free instance subscription");
764
765        while let Some(message) = subscription.next().await {
766            let Some(reply_subject) = message.reply else {
767                continue;
768            };
769
770            debug!(%reply_subject, "Free instance request");
771
772            let has_free_capacity = plotter.has_free_capacity().await.unwrap_or_default();
773            let response: <ClusterPlotterFreeInstanceRequest as GenericRequest>::Response =
774                has_free_capacity.then(|| plotter_id.to_string());
775
776            if let Err(error) = nats_client
777                .publish(reply_subject, response.encode().into())
778                .await
779            {
780                warn!(%error, "Failed to send free instance response");
781            }
782
783            if !has_free_capacity {
784                subscription.unsubscribe().await.map_err(|error| {
785                    anyhow!("Failed to unsubscribe from free instance requests: {error}")
786                })?;
787            }
788        }
789    }
790}
791
792async fn plot_sector_responder<P>(
793    plotter_id: &ClusterPlotterId,
794    nats_client: &NatsClient,
795    plotter: &P,
796) -> anyhow::Result<()>
797where
798    P: Plotter + Sync,
799{
800    let plotter_id_string = plotter_id.to_string();
801
802    nats_client
803        .stream_request_responder(
804            Some(&plotter_id_string),
805            Some(plotter_id_string.clone()),
806            |request| async move {
807                let (progress_sender, mut progress_receiver) = mpsc::channel(10);
808
809                let fut =
810                    process_plot_sector_request(nats_client, plotter, request, progress_sender);
811                let mut fut = Box::pin(fut.fuse());
812
813                Some(
814                    // Drive above future and stream back any pieces that were downloaded so far
815                    stream::poll_fn(move |cx| {
816                        if !fut.is_terminated() {
817                            // Result doesn't matter, we'll need to poll stream below anyway
818                            let _ = fut.poll_unpin(cx);
819                        }
820
821                        if let Poll::Ready(maybe_result) = progress_receiver.poll_next_unpin(cx) {
822                            return Poll::Ready(maybe_result);
823                        }
824
825                        // Exit will be done by the stream above
826                        Poll::Pending
827                    }),
828                )
829            },
830        )
831        .await
832}
833
834async fn process_plot_sector_request<P>(
835    nats_client: &NatsClient,
836    plotter: &P,
837    request: ClusterPlotterPlotSectorRequest,
838    mut response_proxy_sender: mpsc::Sender<ClusterSectorPlottingProgress>,
839) where
840    P: Plotter,
841{
842    let ClusterPlotterPlotSectorRequest {
843        public_key,
844        sector_index,
845        farmer_protocol_info,
846        pieces_in_sector,
847    } = request;
848
849    // Wrapper future just for instrumentation below
850    let inner_fut = async {
851        info!("Plot sector request");
852
853        let (progress_sender, mut progress_receiver) = mpsc::channel(1);
854
855        if !plotter
856            .try_plot_sector(
857                public_key,
858                sector_index,
859                farmer_protocol_info,
860                pieces_in_sector,
861                false,
862                progress_sender,
863            )
864            .await
865        {
866            debug!("Plotter is currently occupied and can't plot more sectors");
867
868            if let Err(error) = response_proxy_sender
869                .send(ClusterSectorPlottingProgress::Occupied)
870                .await
871            {
872                warn!(%error, "Failed to send plotting progress");
873                return;
874            }
875            return;
876        }
877
878        let progress_proxy_fut = {
879            let mut response_proxy_sender = response_proxy_sender.clone();
880            let approximate_max_message_size = nats_client.approximate_max_message_size();
881
882            async move {
883                while let Some(progress) = progress_receiver.next().await {
884                    send_publish_progress(
885                        &mut response_proxy_sender,
886                        progress,
887                        approximate_max_message_size,
888                    )
889                    .await;
890                }
891            }
892        };
893
894        let mut ping_interval = tokio::time::interval(PING_INTERVAL);
895        ping_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
896        let ping_fut = async {
897            loop {
898                ping_interval.tick().await;
899                if let Err(error) = response_proxy_sender
900                    .send(ClusterSectorPlottingProgress::Ping)
901                    .await
902                {
903                    warn!(%error, "Failed to send plotting ping");
904                    return;
905                }
906            }
907        };
908
909        select! {
910            _ = progress_proxy_fut.fuse() => {
911                // Done
912            }
913            _ = ping_fut.fuse() => {
914                unreachable!("Ping loop never ends");
915            }
916        }
917
918        info!("Finished plotting sector successfully");
919    };
920
921    inner_fut
922        .instrument(info_span!("", %public_key, %sector_index))
923        .await
924}
925
926async fn send_publish_progress(
927    response_sender: &mut mpsc::Sender<ClusterSectorPlottingProgress>,
928    progress: SectorPlottingProgress,
929    approximate_max_message_size: usize,
930) {
931    // Finished response is large and needs special care
932    let cluster_progress = match progress {
933        SectorPlottingProgress::Downloading => ClusterSectorPlottingProgress::Downloading,
934        SectorPlottingProgress::Downloaded(time) => ClusterSectorPlottingProgress::Downloaded(time),
935        SectorPlottingProgress::Encoding => ClusterSectorPlottingProgress::Encoding,
936        SectorPlottingProgress::Encoded(time) => ClusterSectorPlottingProgress::Encoded(time),
937        SectorPlottingProgress::Finished {
938            plotted_sector,
939            time,
940            mut sector,
941        } => {
942            if let Err(error) = response_sender
943                .send(ClusterSectorPlottingProgress::Finished {
944                    plotted_sector,
945                    time,
946                })
947                .await
948            {
949                warn!(%error, "Failed to send plotting progress");
950                return;
951            }
952
953            while let Some(maybe_sector_chunk) = sector.next().await {
954                match maybe_sector_chunk {
955                    Ok(sector_chunk) => {
956                        // Slice large chunks into smaller ones before publishing
957                        for small_sector_chunk in sector_chunk.chunks(approximate_max_message_size)
958                        {
959                            if let Err(error) = response_sender
960                                .send(ClusterSectorPlottingProgress::SectorChunk(Ok(
961                                    sector_chunk.slice_ref(small_sector_chunk)
962                                )))
963                                .await
964                            {
965                                warn!(%error, "Failed to send plotting progress");
966                                return;
967                            }
968                        }
969                    }
970                    Err(error) => {
971                        if let Err(error) = response_sender
972                            .send(ClusterSectorPlottingProgress::SectorChunk(Err(error)))
973                            .await
974                        {
975                            warn!(%error, "Failed to send plotting progress");
976                            return;
977                        }
978                    }
979                }
980            }
981
982            return;
983        }
984        SectorPlottingProgress::Error { error } => ClusterSectorPlottingProgress::Error { error },
985    };
986
987    if let Err(error) = response_sender.send(cluster_progress).await {
988        warn!(%error, "Failed to send plotting progress");
989    }
990}