Skip to main content

turbopack_core/chunk/
chunk_group.rs

1use std::sync::atomic::AtomicBool;
2
3use anyhow::{Context, Result, bail};
4use bincode::{Decode, Encode};
5use rustc_hash::{FxHashMap, FxHashSet};
6use smallvec::SmallVec;
7use tracing::Instrument;
8use turbo_rcstr::rcstr;
9use turbo_tasks::{
10    FxIndexSet, JoinIterExt, OperationVc, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc,
11};
12
13use super::{
14    ChunkItemWithAsyncModuleInfo, ChunkingContext, availability_info::AvailabilityInfo,
15    chunking::make_chunks,
16};
17use crate::{
18    chunk::{
19        ChunkGroupContent, ChunkGroupContentInner, ChunkableModule, ChunkingType, Chunks,
20        available_modules::{AvailableModuleItem, AvailableModulesSet},
21        chunk_item_batch::{ChunkItemBatchGroup, ChunkItemOrBatchWithAsyncModuleInfo},
22    },
23    emit_collect::CollectingModule,
24    module::{Module, Modules},
25    module_graph::{
26        GraphTraversalAction, ModuleGraph,
27        chunk_group_info::ChunkGroup,
28        merged_modules::MergedModuleInfo,
29        module_batch::{
30            ChunkableModuleBatchGroup, ChunkableModuleOrBatch, ModuleBatch, ModuleBatchGroup,
31            ModuleOrBatch,
32        },
33        module_batches::{BatchingConfig, ModuleBatchesGraphEdge},
34    },
35    output::OutputAssetsReference,
36};
37
38pub struct MakeChunkGroupResult {
39    pub chunks: ResolvedVc<Chunks>,
40    pub references: Vec<ResolvedVc<Box<dyn OutputAssetsReference>>>,
41    pub availability_info: AvailabilityInfo,
42}
43
44/// Creates one chunk group from `chunk_groups`, chunking all of them together.
45///
46/// See [`chunk_group_content`] for how several groups are combined.
47pub async fn make_chunk_group(
48    chunk_groups: SmallVec<[ChunkGroup; 1]>,
49    module_graph: ResolvedVc<ModuleGraph>,
50    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
51    availability_info: AvailabilityInfo,
52) -> Result<MakeChunkGroupResult> {
53    let can_split_async = chunking_context.chunk_loading().await?.can_split_async();
54    let is_nested_async_availability_enabled = *chunking_context
55        .is_nested_async_availability_enabled()
56        .await?;
57    let should_merge_modules = *chunking_context.is_module_merging_enabled().await?;
58    let batching_config = chunking_context.batching_config().to_resolved().await?;
59
60    let ChunkGroupContent {
61        inner,
62        availability_info: new_availability_info,
63    } = chunk_group_content(
64        module_graph,
65        chunk_groups,
66        ChunkGroupContentOptions {
67            availability_info,
68            can_split_async,
69            should_merge_modules,
70            batching_config,
71        },
72    )
73    .await?;
74    let ChunkGroupContentInner {
75        chunkable_items,
76        batch_groups,
77        async_modules,
78        collecting_modules,
79        available_modules: _,
80    } = &*inner;
81
82    let async_module_info = module_graph.async_module_info();
83
84    // Attach async info to chunkable modules
85    let mut chunk_items = chunkable_items
86        .iter()
87        .copied()
88        .map(|m| {
89            ChunkItemOrBatchWithAsyncModuleInfo::from_chunkable_module_or_batch(
90                m,
91                async_module_info,
92                *module_graph,
93                *chunking_context,
94            )
95        })
96        .join()
97        .await
98        .into_iter()
99        .filter_map(Result::transpose)
100        .collect::<Result<Vec<_>>>()?;
101
102    let chunk_item_batch_groups = batch_groups
103        .iter()
104        .map(|&batch_group| {
105            ChunkItemBatchGroup::from_module_batch_group(
106                ChunkableModuleBatchGroup::from_module_batch_group(*batch_group),
107                *module_graph,
108                *chunking_context,
109            )
110            .to_resolved()
111        })
112        .try_join()
113        .await?;
114
115    chunk_items.extend(
116        collecting_modules
117            .into_iter()
118            .map(async |module| {
119                let Some(entry_chunk_group) = new_availability_info.entry_group() else {
120                    bail!("unexpected collect module in non-entry chunk group",);
121                };
122                let chunk_item = module
123                    .as_chunk_item(*module_graph, *chunking_context, *entry_chunk_group)
124                    .to_resolved()
125                    .await?;
126                let chunk_type = chunk_item
127                    .into_trait_ref()
128                    .await?
129                    .ty()
130                    .to_resolved()
131                    .await?;
132                Ok(ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(
133                    ChunkItemWithAsyncModuleInfo {
134                        chunk_item,
135                        chunk_type,
136                        module: Some(ResolvedVc::upcast(*module)),
137                        async_info: None,
138                    },
139                ))
140            })
141            .try_join()
142            .await?,
143    );
144
145    // Insert async chunk loaders for every referenced async module
146    let async_availability_info =
147        if is_nested_async_availability_enabled || !availability_info.is_in_async_module() {
148            new_availability_info.in_async_module()
149        } else {
150            availability_info
151        };
152    let async_loaders = async_modules
153        .iter()
154        .copied()
155        .map(|module| {
156            chunking_context
157                .async_loader_chunk_item(*module, *module_graph, async_availability_info)
158                .to_resolved()
159        })
160        .try_join()
161        .await?;
162    let async_loader_chunk_items = async_loaders
163        .iter()
164        .map(async |&chunk_item| {
165            let chunk_type = chunk_item
166                .into_trait_ref()
167                .await?
168                .ty()
169                .to_resolved()
170                .await?;
171            Ok(ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(
172                ChunkItemWithAsyncModuleInfo {
173                    chunk_item,
174                    chunk_type,
175                    module: None,
176                    async_info: None,
177                },
178            ))
179        })
180        .try_join()
181        .await?;
182
183    chunk_items.extend(async_loader_chunk_items);
184
185    // Pass chunk items to chunking algorithm
186    let chunks = make_chunks(
187        *module_graph,
188        *chunking_context,
189        Vc::cell(chunk_items),
190        Vc::cell(chunk_item_batch_groups),
191        rcstr!(""),
192    )
193    .to_resolved()
194    .await?;
195
196    Ok(MakeChunkGroupResult {
197        chunks,
198        references: ResolvedVc::upcast_vec(async_loaders),
199        availability_info: new_availability_info,
200    })
201}
202
203#[turbo_tasks::task_input]
204#[derive(Debug, Clone, Hash, PartialEq, Eq, Encode, Decode)]
205pub struct ChunkGroupContentOptions {
206    /// The availability info of the chunk group
207    pub availability_info: AvailabilityInfo,
208    /// Whether async modules can be split into separate chunks
209    pub can_split_async: bool,
210    /// Whether module merging is enabled
211    pub should_merge_modules: bool,
212    /// The batching config to use
213    pub batching_config: ResolvedVc<BatchingConfig>,
214}
215
216/// Computes the content of one chunk group made of all `chunk_groups`.
217///
218/// The groups are chunked together, in a single task: the traversal starts from the entries of each
219/// group in list order and shares its state, so a module reached from an earlier group is not
220/// collected again for a later one. The resulting availability includes all of them. The modules
221/// of all [`ChunkGroup::Entry`] groups together form the entry group. Groups listed more than once
222/// are chunked once; an empty list is an error.
223pub async fn chunk_group_content(
224    module_graph: ResolvedVc<ModuleGraph>,
225    chunk_groups: SmallVec<[ChunkGroup; 1]>,
226    options: ChunkGroupContentOptions,
227) -> Result<ChunkGroupContent> {
228    let availability_info = options.availability_info;
229    let chunk_groups = dedup_chunk_groups(chunk_groups)?;
230
231    let entry_group: Option<ResolvedVc<Modules>> =
232        entry_group_modules(&chunk_groups).map(ResolvedVc::cell);
233
234    let chunk_group_content = chunk_group_content_operation(module_graph, chunk_groups, options);
235    let available_modules = available_modules_operation(chunk_group_content);
236    let availability_info = availability_info.with_modules(available_modules).await?;
237
238    let availability_info = if let Some(entry_group) = entry_group {
239        availability_info.with_entry_group(entry_group)
240    } else {
241        availability_info
242    };
243    let inner = chunk_group_content.connect().await?;
244
245    Ok(ChunkGroupContent {
246        inner,
247        availability_info,
248    })
249}
250
251/// Rejects an empty list and drops groups that are listed more than once (by key), keeping list
252/// order.
253fn dedup_chunk_groups(
254    chunk_groups: SmallVec<[ChunkGroup; 1]>,
255) -> Result<SmallVec<[ChunkGroup; 1]>> {
256    if chunk_groups.is_empty() {
257        bail!("Cannot chunk an empty list of chunk groups");
258    }
259    let mut keys = FxHashSet::default();
260    Ok(chunk_groups
261        .into_iter()
262        .filter(|chunk_group| keys.insert(chunk_group.key()))
263        .collect())
264}
265
266/// The modules of all [`ChunkGroup::Entry`] groups in `chunk_groups`, in list order, or `None` if
267/// there are none.
268fn entry_group_modules(chunk_groups: &[ChunkGroup]) -> Option<Vec<ResolvedVc<Box<dyn Module>>>> {
269    let mut modules = FxIndexSet::default();
270    let mut has_entry_group = false;
271    for chunk_group in chunk_groups {
272        if let ChunkGroup::Entry(entries) = chunk_group {
273            has_entry_group = true;
274            modules.extend(entries.iter().copied());
275        }
276    }
277    has_entry_group.then(|| modules.into_iter().collect())
278}
279
280#[turbo_tasks::function(operation)]
281async fn available_modules_operation(
282    chunk_group_content: OperationVc<ChunkGroupContentInner>,
283) -> Result<Vc<AvailableModulesSet>> {
284    Ok(*chunk_group_content.connect().await?.available_modules)
285}
286
287#[turbo_tasks::function(operation)]
288async fn chunk_group_content_operation(
289    module_graph: ResolvedVc<ModuleGraph>,
290    chunk_groups: SmallVec<[ChunkGroup; 1]>,
291    ChunkGroupContentOptions {
292        availability_info,
293        can_split_async,
294        should_merge_modules,
295        batching_config,
296    }: ChunkGroupContentOptions,
297) -> Result<Vc<ChunkGroupContentInner>> {
298    let module_batches_graph = module_graph.module_batches(*batching_config).await?;
299
300    type ModuleToChunkableMap = FxHashMap<ModuleOrBatch, ChunkableModuleOrBatch>;
301
302    struct TraverseState {
303        unsorted_items: ModuleToChunkableMap,
304        chunkable_items: FxIndexSet<ChunkableModuleOrBatch>,
305        async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
306        collecting_modules: FxIndexSet<ResolvedVc<Box<dyn CollectingModule>>>,
307    }
308
309    let mut state = TraverseState {
310        unsorted_items: FxHashMap::default(),
311        chunkable_items: FxIndexSet::default(),
312        async_modules: FxIndexSet::default(),
313        collecting_modules: FxIndexSet::default(),
314    };
315
316    let available_modules = match availability_info.available_modules() {
317        Some(available_modules) => Some(available_modules.snapshot().await?),
318        None => None,
319    };
320
321    // The entries of all groups, in list order. An entry shared by several groups is traversed
322    // once.
323    let entries = chunk_groups
324        .iter()
325        .flat_map(|chunk_group| chunk_group.entries())
326        .collect::<FxIndexSet<_>>()
327        .into_iter()
328        .map(|entry| module_batches_graph.get_entry_index(entry))
329        .try_join()
330        .await?;
331
332    let active_page_entries: Option<FxHashSet<ResolvedVc<Box<dyn Module>>>> =
333        if let Some(entry_modules) = entry_group_modules(&chunk_groups) {
334            Some(entry_modules.into_iter().collect())
335        } else if let Some(entry_group) = availability_info.entry_group() {
336            Some(entry_group.await?.iter().copied().collect())
337        } else {
338            None
339        };
340
341    {
342        let _span = tracing::trace_span!("traversal").entered();
343        module_batches_graph.traverse_edges_from_entries_dfs(
344            entries,
345            active_page_entries.as_ref(),
346            &mut state,
347            |parent_info, &node, state| {
348                if matches!(node, ModuleOrBatch::None(_)) {
349                    return Ok(GraphTraversalAction::Continue);
350                }
351                // Traced modules are completely ignored during chunking
352                if let Some((
353                    _,
354                    ModuleBatchesGraphEdge {
355                        ty: ChunkingType::Traced { .. },
356                        ..
357                    },
358                )) = parent_info
359                {
360                    return Ok(GraphTraversalAction::Exclude);
361                }
362
363                if let Some(collecting_module) = parent_info
364                    .and_then(|(_, edge)| edge.module)
365                    .and_then(ResolvedVc::try_downcast::<Box<dyn CollectingModule>>)
366                {
367                    state.collecting_modules.insert(collecting_module);
368                    return Ok(GraphTraversalAction::Continue);
369                }
370
371                let Some(chunkable_node) = ChunkableModuleOrBatch::from_module_or_batch(node)
372                else {
373                    return Ok(GraphTraversalAction::Exclude);
374                };
375
376                let is_available = available_modules
377                    .as_ref()
378                    .is_some_and(|available_modules| available_modules.get(chunkable_node.into()));
379
380                let Some((_, edge)) = parent_info else {
381                    // An entry from the entries list
382                    return Ok(if is_available {
383                        GraphTraversalAction::Exclude
384                    } else if state
385                        .unsorted_items
386                        .try_insert(node, chunkable_node)
387                        .is_ok()
388                    {
389                        GraphTraversalAction::Continue
390                    } else {
391                        GraphTraversalAction::Exclude
392                    });
393                };
394
395                Ok(match edge.ty {
396                    ChunkingType::Parallel { .. }
397                    | ChunkingType::Shared { .. }
398                    | ChunkingType::Collected { .. } => {
399                        if is_available {
400                            GraphTraversalAction::Exclude
401                        } else if state
402                            .unsorted_items
403                            .try_insert(node, chunkable_node)
404                            .is_ok()
405                        {
406                            GraphTraversalAction::Continue
407                        } else {
408                            GraphTraversalAction::Exclude
409                        }
410                    }
411                    ChunkingType::Async => {
412                        if can_split_async {
413                            let chunkable_module =
414                                ResolvedVc::try_downcast(edge.module.unwrap())
415                                    .context("Module in async chunking edge is not chunkable")?;
416                            let is_async_loader_available =
417                                available_modules.as_ref().is_some_and(|available_modules| {
418                                    available_modules
419                                        .get(AvailableModuleItem::AsyncLoader(chunkable_module))
420                                });
421                            if !is_async_loader_available {
422                                state.async_modules.insert(chunkable_module);
423                            }
424                            GraphTraversalAction::Exclude
425                        } else if is_available {
426                            GraphTraversalAction::Exclude
427                        } else if state
428                            .unsorted_items
429                            .try_insert(node, chunkable_node)
430                            .is_ok()
431                        {
432                            GraphTraversalAction::Continue
433                        } else {
434                            GraphTraversalAction::Exclude
435                        }
436                    }
437                    ChunkingType::Traced { .. } => {
438                        // handled above before the sidecast
439                        unreachable!();
440                    }
441                    ChunkingType::PerEntry => {
442                        // TODO currently not implemented
443                        bail!(
444                            "ChunkingType::PerEntry is currently only supported for \
445                             CollectingModule"
446                        );
447                    }
448                    ChunkingType::Emitted { .. } => {
449                        // Already handled during module graph construction
450                        GraphTraversalAction::Exclude
451                    }
452                    ChunkingType::Isolated { .. } => {
453                        // TODO currently not implemented
454                        GraphTraversalAction::Exclude
455                    }
456                })
457            },
458            |_, node, state| {
459                // Insert modules in topological order
460                if let Some(chunkable_module) = state.unsorted_items.get(node).copied() {
461                    state.chunkable_items.insert(chunkable_module);
462                }
463            },
464        )?;
465    }
466
467    // This needs to use the unmerged items
468    let available_modules: FxIndexSet<AvailableModuleItem> = state
469        .chunkable_items
470        .iter()
471        .copied()
472        .map(Into::into)
473        .chain(
474            state
475                .async_modules
476                .iter()
477                .copied()
478                .map(AvailableModuleItem::AsyncLoader),
479        )
480        .collect();
481    let available_modules: ResolvedVc<AvailableModulesSet> =
482        Vc::<AvailableModulesSet>::cell(available_modules)
483            .to_resolved()
484            .await?;
485
486    let should_merge_modules = if should_merge_modules {
487        let merged_modules = module_graph.merged_modules();
488        let merged_modules_ref = merged_modules.await?;
489        Some((merged_modules, merged_modules_ref))
490    } else {
491        None
492    };
493
494    let chunkable_items = if let Some((merged_modules, merged_modules_ref)) = &should_merge_modules
495    {
496        state
497            .chunkable_items
498            .into_iter()
499            .map(async |chunkable_module| match chunkable_module {
500                ChunkableModuleOrBatch::Module(module) => {
501                    let module = match merged_modules_ref
502                        .should_replace_module(ResolvedVc::upcast(module))
503                        .await?
504                    {
505                        Some(None) => return Ok(None),
506                        Some(Some(replacement)) => replacement,
507                        None => module,
508                    };
509
510                    Ok(Some(ChunkableModuleOrBatch::Module(module)))
511                }
512                ChunkableModuleOrBatch::Batch(batch) => Ok(Some(ChunkableModuleOrBatch::Batch(
513                    map_module_batch(*merged_modules, *batch)
514                        .to_resolved()
515                        .await?,
516                ))),
517                ChunkableModuleOrBatch::None(i) => Ok(Some(ChunkableModuleOrBatch::None(i))),
518            })
519            .try_flat_join()
520            .instrument(tracing::trace_span!("replace with merged modules"))
521            .await?
522    } else {
523        state.chunkable_items.into_iter().collect()
524    };
525
526    let mut batch_groups = FxIndexSet::default();
527    for &module in &chunkable_items {
528        if let Some(batch_group) = module_batches_graph.get_batch_group(&module.into()) {
529            batch_groups.insert(batch_group);
530        }
531    }
532
533    let batch_groups = if let Some((merged_modules, _)) = &should_merge_modules {
534        batch_groups
535            .into_iter()
536            .map(|group| map_module_batch_group(*merged_modules, *group).to_resolved())
537            .try_join()
538            .await?
539    } else {
540        batch_groups.into_iter().collect()
541    };
542
543    Ok(ChunkGroupContentInner {
544        chunkable_items,
545        batch_groups,
546        async_modules: state.async_modules,
547        collecting_modules: state.collecting_modules,
548        available_modules,
549    }
550    .cell())
551}
552
553#[turbo_tasks::function]
554async fn map_module_batch(
555    merged_modules: Vc<MergedModuleInfo>,
556    batch: Vc<ModuleBatch>,
557) -> Result<Vc<ModuleBatch>> {
558    let merged_modules = merged_modules.await?;
559    let batch_ref = batch.await?;
560
561    let modified = AtomicBool::new(false);
562    let modules = batch_ref
563        .modules
564        .iter()
565        .copied()
566        .map(async |module| {
567            let module = match merged_modules
568                .should_replace_module(ResolvedVc::upcast(module))
569                .await?
570            {
571                Some(None) => {
572                    modified.store(true, std::sync::atomic::Ordering::Relaxed);
573                    return Ok(None);
574                }
575                Some(Some(replacement)) => {
576                    modified.store(true, std::sync::atomic::Ordering::Relaxed);
577                    replacement
578                }
579                None => module,
580            };
581
582            Ok(Some(module))
583        })
584        .try_flat_join()
585        .await?;
586
587    if modified.into_inner() {
588        Ok(ModuleBatch::new(
589            ResolvedVc::deref_vec(modules),
590            batch_ref.chunk_groups.clone(),
591        ))
592    } else {
593        Ok(batch)
594    }
595}
596
597#[turbo_tasks::function]
598async fn map_module_batch_group(
599    merged_modules: Vc<MergedModuleInfo>,
600    group: Vc<ModuleBatchGroup>,
601) -> Result<Vc<ModuleBatchGroup>> {
602    let merged_modules_ref = merged_modules.await?;
603    let group_ref = group.await?;
604
605    let modified = AtomicBool::new(false);
606    let items = group_ref
607        .items
608        .iter()
609        .copied()
610        .map(async |chunkable_module| match chunkable_module {
611            ModuleOrBatch::Module(module) => {
612                let module = match merged_modules_ref.should_replace_module(module).await? {
613                    Some(None) => {
614                        modified.store(true, std::sync::atomic::Ordering::Relaxed);
615                        return Ok(None);
616                    }
617                    Some(Some(replacement)) => {
618                        modified.store(true, std::sync::atomic::Ordering::Relaxed);
619                        ResolvedVc::upcast(replacement)
620                    }
621                    None => module,
622                };
623
624                Ok(Some(ModuleOrBatch::Module(module)))
625            }
626            ModuleOrBatch::Batch(batch) => {
627                let replacement = map_module_batch(merged_modules, *batch)
628                    .to_resolved()
629                    .await?;
630                if replacement != batch {
631                    modified.store(true, std::sync::atomic::Ordering::Relaxed);
632                }
633                Ok(Some(ModuleOrBatch::Batch(replacement)))
634            }
635            ModuleOrBatch::None(i) => Ok(Some(ModuleOrBatch::None(i))),
636        })
637        .try_flat_join()
638        .await?;
639
640    if modified.into_inner() {
641        Ok(ModuleBatchGroup::new(items, group_ref.chunk_groups.clone()))
642    } else {
643        Ok(group)
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use anyhow::Result;
650    use smallvec::smallvec;
651    use turbo_rcstr::rcstr;
652    use turbo_tasks::{Completion, ResolvedVc, Vc};
653    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
654    use turbo_tasks_fs::{File, FileContent, FileSystem, VirtualFileSystem};
655
656    use super::{dedup_chunk_groups, entry_group_modules};
657    use crate::{
658        asset::AssetContent, module::Module, module_graph::chunk_group_info::ChunkGroup,
659        raw_module::RawModule, source::Source, virtual_source::VirtualSource,
660    };
661
662    async fn test_module(name: &str) -> Result<ResolvedVc<Box<dyn Module>>> {
663        let root = VirtualFileSystem::new_with_name(rcstr!("chunk-group-test"))
664            .root()
665            .await?;
666        let source = Vc::upcast::<Box<dyn Source>>(VirtualSource::new(
667            root.join(name)?,
668            AssetContent::file(FileContent::Content(File::from("")).cell()),
669        ));
670        Vc::upcast::<Box<dyn Module>>(RawModule::new(source))
671            .to_resolved()
672            .await
673    }
674
675    #[turbo_tasks::function(operation, root)]
676    async fn combines_chunk_group_lists_operation() -> Result<Vc<Completion>> {
677        let error = dedup_chunk_groups(smallvec![]).unwrap_err().to_string();
678        assert_eq!(error, "Cannot chunk an empty list of chunk groups");
679
680        let a = test_module("a.js").await?;
681        let b = test_module("b.js").await?;
682        let c = test_module("c.js").await?;
683
684        let shared_a = ChunkGroup::Shared(a);
685        let shared_b = ChunkGroup::Shared(b);
686        let groups = dedup_chunk_groups(smallvec![
687            shared_a.clone(),
688            shared_b.clone(),
689            shared_a.clone()
690        ])?;
691        assert_eq!(
692            groups.iter().map(ChunkGroup::key).collect::<Vec<_>>(),
693            vec![shared_a.key(), shared_b.key()]
694        );
695
696        assert_eq!(entry_group_modules(&[ChunkGroup::Shared(a)]), None);
697        assert_eq!(
698            entry_group_modules(&[
699                ChunkGroup::Entry(vec![a, b]),
700                ChunkGroup::Shared(c),
701                ChunkGroup::Entry(vec![b, c]),
702            ]),
703            Some(vec![a, b, c])
704        );
705
706        Ok(Completion::new())
707    }
708
709    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
710    async fn combines_chunk_group_lists() {
711        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
712            BackendOptions::default(),
713            noop_backing_storage(),
714        ));
715        tt.run_once(async {
716            combines_chunk_group_lists_operation()
717                .read_strongly_consistent()
718                .await?;
719            Ok(())
720        })
721        .await
722        .unwrap();
723    }
724}