1use 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);
43const PING_INTERVAL: Duration = Duration::from_secs(10);
45const PING_TIMEOUT: Duration = Duration::from_mins(1);
47
48pub 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#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Display)]
54pub enum ClusterPlotterId {
55 Ulid(Ulid),
57}
58
59#[allow(clippy::new_without_default)]
60impl ClusterPlotterId {
61 pub fn new() -> Self {
63 Self::Ulid(Ulid::new())
64 }
65}
66
67#[derive(Debug, Clone, Encode, Decode)]
69struct ClusterPlotterFreeInstanceRequest;
70
71impl GenericRequest for ClusterPlotterFreeInstanceRequest {
72 const SUBJECT: &'static str = "subspace.plotter.free-instance";
73 type Response = Option<String>;
75}
76
77#[derive(Debug, Encode, Decode)]
78enum ClusterSectorPlottingProgress {
79 Occupied,
81 Ping,
83 Downloading,
85 Downloaded(Duration),
87 Encoding,
89 Encoded(Duration),
91 Finished {
93 plotted_sector: PlottedSector,
95 time: Duration,
97 },
98 SectorChunk(Result<Bytes, String>),
100 Error {
102 error: String,
104 },
105}
106
107#[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#[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 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 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 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 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 }
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 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 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 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 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 }
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, §or_index, &progress);
457 }
458 }
459}
460
461async 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 }
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 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 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 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
723pub 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 stream::poll_fn(move |cx| {
816 if !fut.is_terminated() {
817 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 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 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 }
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 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 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}