1use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17use std::time::{Duration, Instant, SystemTime};
18
19use fail::fail_point;
20use parking_lot::RwLock;
21use risingwave_hummock_sdk::compact::statistics_compact_task;
22use risingwave_hummock_sdk::compact_task::CompactTask;
23use risingwave_hummock_sdk::{HummockCompactionTaskId, HummockContextId};
24use risingwave_pb::hummock::subscribe_compaction_event_response::Event as ResponseEvent;
25use risingwave_pb::hummock::{
26 CancelCompactTask, CompactTaskProgress, SubscribeCompactionEventResponse,
27};
28use risingwave_pb::iceberg_compaction::subscribe_iceberg_compaction_event_response::Event as IcebergResponseEvent;
29use risingwave_pb::iceberg_compaction::{
30 CancelCompactTask as IcebergCancelCompactTask, SubscribeIcebergCompactionEventResponse,
31};
32use risingwave_pb::id::IcebergCompactionTaskId;
33use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
34
35use crate::MetaResult;
36use crate::hummock::model::ext::compaction_task_model_to_assignment;
37use crate::manager::MetaSrvEnv;
38use crate::model::MetadataModelError;
39
40pub type CompactorManagerRef = Arc<CompactorManager>;
41pub type IcebergCompactorManagerRef = Arc<IcebergCompactorManager>;
42
43pub const TASK_RUN_TOO_LONG: &str = "running too long";
44pub const TASK_NOT_FOUND: &str = "task not found";
45pub const TASK_NORMAL: &str = "task is normal, please wait some time";
46
47type CompactorSubscribeStreamSender = UnboundedSender<MetaResult<SubscribeCompactionEventResponse>>;
48type CompactorSubscribeStreamReceiver =
49 UnboundedReceiver<MetaResult<SubscribeCompactionEventResponse>>;
50
51type IcebergCompactorSubscribeStreamSender =
52 UnboundedSender<MetaResult<SubscribeIcebergCompactionEventResponse>>;
53type IcebergCompactorSubscribeStreamReceiver =
54 UnboundedReceiver<MetaResult<SubscribeIcebergCompactionEventResponse>>;
55
56type CompactorSubscribeResponseEvent = ResponseEvent;
57
58type IcebergCompactorSubscribeResponseEvent = IcebergResponseEvent;
59
60pub struct Compactor {
63 context_id: HummockContextId,
64 sender: CompactorSubscribeStreamSender,
65}
66
67pub struct IcebergCompactor {
68 context_id: HummockContextId,
69 sender: IcebergCompactorSubscribeStreamSender,
70}
71
72struct TaskHeartbeat {
73 task: CompactTask,
74 num_ssts_sealed: u32,
75 num_ssts_uploaded: u32,
76 num_progress_key: u64,
77 num_pending_read_io: u64,
78 num_pending_write_io: u64,
79 create_time: Instant,
80 expire_at: u64,
81
82 update_at: u64,
83}
84
85impl Compactor {
86 pub fn new(context_id: HummockContextId, sender: CompactorSubscribeStreamSender) -> Self {
87 Self { context_id, sender }
88 }
89
90 pub fn send_event(&self, event: CompactorSubscribeResponseEvent) -> MetaResult<()> {
91 fail_point!("compaction_send_task_fail", |_| Err(anyhow::anyhow!(
92 "compaction_send_task_fail"
93 )
94 .into()));
95
96 self.sender
97 .send(Ok(SubscribeCompactionEventResponse {
98 create_at: SystemTime::now()
99 .duration_since(SystemTime::UNIX_EPOCH)
100 .expect("Clock may have gone backwards")
101 .as_millis() as u64,
102 event: Some(event),
103 }))
104 .map_err(|e| anyhow::anyhow!(e))?;
105
106 Ok(())
107 }
108
109 pub fn cancel_task(&self, task_id: u64) -> MetaResult<()> {
110 self.send_event(ResponseEvent::CancelCompactTask(CancelCompactTask {
111 context_id: self.context_id,
112 task_id,
113 }))
114 }
115
116 pub fn cancel_tasks(&self, task_ids: &Vec<u64>) -> MetaResult<()> {
117 for task_id in task_ids {
118 self.cancel_task(*task_id)?;
119 }
120 Ok(())
121 }
122
123 pub fn context_id(&self) -> HummockContextId {
124 self.context_id
125 }
126}
127
128pub trait CompactorManagerTrait {
129 fn add_compactor(&self, context_id: HummockContextId) -> CompactorSubscribeStreamReceiver;
130 fn remove_compactor(&self, context_id: HummockContextId);
131 fn get_compactor(&self, context_id: HummockContextId) -> Option<Arc<Compactor>>;
132 fn next_compactor(&self) -> Option<Arc<Compactor>>;
133 fn compactor_num(&self) -> usize;
134}
135
136pub struct CompactorManagerInner {
148 pub task_expired_seconds: u64,
149 pub heartbeat_expired_seconds: u64,
150 task_heartbeats: HashMap<HummockCompactionTaskId, TaskHeartbeat>,
151
152 pub compactor_map: HashMap<HummockContextId, Arc<Compactor>>,
154}
155
156impl CompactorManagerInner {
157 pub async fn with_meta(env: MetaSrvEnv) -> MetaResult<Self> {
158 use risingwave_meta_model::compaction_task;
159 use sea_orm::EntityTrait;
160 let task_assignment: Vec<_> = compaction_task::Entity::find()
162 .all(&env.meta_store_ref().conn)
163 .await
164 .map_err(MetadataModelError::from)?
165 .into_iter()
166 .map(compaction_task_model_to_assignment)
167 .collect();
168 let mut manager = Self {
169 task_expired_seconds: env.opts.compaction_task_max_progress_interval_secs,
170 heartbeat_expired_seconds: env.opts.compaction_task_max_heartbeat_interval_secs,
171 task_heartbeats: Default::default(),
172 compactor_map: Default::default(),
173 };
174 task_assignment.into_iter().for_each(|assignment| {
176 manager.initiate_task_heartbeat(assignment.compact_task);
177 });
178 Ok(manager)
179 }
180
181 pub fn for_test() -> Self {
183 Self {
184 task_expired_seconds: 1,
185 heartbeat_expired_seconds: 1,
186 task_heartbeats: Default::default(),
187 compactor_map: Default::default(),
188 }
189 }
190
191 pub fn next_compactor(&self) -> Option<Arc<Compactor>> {
192 use rand::Rng;
193 if self.compactor_map.is_empty() {
194 return None;
195 }
196
197 let rand_index = rand::rng().random_range(0..self.compactor_map.len());
198 let compactor = self.compactor_map.values().nth(rand_index).unwrap().clone();
199
200 Some(compactor)
201 }
202
203 pub fn add_compactor(
210 &mut self,
211 context_id: HummockContextId,
212 ) -> CompactorSubscribeStreamReceiver {
213 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
214
215 self.compactor_map
216 .insert(context_id, Arc::new(Compactor::new(context_id, tx)));
217
218 tracing::info!(context_id = %context_id, "Added compactor session");
219
220 rx
221 }
222
223 pub fn abort_all_compactors(&mut self) {
225 while let Some(compactor) = self.next_compactor() {
226 self.remove_compactor(compactor.context_id);
227 }
228 }
229
230 pub fn remove_compactor(&mut self, context_id: HummockContextId) {
231 if self.compactor_map.remove(&context_id).is_some() {
232 tracing::info!(context_id = %context_id, "Removed compactor session")
233 };
234 }
235
236 pub fn get_compactor(&self, context_id: HummockContextId) -> Option<Arc<Compactor>> {
237 self.compactor_map.get(&context_id).cloned()
238 }
239
240 pub fn check_tasks_status(
241 &self,
242 tasks: &[HummockCompactionTaskId],
243 slow_task_duration: Duration,
244 ) -> HashMap<HummockCompactionTaskId, (Duration, &'static str)> {
245 let tasks_ids: HashSet<u64> = HashSet::from_iter(tasks.to_vec());
246 let mut ret = HashMap::default();
247 for TaskHeartbeat {
248 task, create_time, ..
249 } in self.task_heartbeats.values()
250 {
251 if !tasks_ids.contains(&task.task_id) {
252 continue;
253 }
254 let pending_time = create_time.elapsed();
255 if pending_time > slow_task_duration {
256 ret.insert(task.task_id, (pending_time, TASK_RUN_TOO_LONG));
257 } else {
258 ret.insert(task.task_id, (pending_time, TASK_NORMAL));
259 }
260 }
261
262 for task_id in tasks {
263 if !ret.contains_key(task_id) {
264 ret.insert(*task_id, (Duration::from_secs(0), TASK_NOT_FOUND));
265 }
266 }
267 ret
268 }
269
270 pub fn get_heartbeat_expired_tasks(&self) -> Vec<CompactTask> {
271 let heartbeat_expired_ts: u64 = SystemTime::now()
272 .duration_since(SystemTime::UNIX_EPOCH)
273 .expect("Clock may have gone backwards")
274 .as_secs()
275 - self.heartbeat_expired_seconds;
276 Self::get_heartbeat_expired_tasks_impl(&self.task_heartbeats, heartbeat_expired_ts)
277 }
278
279 fn get_heartbeat_expired_tasks_impl(
280 task_heartbeats: &HashMap<HummockCompactionTaskId, TaskHeartbeat>,
281 heartbeat_expired_ts: u64,
282 ) -> Vec<CompactTask> {
283 let mut cancellable_tasks = vec![];
284 const MAX_TASK_DURATION_SEC: u64 = 2700;
285
286 for TaskHeartbeat {
287 expire_at,
288 task,
289 create_time,
290 num_ssts_sealed,
291 num_ssts_uploaded,
292 num_progress_key,
293 num_pending_read_io,
294 num_pending_write_io,
295 update_at,
296 } in task_heartbeats.values()
297 {
298 if *update_at < heartbeat_expired_ts {
299 cancellable_tasks.push(task.clone());
300 }
301
302 let task_duration_too_long = create_time.elapsed().as_secs() > MAX_TASK_DURATION_SEC;
303 if task_duration_too_long {
304 let compact_task_statistics = statistics_compact_task(task);
305 tracing::info!(
306 "CompactionGroupId {} Task {} duration too long create_time {:?} expire_at {:?} num_ssts_sealed {} num_ssts_uploaded {} num_progress_key {} \
307 pending_read_io_count {} pending_write_io_count {} target_level {} \
308 base_level {} target_sub_level_id {} task_type {} compact_task_statistics {:?}",
309 task.compaction_group_id,
310 task.task_id,
311 create_time,
312 expire_at,
313 num_ssts_sealed,
314 num_ssts_uploaded,
315 num_progress_key,
316 num_pending_read_io,
317 num_pending_write_io,
318 task.target_level,
319 task.base_level,
320 task.target_sub_level_id,
321 task.task_type.as_str_name(),
322 compact_task_statistics
323 );
324 }
325 }
326 cancellable_tasks
327 }
328
329 pub fn initiate_task_heartbeat(&mut self, task: CompactTask) {
330 let now = SystemTime::now()
331 .duration_since(SystemTime::UNIX_EPOCH)
332 .expect("Clock may have gone backwards")
333 .as_secs();
334 self.task_heartbeats.insert(
335 task.task_id,
336 TaskHeartbeat {
337 task,
338 num_ssts_sealed: 0,
339 num_ssts_uploaded: 0,
340 num_progress_key: 0,
341 num_pending_read_io: 0,
342 num_pending_write_io: 0,
343 create_time: Instant::now(),
344 expire_at: now + self.task_expired_seconds,
345 update_at: now,
346 },
347 );
348 }
349
350 pub fn remove_task_heartbeat(&mut self, task_id: u64) {
351 self.task_heartbeats.remove(&task_id).unwrap();
352 }
353
354 pub fn update_task_heartbeats(
356 &mut self,
357 progress_list: &Vec<CompactTaskProgress>,
358 ) -> (Vec<HummockCompactionTaskId>, Vec<HummockCompactionTaskId>) {
359 let now = SystemTime::now()
360 .duration_since(SystemTime::UNIX_EPOCH)
361 .expect("Clock may have gone backwards")
362 .as_secs();
363 let mut cancel_tasks = vec![];
364 let mut untracked_tasks = vec![];
365 for progress in progress_list {
366 if let Some(task_ref) = self.task_heartbeats.get_mut(&progress.task_id) {
367 task_ref.update_at = now;
368
369 if task_ref.num_ssts_sealed < progress.num_ssts_sealed
370 || task_ref.num_ssts_uploaded < progress.num_ssts_uploaded
371 || task_ref.num_progress_key < progress.num_progress_key
372 {
373 task_ref.expire_at = now + self.task_expired_seconds;
375 task_ref.num_ssts_sealed = progress.num_ssts_sealed;
376 task_ref.num_ssts_uploaded = progress.num_ssts_uploaded;
377 task_ref.num_progress_key = progress.num_progress_key;
378 }
379 task_ref.num_pending_read_io = progress.num_pending_read_io;
380 task_ref.num_pending_write_io = progress.num_pending_write_io;
381
382 if task_ref.expire_at < now {
384 cancel_tasks.push(progress.task_id)
386 }
387 } else {
388 untracked_tasks.push(progress.task_id);
391 }
392 }
393
394 (cancel_tasks, untracked_tasks)
395 }
396
397 pub fn compactor_num(&self) -> usize {
398 self.compactor_map.len()
399 }
400
401 pub fn get_progress(&self) -> Vec<CompactTaskProgress> {
402 self.task_heartbeats
403 .values()
404 .map(|hb| CompactTaskProgress {
405 task_id: hb.task.task_id,
406 num_ssts_sealed: hb.num_ssts_sealed,
407 num_ssts_uploaded: hb.num_ssts_uploaded,
408 num_progress_key: hb.num_progress_key,
409 num_pending_read_io: hb.num_pending_read_io,
410 num_pending_write_io: hb.num_pending_write_io,
411 compaction_group_id: Some(hb.task.compaction_group_id),
412 })
413 .collect()
414 }
415}
416
417pub struct CompactorManager {
418 inner: Arc<RwLock<CompactorManagerInner>>,
419}
420
421impl CompactorManager {
422 pub async fn with_meta(env: MetaSrvEnv) -> MetaResult<Self> {
423 let inner = CompactorManagerInner::with_meta(env).await?;
424
425 Ok(Self {
426 inner: Arc::new(RwLock::new(inner)),
427 })
428 }
429
430 pub fn for_test() -> Self {
432 let inner = CompactorManagerInner::for_test();
433 Self {
434 inner: Arc::new(RwLock::new(inner)),
435 }
436 }
437
438 pub fn next_compactor(&self) -> Option<Arc<Compactor>> {
439 self.inner.read().next_compactor()
440 }
441
442 pub fn add_compactor(&self, context_id: HummockContextId) -> CompactorSubscribeStreamReceiver {
443 self.inner.write().add_compactor(context_id)
444 }
445
446 pub fn abort_all_compactors(&self) {
447 self.inner.write().abort_all_compactors();
448 }
449
450 pub fn remove_compactor(&self, context_id: HummockContextId) {
451 self.inner.write().remove_compactor(context_id)
452 }
453
454 pub fn get_compactor(&self, context_id: HummockContextId) -> Option<Arc<Compactor>> {
455 self.inner.read().get_compactor(context_id)
456 }
457
458 pub fn check_tasks_status(
459 &self,
460 tasks: &[HummockCompactionTaskId],
461 slow_task_duration: Duration,
462 ) -> HashMap<HummockCompactionTaskId, (Duration, &'static str)> {
463 self.inner
464 .read()
465 .check_tasks_status(tasks, slow_task_duration)
466 }
467
468 pub fn get_heartbeat_expired_tasks(&self) -> Vec<CompactTask> {
469 self.inner.read().get_heartbeat_expired_tasks()
470 }
471
472 pub fn initiate_task_heartbeat(&self, task: CompactTask) {
473 self.inner.write().initiate_task_heartbeat(task);
474 }
475
476 pub fn remove_task_heartbeat(&self, task_id: u64) {
477 self.inner.write().remove_task_heartbeat(task_id);
478 }
479
480 pub fn update_task_heartbeats(
481 &self,
482 progress_list: &Vec<CompactTaskProgress>,
483 ) -> (Vec<HummockCompactionTaskId>, Vec<HummockCompactionTaskId>) {
484 self.inner.write().update_task_heartbeats(progress_list)
485 }
486
487 pub fn compactor_num(&self) -> usize {
488 self.inner.read().compactor_num()
489 }
490
491 pub fn get_progress(&self) -> Vec<CompactTaskProgress> {
492 self.inner.read().get_progress()
493 }
494}
495
496impl IcebergCompactor {
497 pub fn new(
498 context_id: HummockContextId,
499 sender: IcebergCompactorSubscribeStreamSender,
500 ) -> Self {
501 Self { context_id, sender }
502 }
503
504 pub fn context_id(&self) -> HummockContextId {
505 self.context_id
506 }
507
508 pub fn send_event(&self, event: IcebergCompactorSubscribeResponseEvent) -> MetaResult<()> {
509 fail_point!("iceberg_compaction_send_task_fail", |_| Err(
510 anyhow::anyhow!("iceberg_compaction_send_task_fail").into()
511 ));
512
513 self.sender
514 .send(Ok(SubscribeIcebergCompactionEventResponse {
515 create_at: SystemTime::now()
516 .duration_since(SystemTime::UNIX_EPOCH)
517 .expect("Clock may have gone backwards")
518 .as_millis() as u64,
519 event: Some(event),
520 }))
521 .map_err(|e| anyhow::anyhow!(e))?;
522
523 Ok(())
524 }
525
526 pub fn cancel_task(&self, task_id: IcebergCompactionTaskId) -> MetaResult<()> {
527 self.send_event(IcebergResponseEvent::CancelCompactTask(
528 IcebergCancelCompactTask { task_id },
529 ))
530 }
531}
532
533pub struct IcebergCompactorManagerInner {
534 pub compactor_map: HashMap<HummockContextId, Arc<IcebergCompactor>>,
535}
536
537pub struct IcebergCompactorManager {
538 inner: Arc<RwLock<IcebergCompactorManagerInner>>,
539}
540
541impl IcebergCompactorManager {
542 pub fn new() -> Self {
543 Self {
544 inner: Arc::new(RwLock::new(IcebergCompactorManagerInner {
545 compactor_map: HashMap::new(),
546 })),
547 }
548 }
549
550 pub fn add_compactor(
551 &self,
552 context_id: HummockContextId,
553 ) -> IcebergCompactorSubscribeStreamReceiver {
554 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
555 self.inner
556 .write()
557 .compactor_map
558 .insert(context_id, Arc::new(IcebergCompactor::new(context_id, tx)));
559 tracing::info!(context_id = %context_id, "Added iceberg compactor session");
560 rx
561 }
562
563 pub fn remove_compactor(&self, context_id: HummockContextId) {
564 if self
565 .inner
566 .write()
567 .compactor_map
568 .remove(&context_id)
569 .is_some()
570 {
571 tracing::info!(context_id = %context_id, "Removed iceberg compactor session");
572 }
573 }
574
575 pub fn get_compactor(&self, context_id: HummockContextId) -> Option<Arc<IcebergCompactor>> {
576 self.inner.read().compactor_map.get(&context_id).cloned()
577 }
578
579 pub fn next_compactor(&self) -> Option<Arc<IcebergCompactor>> {
580 use rand::Rng;
581 let compactor_map = &self.inner.read().compactor_map;
582 if compactor_map.is_empty() {
583 return None;
584 }
585 let rand_index = rand::rng().random_range(0..compactor_map.len());
586 compactor_map.values().nth(rand_index).cloned()
587 }
588
589 pub fn compactor_num(&self) -> usize {
590 self.inner.read().compactor_map.len()
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use std::sync::Arc;
597 use std::time::Duration;
598
599 use risingwave_hummock_sdk::compact_task::CompactTask;
600 use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId;
601 use risingwave_pb::hummock::CompactTaskProgress;
602 use risingwave_pb::iceberg_compaction::subscribe_iceberg_compaction_event_response::Event as IcebergResponseEvent;
603 use risingwave_rpc_client::HummockMetaClient;
604
605 use super::CompactorManagerInner;
606 use crate::hummock::compaction::selector::default_compaction_selector;
607 use crate::hummock::test_utils::{
608 add_ssts, register_table_ids_to_compaction_group, setup_compute_env,
609 };
610 use crate::hummock::{CompactorManager, IcebergCompactorManager, MockHummockMetaClient};
611
612 #[test]
613 fn test_update_task_heartbeats_returns_cancellation_ids() {
614 let mut manager = CompactorManagerInner::for_test();
615 for task_id in [1, 2] {
616 manager.initiate_task_heartbeat(CompactTask {
617 task_id,
618 ..Default::default()
619 });
620 }
621 manager.task_heartbeats.get_mut(&1).unwrap().expire_at = 0;
622 manager.task_heartbeats.get_mut(&2).unwrap().expire_at = u64::MAX;
623
624 let progress = [1, 2, 3]
625 .into_iter()
626 .map(|task_id| CompactTaskProgress {
627 task_id,
628 ..Default::default()
629 })
630 .collect();
631 let (expired, untracked) = manager.update_task_heartbeats(&progress);
632
633 assert_eq!(expired, vec![1]);
634 assert_eq!(untracked, vec![3]);
635 assert_eq!(manager.get_progress().len(), 2);
636 }
637
638 #[tokio::test]
639 async fn test_compactor_manager() {
640 let (env, context_id) = {
642 let (env, hummock_manager, _cluster_manager, worker_id) = setup_compute_env(80).await;
643 let context_id = worker_id as _;
644 let hummock_meta_client: Arc<dyn HummockMetaClient> = Arc::new(
645 MockHummockMetaClient::new(hummock_manager.clone(), context_id),
646 );
647 let compactor_manager = hummock_manager.compactor_manager.clone();
648 register_table_ids_to_compaction_group(
649 hummock_manager.as_ref(),
650 &[1],
651 StaticCompactionGroupId::StateDefault,
652 )
653 .await;
654 let _sst_infos =
655 add_ssts(1, hummock_manager.as_ref(), hummock_meta_client.clone()).await;
656 let _receiver = compactor_manager.add_compactor(context_id);
657 hummock_manager
658 .get_compact_task(
659 context_id,
660 StaticCompactionGroupId::StateDefault,
661 &mut *default_compaction_selector(),
662 )
663 .await
664 .unwrap()
665 .unwrap();
666 (env, context_id)
667 };
668
669 let compactor_manager = CompactorManager::with_meta(env).await.unwrap();
671 assert_eq!(compactor_manager.compactor_num(), 0);
674 assert!(compactor_manager.get_compactor(context_id).is_none());
675
676 tokio::time::sleep(Duration::from_secs(2)).await;
678 let expired = compactor_manager.get_heartbeat_expired_tasks();
679 assert_eq!(expired.len(), 1);
680
681 assert_eq!(compactor_manager.get_heartbeat_expired_tasks().len(), 1);
683
684 compactor_manager.update_task_heartbeats(&vec![CompactTaskProgress {
686 task_id: expired[0].task_id + 1,
687 num_ssts_sealed: 1,
688 num_ssts_uploaded: 1,
689 num_progress_key: 100,
690 ..Default::default()
691 }]);
692 assert_eq!(compactor_manager.get_heartbeat_expired_tasks().len(), 1);
693
694 compactor_manager.update_task_heartbeats(&vec![CompactTaskProgress {
696 task_id: expired[0].task_id,
697 num_ssts_sealed: 1,
698 num_ssts_uploaded: 1,
699 num_progress_key: 100,
700 ..Default::default()
701 }]);
702 assert_eq!(compactor_manager.get_heartbeat_expired_tasks().len(), 0);
703
704 assert_eq!(compactor_manager.compactor_num(), 0);
706 assert!(compactor_manager.get_compactor(context_id).is_none());
707 compactor_manager.add_compactor(context_id);
708 assert_eq!(compactor_manager.compactor_num(), 1);
709 assert_eq!(
710 compactor_manager
711 .get_compactor(context_id)
712 .unwrap()
713 .context_id(),
714 context_id
715 );
716 compactor_manager.remove_compactor(context_id);
718 assert_eq!(compactor_manager.compactor_num(), 0);
719 assert!(compactor_manager.get_compactor(context_id).is_none());
720 }
721
722 #[test]
723 fn test_iceberg_compactor_manager() {
724 let iceberg_context_id = 1000.into();
726 let iceberg_compactor_manager = IcebergCompactorManager::new();
727 assert_eq!(iceberg_compactor_manager.compactor_num(), 0);
728 assert!(
729 iceberg_compactor_manager
730 .get_compactor(iceberg_context_id)
731 .is_none()
732 );
733 let mut receiver = iceberg_compactor_manager.add_compactor(iceberg_context_id);
734 assert_eq!(iceberg_compactor_manager.compactor_num(), 1);
735 let compactor = iceberg_compactor_manager
736 .get_compactor(iceberg_context_id)
737 .unwrap();
738 assert_eq!(compactor.context_id(), iceberg_context_id);
739
740 compactor.cancel_task(42.into()).unwrap();
741 let event = receiver.try_recv().unwrap().unwrap().event.unwrap();
742 match event {
743 IcebergResponseEvent::CancelCompactTask(cancel_task) => {
744 assert_eq!(cancel_task.task_id, 42);
745 }
746 other => panic!("unexpected iceberg compactor event: {other:?}"),
747 }
748
749 iceberg_compactor_manager.remove_compactor(iceberg_context_id);
751 assert_eq!(iceberg_compactor_manager.compactor_num(), 0);
752 assert!(
753 iceberg_compactor_manager
754 .get_compactor(iceberg_context_id)
755 .is_none()
756 );
757 }
758}