1mod gpu_encoders_manager;
4pub mod metrics;
5
6use crate::plotter::gpu::gpu_encoders_manager::GpuRecordsEncoderManager;
7use crate::plotter::gpu::metrics::GpuPlotterMetrics;
8use crate::plotter::{Plotter, SectorPlottingProgress};
9use crate::utils::AsyncJoinOnDrop;
10use ab_core_primitives::ed25519::Ed25519PublicKey;
11use ab_core_primitives::sectors::SectorIndex;
12use ab_core_primitives::solutions::ShardCommitmentHash;
13use ab_data_retrieval::piece_getter::PieceGetter;
14use ab_erasure_coding::ErasureCoding;
15use ab_farmer_components::FarmerProtocolInfo;
16use ab_farmer_components::plotting::{
17 DownloadSectorOptions, EncodeSectorOptions, PlottingError, download_sector, encode_sector,
18 write_sector,
19};
20use ab_proof_of_space_gpu::GpuRecordsEncoder;
21use async_lock::{Mutex as AsyncMutex, Semaphore, SemaphoreGuardArc};
22use async_trait::async_trait;
23use bytes::Bytes;
24use event_listener_primitives::{Bag, HandlerId};
25use futures::channel::mpsc;
26use futures::stream::FuturesUnordered;
27use futures::{FutureExt, Sink, SinkExt, StreamExt, select, stream};
28use prometheus_client::registry::Registry;
29use std::error::Error;
30use std::fmt;
31use std::future::pending;
32use std::num::TryFromIntError;
33use std::pin::pin;
34use std::sync::Arc as StdArc;
35use std::sync::atomic::{AtomicBool, Ordering};
36use std::task::Poll;
37use std::time::Instant;
38use tokio::task::yield_now;
39use tracing::{Instrument, warn};
40use triomphe::Arc;
41
42pub type HandlerFn3<A, B, C> = StdArc<dyn Fn(&A, &B, &C) + Send + Sync + 'static>;
44type Handler3<A, B, C> = Bag<HandlerFn3<A, B, C>, A, B, C>;
45
46#[derive(Default, Debug)]
47struct Handlers {
48 plotting_progress: Handler3<Ed25519PublicKey, SectorIndex, SectorPlottingProgress>,
49}
50
51pub struct GpuPlotter<PG> {
53 piece_getter: PG,
54 downloading_semaphore: StdArc<Semaphore>,
55 gpu_records_encoders_manager: GpuRecordsEncoderManager,
56 global_mutex: Arc<AsyncMutex<()>>,
57 erasure_coding: ErasureCoding,
58 handlers: Arc<Handlers>,
59 tasks_sender: mpsc::Sender<AsyncJoinOnDrop<()>>,
60 _background_tasks: AsyncJoinOnDrop<()>,
61 abort_early: Arc<AtomicBool>,
62 metrics: Option<Arc<GpuPlotterMetrics>>,
63}
64
65impl<PG> fmt::Debug for GpuPlotter<PG> {
66 #[inline]
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.debug_struct("GpuPlotter").finish_non_exhaustive()
69 }
70}
71
72impl<PG> Drop for GpuPlotter<PG> {
73 #[inline]
74 fn drop(&mut self) {
75 self.abort_early.store(true, Ordering::Release);
76 self.tasks_sender.close_channel();
77 }
78}
79
80#[async_trait]
81impl<PG> Plotter for GpuPlotter<PG>
82where
83 PG: PieceGetter + Clone + Send + Sync + 'static,
84{
85 async fn has_free_capacity(&self) -> Result<bool, String> {
86 Ok(self.downloading_semaphore.try_acquire().is_some())
87 }
88
89 async fn plot_sector(
90 &self,
91 public_key: Ed25519PublicKey,
92 shard_commitments_root: ShardCommitmentHash,
93 sector_index: SectorIndex,
94 farmer_protocol_info: FarmerProtocolInfo,
95 pieces_in_sector: u16,
96 _replotting: bool,
97 progress_sender: mpsc::Sender<SectorPlottingProgress>,
98 ) {
99 let start = Instant::now();
100
101 let downloading_permit = self.downloading_semaphore.acquire_arc().await;
104
105 self.plot_sector_internal(
106 start,
107 downloading_permit,
108 public_key,
109 shard_commitments_root,
110 sector_index,
111 farmer_protocol_info,
112 pieces_in_sector,
113 progress_sender,
114 )
115 .await;
116 }
117
118 async fn try_plot_sector(
119 &self,
120 public_key: Ed25519PublicKey,
121 shard_commitments_root: ShardCommitmentHash,
122 sector_index: SectorIndex,
123 farmer_protocol_info: FarmerProtocolInfo,
124 pieces_in_sector: u16,
125 _replotting: bool,
126 progress_sender: mpsc::Sender<SectorPlottingProgress>,
127 ) -> bool {
128 let start = Instant::now();
129
130 let Some(downloading_permit) = self.downloading_semaphore.try_acquire_arc() else {
131 return false;
132 };
133
134 self.plot_sector_internal(
135 start,
136 downloading_permit,
137 public_key,
138 shard_commitments_root,
139 sector_index,
140 farmer_protocol_info,
141 pieces_in_sector,
142 progress_sender,
143 )
144 .await;
145
146 true
147 }
148}
149
150impl<PG> GpuPlotter<PG>
151where
152 PG: PieceGetter + Clone + Send + Sync + 'static,
153{
154 pub fn new(
158 piece_getter: PG,
159 downloading_semaphore: StdArc<Semaphore>,
160 gpu_records_encoders: Vec<GpuRecordsEncoder>,
161 global_mutex: Arc<AsyncMutex<()>>,
162 erasure_coding: ErasureCoding,
163 registry: Option<&mut Registry>,
164 ) -> Result<Self, TryFromIntError> {
165 let (tasks_sender, mut tasks_receiver) = mpsc::channel(1);
166
167 let background_tasks = AsyncJoinOnDrop::new(
169 tokio::spawn(async move {
170 let background_tasks = FuturesUnordered::new();
171 let mut background_tasks = pin!(background_tasks);
172 background_tasks.push(AsyncJoinOnDrop::new(tokio::spawn(pending::<()>()), true));
174
175 loop {
176 select! {
177 maybe_background_task = tasks_receiver.next().fuse() => {
178 let Some(background_task) = maybe_background_task else {
179 break;
180 };
181
182 background_tasks.push(background_task);
183 },
184 _ = background_tasks.select_next_some() => {
185 }
187 }
188 }
189 }),
190 true,
191 );
192
193 let abort_early = Arc::new(AtomicBool::new(false));
194 let gpu_records_encoders_manager = GpuRecordsEncoderManager::new(gpu_records_encoders)?;
195 let metrics = registry.map(|registry| {
196 Arc::new(GpuPlotterMetrics::new(
197 registry,
198 gpu_records_encoders_manager.gpu_records_encoders(),
199 ))
200 });
201
202 Ok(Self {
203 piece_getter,
204 downloading_semaphore,
205 gpu_records_encoders_manager,
206 global_mutex,
207 erasure_coding,
208 handlers: Arc::default(),
209 tasks_sender,
210 _background_tasks: background_tasks,
211 abort_early,
212 metrics,
213 })
214 }
215
216 pub fn on_plotting_progress(
218 &self,
219 callback: HandlerFn3<Ed25519PublicKey, SectorIndex, SectorPlottingProgress>,
220 ) -> HandlerId {
221 self.handlers.plotting_progress.add(callback)
222 }
223
224 #[expect(clippy::too_many_arguments, reason = "Internal API")]
225 async fn plot_sector_internal<PS>(
226 &self,
227 start: Instant,
228 downloading_permit: SemaphoreGuardArc,
229 public_key: Ed25519PublicKey,
230 shard_commitments_root: ShardCommitmentHash,
231 sector_index: SectorIndex,
232 farmer_protocol_info: FarmerProtocolInfo,
233 pieces_in_sector: u16,
234 mut progress_sender: PS,
235 ) where
236 PS: Sink<SectorPlottingProgress> + Unpin + Send + 'static,
237 PS::Error: Error,
238 {
239 if let Some(metrics) = &self.metrics {
240 metrics.sector_plotting.inc();
241 }
242
243 let progress_updater = ProgressUpdater {
244 public_key,
245 sector_index,
246 handlers: Arc::clone(&self.handlers),
247 metrics: self.metrics.clone(),
248 };
249
250 let plotting_fut = {
251 let piece_getter = self.piece_getter.clone();
252 let gpu_records_encoders_manager = self.gpu_records_encoders_manager.clone();
253 let global_mutex = Arc::clone(&self.global_mutex);
254 let erasure_coding = self.erasure_coding.clone();
255 let abort_early = Arc::clone(&self.abort_early);
256 let metrics = self.metrics.clone();
257
258 async move {
259 let downloaded_sector = {
261 if !progress_updater
262 .update_progress_and_events(
263 &mut progress_sender,
264 SectorPlottingProgress::Downloading,
265 )
266 .await
267 {
268 return;
269 }
270
271 global_mutex.lock().await;
273
274 let downloading_start = Instant::now();
275 let public_key_hash = &public_key.hash();
276
277 let downloaded_sector_fut = download_sector(DownloadSectorOptions {
278 public_key_hash,
279 shard_commitments_root: &shard_commitments_root,
280 sector_index,
281 piece_getter: &piece_getter,
282 farmer_protocol_info,
283 erasure_coding: &erasure_coding,
284 pieces_in_sector,
285 });
286
287 let downloaded_sector = match downloaded_sector_fut.await {
288 Ok(downloaded_sector) => downloaded_sector,
289 Err(error) => {
290 warn!(%error, "Failed to download sector");
291
292 progress_updater
293 .update_progress_and_events(
294 &mut progress_sender,
295 SectorPlottingProgress::Error {
296 error: format!("Failed to download sector: {error}"),
297 },
298 )
299 .await;
300
301 return;
302 }
303 };
304
305 if !progress_updater
306 .update_progress_and_events(
307 &mut progress_sender,
308 SectorPlottingProgress::Downloaded(downloading_start.elapsed()),
309 )
310 .await
311 {
312 return;
313 }
314
315 downloaded_sector
316 };
317
318 let (sector, plotted_sector) = {
320 let mut records_encoder = gpu_records_encoders_manager.get_encoder().await;
321 if let Some(metrics) = &metrics {
322 metrics.plotting_capacity_used.inc();
323 }
324
325 yield_now().await;
327
328 if !progress_updater
329 .update_progress_and_events(
330 &mut progress_sender,
331 SectorPlottingProgress::Encoding,
332 )
333 .await
334 {
335 if let Some(metrics) = &metrics {
336 metrics.plotting_capacity_used.dec();
337 }
338 return;
339 }
340
341 let encoding_start = Instant::now();
342
343 let plotting_result = tokio::task::block_in_place(move || {
344 let encoded_sector = encode_sector(
345 downloaded_sector,
346 EncodeSectorOptions {
347 sector_index,
348 records_encoder: &mut *records_encoder,
349 abort_early: &abort_early,
350 },
351 )?;
352
353 if abort_early.load(Ordering::Acquire) {
354 return Err(PlottingError::AbortEarly);
355 }
356
357 drop(records_encoder);
358
359 let mut sector = Vec::new();
360
361 write_sector(&encoded_sector, &mut sector)?;
362
363 Ok((sector, encoded_sector.plotted_sector))
364 });
365
366 if let Some(metrics) = &metrics {
367 metrics.plotting_capacity_used.dec();
368 }
369
370 match plotting_result {
371 Ok(plotting_result) => {
372 if !progress_updater
373 .update_progress_and_events(
374 &mut progress_sender,
375 SectorPlottingProgress::Encoded(encoding_start.elapsed()),
376 )
377 .await
378 {
379 return;
380 }
381
382 plotting_result
383 }
384 Err(PlottingError::AbortEarly) => {
385 return;
386 }
387 Err(error) => {
388 progress_updater
389 .update_progress_and_events(
390 &mut progress_sender,
391 SectorPlottingProgress::Error {
392 error: format!("Failed to encode sector: {error}"),
393 },
394 )
395 .await;
396
397 return;
398 }
399 }
400 };
401
402 progress_updater
403 .update_progress_and_events(
404 &mut progress_sender,
405 SectorPlottingProgress::Finished {
406 plotted_sector,
407 time: start.elapsed(),
408 sector: Box::pin({
409 let mut sector = Some(Ok(Bytes::from(sector)));
410
411 stream::poll_fn(move |_cx| {
412 let _downloading_permit = &downloading_permit;
414
415 Poll::Ready(sector.take())
416 })
417 }),
418 },
419 )
420 .await;
421 }
422 };
423
424 let plotting_task =
426 AsyncJoinOnDrop::new(tokio::spawn(plotting_fut.in_current_span()), true);
427 if let Err(error) = self.tasks_sender.clone().send(plotting_task).await {
428 warn!(%error, "Failed to send plotting task");
429
430 let progress = SectorPlottingProgress::Error {
431 error: format!("Failed to send plotting task: {error}"),
432 };
433
434 self.handlers
435 .plotting_progress
436 .call_simple(&public_key, §or_index, &progress);
437 }
438 }
439}
440
441struct ProgressUpdater {
442 public_key: Ed25519PublicKey,
443 sector_index: SectorIndex,
444 handlers: Arc<Handlers>,
445 metrics: Option<Arc<GpuPlotterMetrics>>,
446}
447
448impl ProgressUpdater {
449 async fn update_progress_and_events<PS>(
451 &self,
452 progress_sender: &mut PS,
453 progress: SectorPlottingProgress,
454 ) -> bool
455 where
456 PS: Sink<SectorPlottingProgress> + Unpin,
457 PS::Error: Error,
458 {
459 if let Some(metrics) = &self.metrics {
460 #[expect(
461 clippy::rest_pattern_accessible_field,
462 reason = "Do not need other fields"
463 )]
464 match &progress {
465 SectorPlottingProgress::Downloading => {
466 metrics.sector_downloading.inc();
467 }
468 SectorPlottingProgress::Downloaded(time) => {
469 metrics.sector_downloading_time.observe(time.as_secs_f64());
470 metrics.sector_downloaded.inc();
471 }
472 SectorPlottingProgress::Encoding => {
473 metrics.sector_encoding.inc();
474 }
475 SectorPlottingProgress::Encoded(time) => {
476 metrics.sector_encoding_time.observe(time.as_secs_f64());
477 metrics.sector_encoded.inc();
478 }
479 SectorPlottingProgress::Finished { time, .. } => {
480 metrics.sector_plotting_time.observe(time.as_secs_f64());
481 metrics.sector_plotted.inc();
482 }
483 SectorPlottingProgress::Error { .. } => {
484 metrics.sector_plotting_error.inc();
485 }
486 }
487 }
488 self.handlers.plotting_progress.call_simple(
489 &self.public_key,
490 &self.sector_index,
491 &progress,
492 );
493
494 if let Err(error) = progress_sender.send(progress).await {
495 warn!(%error, "Failed to send progress update");
496
497 false
498 } else {
499 true
500 }
501 }
502}